import { storage } from "./storage";
import { db } from "./db";
import { shifts, shiftAssignments, guards } from "@shared/schema";
import { eq, and, gte, lte, between, sql } from "drizzle-orm";
import { differenceInMinutes, startOfWeek, endOfWeek, startOfMonth, endOfMonth, differenceInDays } from "date-fns";

export interface CcnlRule {
  key: string;
  value: string | number | boolean;
  description?: string;
}

export interface CcnlValidationResult {
  isValid: boolean;
  violations: CcnlViolation[];
  warnings: CcnlWarning[];
}

export interface CcnlViolation {
  type: "max_hours_exceeded" | "insufficient_rest" | "consecutive_limit_exceeded" | "overtime_violation";
  severity: "error" | "warning";
  message: string;
  details?: any;
}

export interface CcnlWarning {
  type: string;
  message: string;
  details?: any;
}

/**
 * Carica tutte le impostazioni CCNL dal database e le converte in un oggetto chiave-valore
 */
export async function loadCcnlSettings(): Promise<Record<string, any>> {
  const settings = await storage.getAllCcnlSettings();
  const result: Record<string, any> = {};
  
  for (const setting of settings) {
    // Converti i valori stringa nei tipi appropriati
    if (setting.value === "true" || setting.value === "false") {
      result[setting.key] = setting.value === "true";
    } else if (!isNaN(Number(setting.value))) {
      result[setting.key] = Number(setting.value);
    } else {
      result[setting.key] = setting.value;
    }
  }
  
  return result;
}

/**
 * Ottiene un singolo valore CCNL con tipo convertito
 */
export async function getCcnlValue<T = any>(key: string): Promise<T | undefined> {
  const setting = await storage.getCcnlSetting(key);
  if (!setting) return undefined;
  
  if (setting.value === "true" || setting.value === "false") {
    return (setting.value === "true") as T;
  } else if (!isNaN(Number(setting.value))) {
    return Number(setting.value) as T;
  } else {
    return setting.value as T;
  }
}

/**
 * Valida se un turno rispetta le regole CCNL per un dato agente
 */
