import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Cron } from '@nestjs/schedule';
import { ViharStatus, DistanceApprovalStatus } from '@prisma/client';
import { PrismaService } from '../../common/prisma/prisma.service';
import { RoutesService } from '../routes/routes.service';

@Injectable()
export class AutoCloseService {
  private readonly logger = new Logger(AutoCloseService.name);
  private readonly autoCloseAfterHours: number;

  constructor(
    private readonly prisma: PrismaService,
    private readonly routes: RoutesService,
    config: ConfigService,
  ) {
    this.autoCloseAfterHours = parseInt(config.get<string>('AUTO_CLOSE_AFTER_HOURS') ?? '6', 10);
  }

  /**
   * Scheduled every 30 minutes in dev (NestJS Schedule).
   * In prod on kreonAI5, the same logic is invoked via the internal endpoint
   * by an external PM2 cron - either path is safe (idempotent).
   */
  @Cron('0 */6 * * *')
  async runScheduled() {
    try {
      const result = await this.run();
      if (result.closed > 0) {
        this.logger.log(`Auto-closed ${result.closed} vihars`);
      }
    } catch (e) {
      this.logger.error('Auto-close cron failed', e instanceof Error ? e.stack : String(e));
    }
  }

  /**
   * Find in_progress vihars whose planned arrival was more than
   * AUTO_CLOSE_AFTER_HOURS ago, and close them with planned values.
   */
  async run(): Promise<{ closed: number; viharIds: number[] }> {
    // Find candidates. We compute planned arrival in JS rather than SQL because
    // planned_start_time is a TIME column (string) - simpler and portable.
    const candidates = await this.prisma.vihar.findMany({
      where: {
        status: ViharStatus.in_progress,
        viharDate: {
          // Don't even consider vihars in the future or far future
          lte: new Date(),
        },
      },
      include: {
        allocations: { where: { isActive: true, checkOutAt: null } },
      },
    });

    const now = new Date();
    const cutoffMs = this.autoCloseAfterHours * 3600_000;
    const closed: number[] = [];

    for (const v of candidates) {
      const plannedArrival = this.computePlannedArrival(v.viharDate, v.plannedStartTime, v.plannedDurationSec);
      if (now.getTime() - plannedArrival.getTime() < cutoffMs) continue;

      // Determine distance to record
      const km = v.plannedDistanceMeters
        ? v.plannedDistanceMeters / 1000
        : await this.tryFetchPlannedKm(v.cityId, v.departureLocationId, v.arrivalLocationId);

      await this.prisma.$transaction(async (tx) => {
        // Close all open allocations
        await tx.viharVolunteer.updateMany({
          where: { viharId: v.viharId, isActive: true, checkOutAt: null },
          data: {
            checkOutAt: plannedArrival,
            volunteerDistanceKm: km,
            distanceApprovalStatus: DistanceApprovalStatus.auto_accepted,
            autoFilledByCron: true,
          },
        });
        // Close the vihar
        await tx.vihar.update({
          where: { viharId: v.viharId },
          data: {
            status: ViharStatus.auto_closed,
            actualEndAt: plannedArrival,
            actualDistanceKm: km ?? null,
            autoClosed: true,
          },
        });
      });
      closed.push(v.viharId);
    }

    return { closed: closed.length, viharIds: closed };
  }

  private computePlannedArrival(viharDate: Date, plannedStart: string, durSec: number | null): Date {
    const [h, m] = plannedStart.split(':').map((x) => parseInt(x!, 10));
    const start = new Date(viharDate);
    start.setUTCHours(h ?? 0, m ?? 0, 0, 0);
    return new Date(start.getTime() + (durSec ?? 0) * 1000);
  }

  private async tryFetchPlannedKm(cityId: number, fromId: number, toId: number): Promise<number | null> {
    try {
      const r = await this.routes.getWalkingByLocationIds(cityId, fromId, toId);
      return r.distanceMeters / 1000;
    } catch {
      return null;
    }
  }
}
