import { randomUUID } from 'crypto';
import type {
  CreateMaintenanceRecordRequest,
  UpdateMaintenanceRecordRequest,
  CreateScheduleRequest,
  UpdateScheduleRequest,
  MaintenanceRecord,
  MaintenanceSchedule,
  MaintenanceRecordResponse,
  MaintenanceScheduleResponse,
  MaintenanceCategory,
  ScheduleType,
  MaintenanceCostStats,
  ReceiptDocumentMeta
} from './maintenance.types';
import { validateSubtypes } from './maintenance.types';
import { MaintenanceRepository } from '../data/maintenance.repository';
import pool from '../../../core/config/database';

export class MaintenanceService {
  private readonly repo = new MaintenanceRepository(pool);

  async createRecord(userId: string, body: CreateMaintenanceRecordRequest): Promise<MaintenanceRecord> {
    await this.assertVehicleOwnership(userId, body.vehicleId);

    if (!validateSubtypes(body.category, body.subtypes)) {
      const err: any = new Error('Invalid subtypes for selected category');
      err.statusCode = 400;
      throw err;
    }

    const id = randomUUID();
    const record = await this.repo.insertRecord({
      id,
      userId,
      vehicleId: body.vehicleId,
      category: body.category,
      subtypes: body.subtypes,
      date: body.date,
      odometerReading: body.odometerReading,
      cost: body.cost,
      shopName: body.shopName,
      notes: body.notes,
      receiptDocumentId: body.receiptDocumentId,
    });

    // Auto-link: Find and update matching 'time_since_last' schedules
    await this.autoLinkRecordToSchedules(userId, record);

    return record;
  }

  async getRecord(userId: string, id: string): Promise<MaintenanceRecordResponse | null> {
    const result = await this.repo.findRecordByIdWithDocument(id, userId);
    if (!result) return null;
    return this.toRecordResponse(result.record, result.receiptDocument);
  }

  async getRecords(userId: string, filters?: { vehicleId?: string; category?: MaintenanceCategory }): Promise<MaintenanceRecordResponse[]> {
    const records = await this.repo.findRecordsByUserId(userId, filters);
    return records.map(r => this.toRecordResponse(r));
  }

  async getRecordsByVehicle(userId: string, vehicleId: string): Promise<MaintenanceRecordResponse[]> {
    const records = await this.repo.findRecordsByVehicleId(vehicleId, userId);
    return records.map(r => this.toRecordResponse(r));
  }

  async getVehicleMaintenanceCosts(vehicleId: string, userId: string): Promise<MaintenanceCostStats> {
    const records = await this.repo.findRecordsByVehicleId(vehicleId, userId);
    const totalCost = records.reduce((sum, r) => {
      if (r.cost === null || r.cost === undefined) return sum;
      const cost = Number(r.cost);
      if (isNaN(cost)) {
        throw new Error(`Invalid cost value for maintenance record ${r.id}`);
      }
      return sum + cost;
    }, 0);
    return { totalCost, recordCount: records.length };
  }

  async updateRecord(userId: string, id: string, patch: UpdateMaintenanceRecordRequest): Promise<MaintenanceRecordResponse | null> {
    const existing = await this.repo.findRecordById(id, userId);
    if (!existing) return null;

    if (patch.category || patch.subtypes) {
      const category = patch.category || existing.category;
      const subtypes = patch.subtypes || existing.subtypes;
      if (!validateSubtypes(category, subtypes)) {
        const err: any = new Error('Invalid subtypes for selected category');
        err.statusCode = 400;
        throw err;
      }
    }

    // Convert nulls to undefined for repository compatibility
    const cleanPatch = Object.fromEntries(
      Object.entries(patch).map(([k, v]) => [k, v === null ? undefined : v])
    ) as Partial<Pick<MaintenanceRecord, 'date' | 'notes' | 'category' | 'subtypes' | 'odometerReading' | 'cost' | 'shopName'>>;

    const updated = await this.repo.updateRecord(id, userId, cleanPatch);
    if (!updated) return null;
    return this.toRecordResponse(updated);
  }