export async function validateShiftForGuard(
  guardId: string,
  shiftStartTime: Date,
  shiftEndTime: Date,
  excludeShiftId?: string
): Promise<CcnlValidationResult> {
  const violations: CcnlViolation[] = [];
  const warnings: CcnlWarning[] = [];
  
  const ccnlSettings = await loadCcnlSettings();
  
  // Calcola le ore del turno proposto (con precisione decimale)
  const shiftHours = differenceInMinutes(shiftEndTime, shiftStartTime) / 60;
  
  // 1. Verifica ore minime di riposo tra turni
  const minRestHours = ccnlSettings.min_rest_hours_between_shifts || 11;
  const lastShift = await getLastShiftBeforeDate(guardId, shiftStartTime, excludeShiftId);
  
  if (lastShift) {
    const restHours = differenceInMinutes(shiftStartTime, new Date(lastShift.endTime)) / 60;
    if (restHours < minRestHours) {
      violations.push({
        type: "insufficient_rest",
        severity: "error",
        message: `Riposo insufficiente: ${restHours.toFixed(1)} ore (minimo: ${minRestHours} ore)`,
        details: { restHours, minRestHours, lastShiftEnd: lastShift.endTime }
      });
    }
  }
  
  // 2. Verifica ore settimanali massime
  const maxHoursPerWeek = ccnlSettings.max_hours_per_week || 48;
  const weekStart = startOfWeek(shiftStartTime, { weekStartsOn: 1 });
  const weekEnd = endOfWeek(shiftStartTime, { weekStartsOn: 1 });
  const weeklyHours = await getGuardHoursInPeriod(guardId, weekStart, weekEnd, excludeShiftId);
  
  if (weeklyHours + shiftHours > maxHoursPerWeek) {
    violations.push({
      type: "max_hours_exceeded",
      severity: "error",
      message: `Superamento ore settimanali: ${weeklyHours + shiftHours} ore (massimo: ${maxHoursPerWeek} ore)`,
      details: { currentWeekHours: weeklyHours, shiftHours, maxHoursPerWeek }
    });
  } else if (weeklyHours + shiftHours > maxHoursPerWeek * 0.9) {
    warnings.push({
      type: "approaching_weekly_limit",
      message: `Vicino al limite settimanale: ${weeklyHours + shiftHours} ore su ${maxHoursPerWeek} ore`,
      details: { currentWeekHours: weeklyHours, shiftHours, maxHoursPerWeek }
    });
  }
  
  // 3. Verifica ore mensili massime
  const maxHoursPerMonth = ccnlSettings.max_hours_per_month || 190;
  const monthStart = startOfMonth(shiftStartTime);
  const monthEnd = endOfMonth(shiftStartTime);
  const monthlyHours = await getGuardHoursInPeriod(guardId, monthStart, monthEnd, excludeShiftId);
  
  if (monthlyHours + shiftHours > maxHoursPerMonth) {
    violations.push({
      type: "max_hours_exceeded",
      severity: "error",
      message: `Superamento ore mensili: ${monthlyHours + shiftHours} ore (massimo: ${maxHoursPerMonth} ore)`,
      details: { currentMonthHours: monthlyHours, shiftHours, maxHoursPerMonth }
    });
  } else if (monthlyHours + shiftHours > maxHoursPerMonth * 0.9) {
    warnings.push({
      type: "approaching_monthly_limit",
      message: `Vicino al limite mensile: ${monthlyHours + shiftHours} ore su ${maxHoursPerMonth} ore`,
      details: { currentMonthHours: monthlyHours, shiftHours, maxHoursPerMonth }
    });
  }
  
  // 4. Verifica giorni consecutivi
  const maxDaysConsecutive = ccnlSettings.max_days_consecutive || 6;
  const consecutiveDays = await getConsecutiveDaysWorked(guardId, shiftStartTime, excludeShiftId);
  
  if (consecutiveDays >= maxDaysConsecutive) {
    violations.push({
      type: "consecutive_limit_exceeded",
      severity: "error",
      message: `Superamento giorni consecutivi: ${consecutiveDays + 1} giorni (massimo: ${maxDaysConsecutive} giorni)`,
      details: { consecutiveDays: consecutiveDays + 1, maxDaysConsecutive }
    });
  }
  
  // 5. Verifica straordinario settimanale
  const overtimeThreshold = ccnlSettings.overtime_threshold_week || 40;
  if (weeklyHours + shiftHours > overtimeThreshold && ccnlSettings.penalty_if_overtime === true) {
    const overtimeHours = (weeklyHours + shiftHours) - overtimeThreshold;
    warnings.push({
      type: "overtime_warning",
      message: `Straordinario settimanale: ${overtimeHours.toFixed(1)} ore oltre ${overtimeThreshold} ore`,
      details: { overtimeHours, overtimeThreshold }
    });
  }
  
  return {
    isValid: violations.filter(v => v.severity === "error").length === 0,
    violations,
    warnings
  };
}

/**
 * Ottiene l'ultimo turno dell'agente prima di una certa data
 */
async function getLastShiftBeforeDate(guardId: string, beforeDate: Date, excludeShiftId?: string): Promise<any> {
  const query = db
    .select()
    .from(shifts)
    .innerJoin(shiftAssignments, eq(shifts.id, shiftAssignments.shiftId))
    .where(
      and(
        eq(shiftAssignments.guardId, guardId),
        lte(shifts.endTime, beforeDate),
        excludeShiftId ? sql`${shifts.id} != ${excludeShiftId}` : sql`true`
      )
    )
    .orderBy(sql`${shifts.endTime} DESC`)
    .limit(1);
  
  const results = await query;
  return results[0]?.shifts;
}

/**
 * Calcola le ore totali lavorate da un agente in un periodo
 */
async function getGuardHoursInPeriod(
  guardId: string,
  startDate: Date,
  endDate: Date,
  excludeShiftId?: string
): Promise<number> {
  const query = db
    .select()
    .from(shifts)
    .innerJoin(shiftAssignments, eq(shifts.id, shiftAssignments.shiftId))
    .where(
      and(
        eq(shiftAssignments.guardId, guardId),
        gte(shifts.startTime, startDate),
        lte(shifts.endTime, endDate),
        excludeShiftId ? sql`${shifts.id} != ${excludeShiftId}` : sql`true`
      )
    );
  
  const results = await query;
  
  let totalHours = 0;
  for (const result of results) {
    const shiftMinutes = differenceInMinutes(
      new Date(result.shifts.endTime),
      new Date(result.shifts.startTime)
    );
    totalHours += shiftMinutes / 60;
  }
  
  return totalHours;
}

