import { Injectable } from "@nestjs/common";
import { computeHours, getWeekStart } from "src/common/utils/date-utils";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { MS_PER_WEEK } from "src/common/utils/constants.utils";
import { Result } from "src/common/errors/result-error.factory";
/* 
    le calcul est 1/20 des 4 dernières semaines, précédent la semaine incluant le férier. 
    Un maximum de 08h00 est allouable pour le férier
    Un maximum de 40hrs par semaine est retenue pour faire le calcul.
    le bank-code à soumettre à Desjardins doit être le G104
*/

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

    /**
     * Calculates the amount of hours to be paid for a given employee on the provided holiday date.
     * 
     * @param employee_id id of the employee whom the holiday shift belongs to
     * @param holiday_date date that the holiday was taken on
     * 
     * @returns Average daily hours worked in the past four weeks as a `number`, to a maximum of `8`
     */
    private async computeHoursPrevious4Weeks(
        external_payroll_id: number,
        company_code: number,
        holiday_date: Date
    ): Promise<Result<number, string>> {
        try {
            const valid_codes = ['G1', 'G43', 'G140', 'G104', 'G105', 'G305', 'G700', 'G720'];
            const holiday_week_start = getWeekStart(holiday_date);
            const window_start = new Date(holiday_week_start.getTime() - 4 * MS_PER_WEEK);
            const window_end = new Date(holiday_week_start.getTime() - 1);

            const employee = await this.prisma.employees.findFirst({
                where: {
                    external_payroll_id,
                    company_code,
                },
                select: {
                    id: true,
                }
            });

            if (!employee)
                return { success: false, error: 'EMPLOYEE_NOT_FOUND' };

            const shifts = await this.prisma.shifts.findMany({
                where: {
                    timesheet: { employee_id: employee.id },
                    date: { gte: window_start, lte: window_end },
                    bank_code: { bank_code: { in: valid_codes } },
                },
                select: { date: true, start_time: true, end_time: true },
            });

            const hours_by_week = new Map<number, number>();
            for (const shift of shifts) {
                const hours = computeHours(shift.start_time, shift.end_time);

                if (hours <= 0)
                    continue;

                const shift_week_start = getWeekStart(shift.date);
                const key = shift_week_start.getTime();
                hours_by_week.set(key, (hours_by_week.get(key) ?? 0) + hours);
            }

            let capped_total = 0;
            for (let offset = 1; offset <= 4; offset++) {
                const week_start = new Date(holiday_week_start.getTime() - offset * MS_PER_WEEK);
                const key = week_start.getTime();
                const weekly_hours = hours_by_week.get(key) ?? 0;
                capped_total += Math.min(weekly_hours, 40);
            }

            const average_daily_hours = capped_total / 20;
            return { success: true, data: average_daily_hours };
        } catch (error) {
            console.error(error);
            return { success: false, error: `an error occureded during holiday calculation` }
        }
    }

    async calculateHolidayPay(
        employeePayrollID: number,
        companyCode: number,
        holiday_date: Date
    ): Promise<Result<number, string>> {
        const average_daily_hours = await this.computeHoursPrevious4Weeks(employeePayrollID, companyCode, holiday_date);

        if (!average_daily_hours.success) return { success: false, error: average_daily_hours.error };

        const daily_rate = (Math.min(average_daily_hours.data, 8));
        return { success: true, data: daily_rate };
    }
}