  async deleteRecord(userId: string, id: string): Promise<void> {
    await this.repo.deleteRecord(id, userId);
  }

  async createSchedule(userId: string, body: CreateScheduleRequest): Promise<MaintenanceSchedule> {
    await this.assertVehicleOwnership(userId, body.vehicleId);

    if (!validateSubtypes(body.category, body.subtypes)) {
      const err: any = new Error('Invalid subtypes for selected category');
      err.statusCode = 400;
      throw err;
    }

    if (!body.intervalMonths && !body.intervalMiles) {
      const err: any = new Error('At least one interval (months or miles) is required');
      err.statusCode = 400;
      throw err;
    }

    const id = randomUUID();
    return this.repo.insertSchedule({
      id,
      userId,
      vehicleId: body.vehicleId,
      category: body.category,
      subtypes: body.subtypes,
      intervalMonths: body.intervalMonths,
      intervalMiles: body.intervalMiles,
      isActive: true,
      emailNotifications: body.emailNotifications ?? false,
    });
  }

  async getSchedules(userId: string, filters?: { vehicleId?: string }): Promise<MaintenanceScheduleResponse[]> {
    let schedules: MaintenanceSchedule[];
    if (filters?.vehicleId) {
      schedules = await this.repo.findSchedulesByVehicleId(filters.vehicleId, userId);
    } else {
      schedules = await this.repo.findSchedulesByVehicleId('', userId);
    }
    return schedules.map(s => this.toScheduleResponse(s));
  }

  async getSchedulesByVehicle(userId: string, vehicleId: string): Promise<MaintenanceScheduleResponse[]> {
    const schedules = await this.repo.findSchedulesByVehicleId(vehicleId, userId);
    return schedules.map(s => this.toScheduleResponse(s));
  }

  async updateSchedule(userId: string, id: string, patch: UpdateScheduleRequest): Promise<MaintenanceScheduleResponse | null> {
    const existing = await this.repo.findScheduleById(id, userId);
    if (!existing) return null;

    if (patch.category || patch.subtypes) {
      const category = patch.category || existing.category;
      const subtypes = patch.subtypes || existing.subtypes;
      if (!validateSubtypes(category, subtypes)) {
        const err: any = new Error('Invalid subtypes for selected category');
        err.statusCode = 400;
        throw err;
      }
    }

    const needsRecalculation =
      patch.intervalMonths !== undefined ||
      patch.intervalMiles !== undefined;

    const patchWithRecalc: any = { ...patch };
    if (needsRecalculation) {
      const nextDue = this.calculateNextDue({
        lastServiceDate: existing.lastServiceDate,
        lastServiceMileage: existing.lastServiceMileage,
        intervalMonths: patch.intervalMonths ?? existing.intervalMonths,
        intervalMiles: patch.intervalMiles ?? existing.intervalMiles,
      });
      patchWithRecalc.nextDueDate = nextDue.nextDueDate ?? undefined;
      patchWithRecalc.nextDueMileage = nextDue.nextDueMileage ?? undefined;
    }

    // Convert nulls to undefined for repository compatibility
    const cleanPatch = Object.fromEntries(
      Object.entries(patchWithRecalc).map(([k, v]) => [k, v === null ? undefined : v])
    ) as Partial<Pick<MaintenanceSchedule, 'category' | 'subtypes' | 'intervalMonths' | 'intervalMiles' | 'isActive' | 'emailNotifications' | 'lastServiceDate' | 'lastServiceMileage' | 'nextDueDate' | 'nextDueMileage'>>;

    const updated = await this.repo.updateSchedule(id, userId, cleanPatch);
    if (!updated) return null;
    return this.toScheduleResponse(updated);
  }

  async deleteSchedule(userId: string, id: string): Promise<void> {
    await this.repo.deleteSchedule(id, userId);
  }

  async getUpcomingMaintenance(userId: string, vehicleId: string, currentMileage?: number): Promise<MaintenanceScheduleResponse[]> {
    const schedules = await this.repo.findActiveSchedulesByVehicleId(vehicleId, userId);
    const today = new Date().toISOString().split('T')[0];

    return schedules
      .map(s => this.toScheduleResponse(s, today, currentMileage))
      .filter(s => s.isDueSoon || s.isOverdue);
  }