/**
 * Conta i giorni consecutivi lavorati dall'agente fino a una certa data
 */
async function getConsecutiveDaysWorked(
  guardId: string,
  upToDate: Date,
  excludeShiftId?: string
): Promise<number> {
  // Ottieni tutti i turni dell'agente negli ultimi 14 giorni
  const twoWeeksAgo = new Date(upToDate);
  twoWeeksAgo.setDate(twoWeeksAgo.getDate() - 14);
  
  const query = db
    .select()
    .from(shifts)
    .innerJoin(shiftAssignments, eq(shifts.id, shiftAssignments.shiftId))
    .where(
      and(
        eq(shiftAssignments.guardId, guardId),
        gte(shifts.startTime, twoWeeksAgo),
        lte(shifts.startTime, upToDate),
        excludeShiftId ? sql`${shifts.id} != ${excludeShiftId}` : sql`true`
      )
    )
    .orderBy(sql`${shifts.startTime} DESC`);
  
  const results = await query;
  
  if (results.length === 0) return 0;
  
  // Conta i giorni consecutivi partendo dalla data più recente
  let consecutiveDays = 0;
  let currentDate = new Date(upToDate);
  currentDate.setHours(0, 0, 0, 0);
  
  const workedDates = new Set(
    results.map((r: any) => {
      const d = new Date(r.shifts.startTime);
      d.setHours(0, 0, 0, 0);
      return d.getTime();
    })
  );
  
  while (workedDates.has(currentDate.getTime())) {
    consecutiveDays++;
    currentDate.setDate(currentDate.getDate() - 1);
  }
  
  return consecutiveDays;
}

/**
 * Valida un batch di turni per più agenti
 */
export async function validateMultipleShifts(
  assignments: Array<{ guardId: string; shiftStartTime: Date; shiftEndTime: Date }>
): Promise<Record<string, CcnlValidationResult>> {
  const results: Record<string, CcnlValidationResult> = {};
  
  for (const assignment of assignments) {
    results[assignment.guardId] = await validateShiftForGuard(
      assignment.guardId,
      assignment.shiftStartTime,
      assignment.shiftEndTime
    );
  }
  
  return results;
}

/**
 * Ottiene un report sulla disponibilità dell'agente in un periodo
 */
export async function getGuardAvailabilityReport(
  guardId: string,
  startDate: Date,
  endDate: Date
): Promise<{
  totalHours: number;
  weeklyHours: Record<string, number>;
  remainingWeeklyHours: number;
  remainingMonthlyHours: number;
  consecutiveDaysWorked: number;
  lastShiftEnd?: Date;
}> {
  const ccnlSettings = await loadCcnlSettings();
  const maxHoursPerWeek = ccnlSettings.max_hours_per_week || 48;
  const maxHoursPerMonth = ccnlSettings.max_hours_per_month || 190;
  
  const totalHours = await getGuardHoursInPeriod(guardId, startDate, endDate);
  
  const weekStart = startOfWeek(new Date(), { weekStartsOn: 1 });
  const weekEnd = endOfWeek(new Date(), { weekStartsOn: 1 });
  const currentWeekHours = await getGuardHoursInPeriod(guardId, weekStart, weekEnd);
  
  const monthStart = startOfMonth(new Date());
  const monthEnd = endOfMonth(new Date());
  const currentMonthHours = await getGuardHoursInPeriod(guardId, monthStart, monthEnd);
  
  const consecutiveDaysWorked = await getConsecutiveDaysWorked(guardId, new Date());
  
  const lastShift = await getLastShiftBeforeDate(guardId, new Date());
  
  return {
    totalHours,
    weeklyHours: {
      current: currentWeekHours,
    },
    remainingWeeklyHours: Math.max(0, maxHoursPerWeek - currentWeekHours),
    remainingMonthlyHours: Math.max(0, maxHoursPerMonth - currentMonthHours),
    consecutiveDaysWorked,
    lastShiftEnd: lastShift ? new Date(lastShift.endTime) : undefined,
  };
}
