import { Injectable } from "@nestjs/common";
import { Result } from "src/common/errors/result-error.factory";
import { computeHours, computePeriod, toDateFromString, toStringFromDate } from "src/common/utils/date-utils";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { EmployeePeriodOverviewDto, Overview, PayPeriodOverviewDto } from "src/time-and-attendance/pay-period/dtos/overview-pay-period.dto";


@Injectable()
export class GetOverviewService {
    constructor(
        private readonly prisma: PrismaPostgresService,
    ) { }

    async getOverviewByYearPeriod(pay_year: number, period_no: number): Promise<Result<PayPeriodOverviewDto, string>> {
        const period = computePeriod(pay_year, period_no);
        const overview = await this.buildOverview({
            period_start: period.period_start,
            period_end: period.period_end,
            period_no: period.period_no,
            pay_year: period.pay_year,
            payday: period.payday,
            label: period.label,

        });
        if (!overview.success) return { success: false, error: 'INVALID_PAY_PERIOD' }
        return { success: true, data: overview.data }
    }

    async buildOverview(overview: Overview): Promise<Result<PayPeriodOverviewDto, string>> {
        const employee_overviews = await this.prisma.employees.findMany({
            where: {
                OR: [
                    { last_work_day: { gte: toDateFromString(overview.period_start) } },
                    { last_work_day: null },
                ]
            },
            select: {
                id: true,
                last_work_day: true,
                user: {
                    select: {
                        first_name: true,
                        last_name: true,
                        email: true,
                    },
                },
                supervisor: {
                    select: {
                        user: {
                            select: {
                                first_name: true,
                                last_name: true,
                                email: true,
                            },
                        },
                    },
                },
                timesheet: {
                    where: {
                        start_date: { gte: toDateFromString(overview.period_start), lte: toDateFromString(overview.period_end) },
                    },
                    select: {
                        id: true,
                        is_approved: true,
                        start_date: true,
                        shift: {
                            select: {
                                start_time: true,
                                end_time: true,
                                date: true,
                                bank_code: { select: { type: true } },
                            },
                            orderBy: { date: 'asc' },
                        },
                        expense: {
                            select: {
                                amount: true,
                                bank_code: { select: { type: true, modifier: true } },
                            },
                        },
                    },
                },
            },
            orderBy: { user: { first_name: 'asc' } },
        });

        const by_employee = new Map<number, EmployeePeriodOverviewDto>();

        // seed for employee without data
        if (overview.options?.seed_names) {
            for (const [id, { first_name, last_name, email }] of overview.options.seed_names.entries()) {
                by_employee.set(id, this.createEmployeeSeeds(email, first_name, last_name));
            }
        } else {
            for (const employee of employee_overviews) {
                const record = this.createEmployeeSeeds(
                    employee.user.email,
                    employee.user.first_name,
                    employee.user.last_name,
                    employee.supervisor?.user ?? null,
                );
                by_employee.set(employee.id, record);
            }
        }

        const ensure = (id: number, first_name: string, last_name: string, email: string) => {
            if (!by_employee.has(id)) {
                by_employee.set(id, this.createEmployeeSeeds(email, first_name, last_name));
            }
            return by_employee.get(id)!;
        };

        for (const employee of employee_overviews) {
            const record = ensure(
                employee.id,
                employee.user.first_name,
                employee.user.last_name,
                employee.user.email
            );

            for (const timesheet of employee.timesheet) {
                let total_weekly_hours: number = 0;
                let daily_hours: number = 0;
                let previous_shift_date: string = '';

                //totals by types for shifts
                for (const shift of timesheet.shift) {
                    const hours = computeHours(shift.start_time, shift.end_time);
                    const type = (shift.bank_code?.type ?? '').toUpperCase();

                    if (previous_shift_date !== toStringFromDate(shift.date)) {
                        previous_shift_date = toStringFromDate(shift.date);
                        daily_hours = 0;
                    }

                    switch (type) {
                        case "EVENING":
                            if (total_weekly_hours + hours <= 40) {
                                record.other_hours.evening_hours += Math.min(hours, 8 - daily_hours);
                                record.other_hours.overtime_hours += Math.max(daily_hours + hours - 8, 0);
                            } else {
                                record.other_hours.evening_hours += Math.max(40 - total_weekly_hours, 0);
                                record.other_hours.overtime_hours += Math.min(total_weekly_hours + hours - 40, hours);
                            }
                            total_weekly_hours += hours;
                            record.total_hours += hours;
                            break;
                        case "EMERGENCY":
                            record.other_hours.emergency_hours += hours;
                            record.total_hours += hours;
                            total_weekly_hours += hours;
                            break;
                        case "SICK":
                            record.other_hours.sick_hours += hours;
                            break;
                        case "HOLIDAY":
                            record.other_hours.holiday_hours += hours;
                            record.total_hours += hours;
                            total_weekly_hours += hours;
                            break;
                        case "VACATION": record.other_hours.vacation_hours += hours;
                            break;
                        case "REGULAR":
                            if (total_weekly_hours + hours <= 40) {
                                record.regular_hours += Math.min(hours, 8 - daily_hours);
                                record.other_hours.overtime_hours += Math.max(daily_hours + hours - 8, 0);
                            } else {
                                record.regular_hours += Math.max(40 - total_weekly_hours, 0);
                                record.other_hours.overtime_hours += Math.min(total_weekly_hours + hours - 40, hours);
                            }
                            total_weekly_hours += hours;
                            record.total_hours += hours;
                            break;
                    }

                    daily_hours += hours;
                }
                //totals by type for expenses
                for (const expense of timesheet.expense) {
                    const amount = Number(expense.amount)
                    record.expenses = Number((record.expenses + amount).toFixed(2));
                    const type = (expense.bank_code?.type || "").toUpperCase();
                    const rate = expense.bank_code?.modifier ?? 1;
                    const mileage = amount / rate;
                    if (type === "MILEAGE" && rate > 0) {
                        record.mileage = Number((record.mileage += Math.round(mileage)).toFixed(2));
                    }
                }

                record.weekly_hours.push(total_weekly_hours);
            }
        }

        for (const employee of employee_overviews) {
            const record = by_employee.get(employee.id);
            if (!record) continue;
            const timesheets = employee.timesheet;
            const has_data = timesheets.some(timesheet => timesheet.shift.length > 0 || timesheet.expense.length > 0);

            const cutoff_date = new Date();
            cutoff_date.setDate(cutoff_date.getDate() + 14);
            const is_active = employee.last_work_day ? employee.last_work_day.getTime() >= cutoff_date.getTime() : true;

            record.is_approved = has_data && timesheets.every(timesheet => timesheet.is_approved === true);
            record.is_active = is_active;
        }

        const employees_overview = Array.from(by_employee.values()).sort((a, b) =>
            a.employee_first_name.localeCompare(b.employee_first_name, "fr", { sensitivity: "base" }),
        );

        return {
            success: true,
            data: {
                pay_period_no: overview.period_no,
                pay_year: overview.pay_year,
                payday: overview.payday,
                period_start: (overview.period_start),
                period_end: overview.period_end,
                label: overview.label,
                employees_overview,
            }
        };
    }

    createEmployeeSeeds = (
        email: string,
        employee_first_name: string,
        employee_last_name: string,
        supervisor: {
            first_name: string;
            last_name: string;
            email: string;
        } | null = null,
    ): EmployeePeriodOverviewDto => ({
        email,
        employee_first_name,
        employee_last_name,
        supervisor: supervisor ?? null,
        is_active: true,
        regular_hours: 0,
        other_hours: {
            evening_hours: 0,
            emergency_hours: 0,
            overtime_hours: 0,
            sick_hours: 0,
            holiday_hours: 0,
            vacation_hours: 0,
        },
        weekly_hours: [],
        total_hours: 0,
        expenses: 0,
        mileage: 0,
        is_approved: false,
    });
}