  private async assertVehicleOwnership(userId: string, vehicleId: string) {
    const res = await pool.query('SELECT id FROM vehicles WHERE id = $1 AND user_id = $2', [vehicleId, userId]);
    if (!res.rows[0]) {
      const err: any = new Error('Vehicle not found or not owned by user');
      err.statusCode = 403;
      throw err;
    }
  }

  private async autoLinkRecordToSchedules(userId: string, record: MaintenanceRecord): Promise<void> {
    // Find active 'time_since_last' schedules with matching category and overlapping subtypes
    const matchingSchedules = await this.repo.findMatchingSchedules(
      userId,
      record.vehicleId,
      record.category,
      record.subtypes
    );

    for (const schedule of matchingSchedules) {
      // Calculate new next due dates based on intervals
      const nextDue = this.calculateNextDue({
        lastServiceDate: record.date,
        lastServiceMileage: record.odometerReading,
        intervalMonths: schedule.intervalMonths,
        intervalMiles: schedule.intervalMiles,
      });

      // Update the schedule with new last service info
      await this.repo.updateScheduleLastService(
        schedule.id,
        userId,
        record.date,
        record.odometerReading,
        nextDue.nextDueDate,
        nextDue.nextDueMileage
      );
    }
  }

  private calculateNextDue(schedule: {
    lastServiceDate?: string | null;
    lastServiceMileage?: number | null;
    intervalMonths?: number | null;
    intervalMiles?: number | null;
    scheduleType?: ScheduleType;
    fixedDueDate?: string | null;
  }): { nextDueDate: string | null; nextDueMileage: number | null } {
    // Handle fixed_date type - just return the fixed date
    if (schedule.scheduleType === 'fixed_date' && schedule.fixedDueDate) {
      return { nextDueDate: schedule.fixedDueDate, nextDueMileage: null };
    }

    // For interval and time_since_last types, calculate based on last service
    let nextDueDate: string | null = null;
    let nextDueMileage: number | null = null;

    if (schedule.lastServiceDate && schedule.intervalMonths) {
      const lastDate = new Date(schedule.lastServiceDate);
      const nextDate = new Date(lastDate);
      nextDate.setMonth(nextDate.getMonth() + schedule.intervalMonths);
      nextDueDate = nextDate.toISOString().split('T')[0];
    }

    if (schedule.lastServiceMileage !== null && schedule.lastServiceMileage !== undefined && schedule.intervalMiles) {
      nextDueMileage = schedule.lastServiceMileage + schedule.intervalMiles;
    }

    return { nextDueDate, nextDueMileage };
  }

  private toRecordResponse(record: MaintenanceRecord, receiptDocument?: ReceiptDocumentMeta | null): MaintenanceRecordResponse {
    return {
      ...record,
      subtypeCount: record.subtypes.length,
      receiptDocument: receiptDocument ?? null,
    };
  }

  private toScheduleResponse(schedule: MaintenanceSchedule, today?: string, currentMileage?: number): MaintenanceScheduleResponse {
    const todayStr = today || new Date().toISOString().split('T')[0];
    let isDueSoon = false;
    let isOverdue = false;

    if (schedule.nextDueDate) {
      const nextDue = new Date(schedule.nextDueDate);
      const todayDate = new Date(todayStr);
      const daysUntilDue = Math.floor((nextDue.getTime() - todayDate.getTime()) / (1000 * 60 * 60 * 24));

      if (daysUntilDue < 0) {
        isOverdue = true;
      } else if (daysUntilDue <= 30) {
        isDueSoon = true;
      }
    }

    if (currentMileage !== undefined && schedule.nextDueMileage !== null && schedule.nextDueMileage !== undefined) {
      const milesUntilDue = schedule.nextDueMileage - currentMileage;
      if (milesUntilDue < 0) {
        isOverdue = true;
      } else if (milesUntilDue <= 500) {
        isDueSoon = true;
      }
    }

    return {
      ...schedule,
      subtypeCount: schedule.subtypes.length,
      isDueSoon,
      isOverdue,
    };
  }
}
