import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { BaseRepository } from '@edr/api-common';
import { Repository, Between } from 'typeorm';
import { MaintenanceSchedule, MaintenanceStatus } from './entities/maintenance-schedule.entity';
import { MaintenanceCost } from './entities/maintenance-cost.entity';

@Injectable()
export class MaintenanceRepository extends BaseRepository<MaintenanceSchedule> {
  constructor(
    @InjectRepository(MaintenanceSchedule)
    private readonly scheduleRepository: Repository<MaintenanceSchedule>,
    @InjectRepository(MaintenanceCost)
    private readonly costRepository: Repository<MaintenanceCost>,
  ) {
    super(scheduleRepository);
  }

  async getUpcomingMaintenance(vehicleId: string, daysAhead: number = 30) {
    const futureDate = new Date(Date.now() + daysAhead * 24 * 60 * 60 * 1000);
    return this.scheduleRepository.find({
      where: {
        vehicleId,
        scheduledDate: Between(new Date(), futureDate),
        status: MaintenanceStatus.SCHEDULED,
      },
      order: { scheduledDate: 'ASC' },
    });
  }

  async getMaintenanceCosts(vehicleId: string, startDate: Date, endDate: Date) {
    return this.costRepository.find({
      where: {
        vehicleId,
        incurredDate: Between(startDate, endDate),
      },
      order: { incurredDate: 'DESC' },
    });
  }

  async getTotalMaintenanceCost(vehicleId: string, startDate: Date, endDate: Date) {
    const result = await this.costRepository
      .createQueryBuilder()
      .select('SUM(cost_amount)', 'total')
      .where('vehicle_id = :vehicleId', { vehicleId })
      .andWhere('incurred_date BETWEEN :startDate AND :endDate', { startDate, endDate })
      .getRawOne();
    return result?.total || 0;
  }

  /**
   * Fleet-wide "next due" board: one row per vehicle with a SCHEDULED
   * maintenance item, driven by time AND km — whichever is soonest. Current km
   * is the vehicle's latest fuel-up odometer reading (how mileage is actually
   * captured today), falling back to vehicle.actual_distance_km when the
   * vehicle has no fuel purchase on file yet.
   */
  async getDueBoard(): Promise<
    Array<{
      scheduleId: string;
      vehicleId: string;
      plateNumber: string;
      maintenanceType: string;
      serviceItem: string | null;
      description: string;
      scheduledDate: Date;
      nextDueDate: Date | null;
      nextDueKm: number | null;
      currentKm: number | null;
      kmRemaining: number | null;
      daysRemaining: number | null;
      overdue: boolean;
    }>
  > {
    // Every SCHEDULED item, not one per vehicle — a truck legitimately holds
    // several (oil vs tires intervals differ).
    return this.scheduleRepository.manager.query(`
      SELECT
        s.id AS "scheduleId",
        s.vehicle_id AS "vehicleId",
        v.plate_number AS "plateNumber",
        s.maintenance_type AS "maintenanceType",
        s.service_item AS "serviceItem",
        s.description,
        s.scheduled_date AS "scheduledDate",
        s.next_due_date AS "nextDueDate",
        s.next_due_km AS "nextDueKm",
        COALESCE(fp.max_odometer, v.actual_distance_km) AS "currentKm",
        CASE WHEN s.next_due_km IS NOT NULL
          THEN s.next_due_km - COALESCE(fp.max_odometer, v.actual_distance_km, 0)
          ELSE NULL END AS "kmRemaining",
        CASE WHEN s.next_due_date IS NOT NULL
          THEN EXTRACT(DAY FROM s.next_due_date - now())
          ELSE NULL END AS "daysRemaining",
        (
          (s.next_due_date IS NOT NULL AND s.next_due_date <= now())
          OR (s.next_due_km IS NOT NULL
              AND COALESCE(fp.max_odometer, v.actual_distance_km, 0) >= s.next_due_km)
        ) AS overdue
      FROM freight.maintenance_schedules s
      JOIN freight.vehicles v ON v.id = s.vehicle_id AND v.deleted_at IS NULL
      LEFT JOIN LATERAL (
        SELECT MAX(odometer_reading) AS max_odometer
        FROM freight.fuel_purchases fp2
        WHERE fp2.vehicle_id = s.vehicle_id
      ) fp ON true
      WHERE s.status = 'SCHEDULED' AND s.deleted_at IS NULL
      ORDER BY s.vehicle_id, s.scheduled_date ASC
    `);
  }

  /**
   * SCHEDULED items that have crossed their km or date due-point and have not
   * yet been notified. Backs the daily km/date maintenance alert.
   */
  async getUnnotifiedDue(): Promise<
    Array<{
      id: string;
      vehicleId: string;
      plateNumber: string;
      maintenanceType: string;
      description: string;
      nextDueKm: number | null;
      nextDueDate: Date | null;
      currentKm: number | null;
    }>
  > {
    return this.scheduleRepository.manager.query(`
      SELECT
        s.id,
        s.vehicle_id AS "vehicleId",
        v.plate_number AS "plateNumber",
        s.maintenance_type AS "maintenanceType",
        s.description,
        s.next_due_km AS "nextDueKm",
        s.next_due_date AS "nextDueDate",
        COALESCE(fp.max_odometer, v.actual_distance_km) AS "currentKm"
      FROM freight.maintenance_schedules s
      JOIN freight.vehicles v ON v.id = s.vehicle_id AND v.deleted_at IS NULL
      LEFT JOIN LATERAL (
        SELECT MAX(odometer_reading) AS max_odometer
        FROM freight.fuel_purchases fp2
        WHERE fp2.vehicle_id = s.vehicle_id
      ) fp ON true
      WHERE s.status = 'SCHEDULED'
        AND s.deleted_at IS NULL
        AND s.due_notified_at IS NULL
        AND (
          (s.next_due_date IS NOT NULL AND s.next_due_date <= now())
          OR (s.next_due_km IS NOT NULL
              AND COALESCE(fp.max_odometer, v.actual_distance_km, 0) >= s.next_due_km)
        )
    `);
  }
}
