import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, IsNull, Repository } from 'typeorm';
import { ComplianceRepository } from './compliance.repository';
import {
  ComplianceRecord,
  ComplianceStatus,
  ComplianceType,
} from './entities/compliance-record.entity';
import {
  CreateComplianceRecordDto,
  UpdateComplianceRecordDto,
} from './dto/create-compliance-record.dto';
import { Vehicle } from '../vehicles/entities/vehicle.entity';
import { Driver } from '../drivers/entities/driver.entity';

const DUE_SOON_DAYS = 30;
const MS_PER_DAY = 24 * 60 * 60 * 1000;

export type AlertSeverity = 'OVERDUE' | 'DUE_SOON';

export interface ComplianceAlert {
  vehicleId: string;
  vehiclePlate?: string;
  kind: string;
  label: string;
  expiryDate: string;
  daysUntil: number;
  severity: AlertSeverity;
}

@Injectable()
export class ComplianceService {
  constructor(
    private readonly complianceRepository: ComplianceRepository,
    @InjectRepository(Vehicle)
    private readonly vehicleRepo: Repository<Vehicle>,
    @InjectRepository(Driver)
    private readonly driverRepo: Repository<Driver>,
  ) {}

  async create(dto: CreateComplianceRecordDto): Promise<ComplianceRecord> {
    return this.complianceRepository.create({
      ...dto,
      status: dto.status ?? this.deriveStatus(dto.expiryDate),
    });
  }

  async findAll(filter: { vehicleId?: string; type?: ComplianceType } = {}) {
    return this.complianceRepository.findWithFilters(filter);
  }

  async findById(id: string): Promise<ComplianceRecord> {
    const record = await this.complianceRepository.findById(id);
    if (!record) {
      throw new NotFoundException(`Compliance record ${id} not found`);
    }
    return record;
  }

  async update(id: string, dto: UpdateComplianceRecordDto): Promise<ComplianceRecord> {
    await this.findById(id);
    const nextExpiry = dto.expiryDate;
    const updated = await this.complianceRepository.update(id, {
      ...dto,
      // Re-derive status when expiry changes and the caller didn't set it explicitly.
      status: dto.status ?? (nextExpiry ? this.deriveStatus(nextExpiry) : undefined),
    });
    return updated!;
  }

  async remove(id: string): Promise<void> {
    await this.findById(id);
    await this.complianceRepository.softDelete(id);
  }

  /**
   * Flat list of compliance items that are overdue or due within 30 days.
   * Combines the compliance_records table with the vehicle expiry columns
   * (insurance / registration / next inspection) and assigned-driver license
   * expiry. `new Date()` is fine here — this is the NestJS API runtime.
   */
  async getAlerts(): Promise<ComplianceAlert[]> {
    const now = new Date();
    const alerts: ComplianceAlert[] = [];

    const vehicles = await this.vehicleRepo.find({ where: { deletedAt: IsNull() } });
    const vehicleById = new Map(vehicles.map((v) => [v.id, v]));
    const plateOf = (v?: Vehicle) => v?.plateNumber ?? v?.code ?? undefined;

    // 1. Compliance records
    const records = await this.complianceRepository.findWithFilters();
    for (const record of records) {
      const computed = this.computeSeverity(record.expiryDate, now);
      if (!computed) continue;
      const vehicle = vehicleById.get(record.vehicleId);
      alerts.push({
        vehicleId: record.vehicleId,
        vehiclePlate: plateOf(vehicle),
        kind: record.type,
        label: record.documentNumber
          ? `${record.type} · ${record.documentNumber}`
          : record.type,
        expiryDate: record.expiryDate,
        daysUntil: computed.daysUntil,
        severity: computed.severity,
      });
    }

    // 2. Vehicle-level expiry columns
    const vehicleFields: { field: keyof Vehicle; kind: string; label: string }[] = [
      { field: 'insuranceExpiry', kind: 'INSURANCE', label: 'Insurance' },
      { field: 'registrationExpiry', kind: 'REGISTRATION', label: 'Registration' },
      { field: 'nextInspectionDate', kind: 'INSPECTION', label: 'Inspection' },
    ];
    for (const vehicle of vehicles) {
      for (const { field, kind, label } of vehicleFields) {
        const value = vehicle[field] as string | undefined;
        if (!value) continue;
        const computed = this.computeSeverity(value, now);
        if (!computed) continue;
        alerts.push({
          vehicleId: vehicle.id,
          vehiclePlate: plateOf(vehicle),
          kind,
          label,
          expiryDate: value,
          daysUntil: computed.daysUntil,
          severity: computed.severity,
        });
      }
    }

    // 3. Assigned-driver license expiry
    const driverIds = [
      ...new Set(vehicles.map((v) => v.assignedDriverId).filter((id): id is string => !!id)),
    ];
    if (driverIds.length > 0) {
      const drivers = await this.driverRepo.find({ where: { id: In(driverIds) } });
      const driverById = new Map(drivers.map((d) => [d.id, d]));
      for (const vehicle of vehicles) {
        if (!vehicle.assignedDriverId) continue;
        const driver = driverById.get(vehicle.assignedDriverId);
        if (!driver?.licenseExpiryDate) continue;
        const expiry =
          driver.licenseExpiryDate instanceof Date
            ? driver.licenseExpiryDate.toISOString().slice(0, 10)
            : String(driver.licenseExpiryDate);
        const computed = this.computeSeverity(expiry, now);
        if (!computed) continue;
        alerts.push({
          vehicleId: vehicle.id,
          vehiclePlate: plateOf(vehicle),
          kind: 'DRIVER_LICENSE',
          label: `Driver License · ${driver.firstName ?? ''} ${driver.lastName ?? ''}`.trim(),
          expiryDate: expiry,
          daysUntil: computed.daysUntil,
          severity: computed.severity,
        });
      }
    }

    return alerts.sort((a, b) => a.daysUntil - b.daysUntil);
  }

  private computeSeverity(
    expiryDate: string,
    now: Date,
  ): { daysUntil: number; severity: AlertSeverity } | null {
    const daysUntil = Math.ceil((new Date(expiryDate).getTime() - now.getTime()) / MS_PER_DAY);
    if (daysUntil < 0) return { daysUntil, severity: 'OVERDUE' };
    if (daysUntil <= DUE_SOON_DAYS) return { daysUntil, severity: 'DUE_SOON' };
    return null;
  }

  private deriveStatus(expiryDate: string): ComplianceStatus {
    const daysUntil = Math.ceil(
      (new Date(expiryDate).getTime() - Date.now()) / MS_PER_DAY,
    );
    if (daysUntil < 0) return ComplianceStatus.EXPIRED;
    if (daysUntil <= DUE_SOON_DAYS) return ComplianceStatus.EXPIRING;
    return ComplianceStatus.VALID;
  }
}
