import { WEEKLY_LIMIT_HOURS } from "src/common/utils/constants.utils";
import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service";
import { CsvRow, InternalCsvRow } from "src/time-and-attendance/exports/export-csv-options.dto";

const REGULAR = 1;
const EVENING = 140;
const EMERGENCY = 48;
const HOLIDAY = 104;
const OVERTIME = 43;
const VACATION = 109;
const SICK = 105;

export const consolidateRowHoursAndAmountByType = (rows: InternalCsvRow[]): InternalCsvRow[] => {
    const map = new Map<string, InternalCsvRow>();

    for (const row of rows) {
        if (row.code === VACATION || row.code === SICK) {
            map.set(`${row.code}|${row.shift_date.toString()}|${row.timesheet_id}`, row);
        } else {
            const key = `${row.code}|${row.timesheet_id}`;
            if (!map.has(key)) {
                map.set(key, row);
            } else {
                const existing = map.get(key)!;
                existing.quantite_hre = (existing.quantite_hre ?? 0) + (row.quantite_hre ?? 0);
                existing.montant = (existing.montant ?? 0) + (row.montant ?? 0);
            }
        }
    }
    return Array.from(map.values());
}

export const applyHolidayRequalifications = async (
    rows: InternalCsvRow[],
    holiday_service: HolidayService,
    holiday_bank_code: string,
): Promise<InternalCsvRow[]> => {
    const result: InternalCsvRow[] = [];
    const HOLIDAY_BANK_CODE = Number(holiday_bank_code.slice(1,));

    for (const row of rows) {
        if (row.code !== HOLIDAY_BANK_CODE) {
            result.push(row);
            continue;
        }
        if (!row.premier_jour_absence || !row.dernier_jour_absence || !row.employee_matricule || !row.compagnie_no) {
            result.push(row);
            continue;
        }

        const calculated = await holiday_service.calculateHolidayPay(row.employee_matricule, row.compagnie_no, new Date(row.premier_jour_absence));

        if (!calculated.success) {
            result.push({ ...row, quantite_hre: 0 });
            continue;
        }

        result.push({ ...row, quantite_hre: calculated.data });
    }
    return result;
}

export const applyOvertimeRequalifications = (
    consolidated_rows: InternalCsvRow[],
): CsvRow[] => {
    const result: InternalCsvRow[] = [];

    //regroup rows by timesheet and week number
    const grouped_rows = new Map<string, InternalCsvRow[]>();
    for (const row of consolidated_rows) {
        const key = `${row.timesheet_id}|${row.semaine_no}`;
        if (!grouped_rows.has(key)) {
            grouped_rows.set(key, []);
        }
        grouped_rows.get(key)!.push({ ...row });
    }

    for (const [, rows] of grouped_rows) {
        const evening_hours = rows.find(r => r.code === EVENING);
        const emergency_hours = rows.find(r => r.code === EMERGENCY);
        const holiday_hours = rows.find(r => r.code === HOLIDAY);
        const regular_hours = rows.find(r => r.code === REGULAR);
        const vacation_hours = rows.find(r => r.code === VACATION);

        // if no regular hours row, push as is
        if (!regular_hours?.quantite_hre) { result.push(...rows); continue; }

        const total_hours = (
            regular_hours.quantite_hre
            + (evening_hours?.quantite_hre ?? 0)
            + (emergency_hours?.quantite_hre ?? 0)
            + (holiday_hours?.quantite_hre ?? 0)
            + (vacation_hours?.quantite_hre ?? 0)
        )
        // calculate overtime directly from consolidated hours
        const overtime_hours = Math.max(0, total_hours - WEEKLY_LIMIT_HOURS);

        // if no overtime, push as is
        if (overtime_hours <= 0) { result.push(...rows); continue; }

        // ensures that its not possible to deduct more hours than the amount of regular or evening hours
        const deducted_regular = Math.min(overtime_hours, regular_hours.quantite_hre);
        const remaining_overtime = overtime_hours - deducted_regular;
        const deducted_evening = Math.min(remaining_overtime, evening_hours?.quantite_hre ?? 0);

        const remaining_regular = (regular_hours.quantite_hre ?? 0) - deducted_regular;
        const remaining_evening = (evening_hours?.quantite_hre ?? 0) - deducted_evening;

        for (const row of rows) {
            if (row === regular_hours) {
                // pushes the regular row with subtracted overtime hours, if enough hours remaining
                if (remaining_regular > 0) {
                    result.push({ ...regular_hours, quantite_hre: remaining_regular });
                }
            } else if (row === evening_hours) {
                // pushes the evening row with subtracted overtime hours, if enough not enough regular hours remaining
                if (remaining_evening > 0) {
                    result.push({ ...evening_hours, quantite_hre: remaining_evening });
                }
            } else {
                // other rows are left unchanged
                result.push(row);
            }
        }
        //adds a new row with overtime hours deducted from the regular hours
        if (deducted_regular + deducted_evening > 0) {
            result.push({
                ...regular_hours,
                code: OVERTIME,
                quantite_hre: deducted_regular + deducted_evening,
            });
        }
    }
    return result;
}

export const resolveCompanyCodes = (companies: { targo: boolean; solucom: boolean; }): number[] => {
    const out: number[] = [];
    if (companies.targo) {
        const code_no = 271583;
        out.push(code_no);
    }
    if (companies.solucom) {
        const code_no = 271585;
        out.push(code_no);
    }
    return out;
}

export const computeWeekNumber = (start: Date, date: Date): number => {
    const dayMS = 86400000;
    const days = Math.floor((toUTC(date).getTime() - toUTC(start).getTime()) / dayMS);
    return Math.floor(days / 7) + 1;
}

export const toUTC = (date: Date) => {
    return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
}

export const formatDate = (d: Date): string => {
    return d.toISOString().split('T')[0];
}