import { Injectable, Logger } from '@nestjs/common';
import { BookingStatus } from '../../../entities/booking.entity';
import { BookingRepository } from '../repositories/booking.repository';

export interface BookingStatusSyncResult {
  today: string;
  tenantsProcessed: number;
  upcomingUpdated: number;
  ongoingUpdated: number;
  completedUpdated: number;
}

/**
 * Date-driven booking status.
 *
 * Cancelled, completed, and on_hold are never changed. Otherwise:
 *   - today within [start_date, end_date] → ongoing
 *   - today after end_date → completed
 *   - today before start_date and status is ongoing → upcoming
 *     (corrects a premature manual "On Going")
 *
 * Returns the target status, or null when no write is required.
 */
export function resolveDateDrivenBookingStatus(
  currentStatus: string,
  startDate: string,
  endDate: string,
  todayUtc: string,
): BookingStatus | null {
  if (
    currentStatus === BookingStatus.CANCELLED ||
    currentStatus === BookingStatus.COMPLETED ||
    currentStatus === BookingStatus.ON_HOLD
  ) {
    return null;
  }

  const start = startDate.slice(0, 10);
  const end = endDate.slice(0, 10);
  const today = todayUtc.slice(0, 10);

  let target: BookingStatus | null = null;
  if (start <= today && today <= end) target = BookingStatus.ONGOING;
  else if (end < today) target = BookingStatus.COMPLETED;
  else if (today < start && currentStatus === BookingStatus.ONGOING) {
    target = BookingStatus.UPCOMING;
  }

  if (!target || target === currentStatus) return null;
  return target;
}

/**
 * Shared booking-status updater used by the daily cron and the manual
 * `booking:sync-status` CLI. Does not go through BookingService.changeStatus
 * — that path is user-facing (transition graph, hold reason, CLS viewer).
 */
@Injectable()
export class BookingStatusSyncService {
  private readonly logger = new Logger(BookingStatusSyncService.name);

  constructor(private readonly repo: BookingRepository) {}

  async syncAllTenants(todayUtc?: string): Promise<BookingStatusSyncResult> {
    const today = (todayUtc ?? new Date().toISOString().slice(0, 10)).slice(0, 10);
    const tenantIds = await this.repo.listActiveTenantIdsForStatusSync();

    let upcomingUpdated = 0;
    let ongoingUpdated = 0;
    let completedUpdated = 0;
    const failedTenantIds: string[] = [];

    for (const tenantId of tenantIds) {
      try {
        const counts = await this.syncTenant(tenantId, today);
        upcomingUpdated += counts.upcoming;
        ongoingUpdated += counts.ongoing;
        completedUpdated += counts.completed;
      } catch (err) {
        failedTenantIds.push(tenantId);
        this.logger.error(
          `Booking status sync failed for tenant ${tenantId}`,
          err instanceof Error ? err.stack : String(err),
        );
      }
    }

    const result: BookingStatusSyncResult = {
      today,
      tenantsProcessed: tenantIds.length - failedTenantIds.length,
      upcomingUpdated,
      ongoingUpdated,
      completedUpdated,
    };

    this.logger.log(
      `Booking status sync (${today} UTC): ${result.upcomingUpdated} → upcoming, ${result.ongoingUpdated} → ongoing, ${result.completedUpdated} → completed, ${result.tenantsProcessed} tenant(s)`,
    );

    if (failedTenantIds.length > 0) {
      throw new Error(`Booking status sync failed for ${failedTenantIds.length} tenant(s)`);
    }

    return result;
  }

  async syncTenant(
    tenantId: string,
    todayUtc: string,
  ): Promise<{ upcoming: number; ongoing: number; completed: number }> {
    const rows = await this.repo.findEligibleForDateSync(tenantId);
    const toUpcoming: string[] = [];
    const toOngoing: string[] = [];
    const toCompleted: string[] = [];

    for (const row of rows) {
      const next = resolveDateDrivenBookingStatus(
        row.status,
        row.start_date,
        row.end_date,
        todayUtc,
      );
      if (next === BookingStatus.UPCOMING) toUpcoming.push(row.id);
      else if (next === BookingStatus.ONGOING) toOngoing.push(row.id);
      else if (next === BookingStatus.COMPLETED) toCompleted.push(row.id);
    }

    await this.repo.bulkUpdateStatus(tenantId, toUpcoming, BookingStatus.UPCOMING);
    await this.repo.bulkUpdateStatus(tenantId, toOngoing, BookingStatus.ONGOING);
    await this.repo.bulkUpdateStatus(tenantId, toCompleted, BookingStatus.COMPLETED);

    return {
      upcoming: toUpcoming.length,
      ongoing: toOngoing.length,
      completed: toCompleted.length,
    };
  }
}
