import { Injectable } from "@nestjs/common";
import { Prisma } from "prisma/postgres/generated/prisma/client/postgres/client";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { Result } from "src/common/errors/result-error.factory";


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

    async updateSickLeaveHours(employee_id: number): Promise<Result<boolean, string>> {
        const THIRTY_DAYS = 1000 * 60 * 60 * 24 * 30;
        const FOURTEEN_DAYS = 1000 * 60 * 60 * 24 * 14;
        const today = new Date();

        // get employee info
        const employee = await this.prisma.employees.findUnique({
            where: { id: employee_id },
            select: {
                first_work_day: true,
                last_work_day: true,
                id: true,
            }
        })

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

        // check if employee has been inactive for more than 2 weeks
        if (employee.last_work_day) {
            if (today.getTime() - employee.last_work_day.getTime() >= FOURTEEN_DAYS) {
                return { success: false, error: "EMPLOYEE_NOT_ACTIVE" };
            }
        }

        // get employee's PTO info, or create new details if not yet existing
        let pto_details: Prisma.Result<typeof this.prisma.paidTimeOff, Prisma.PaidTimeOffDefaultArgs, 'findUnique' | 'create'>;

        pto_details = await this.prisma.paidTimeOff.findUnique({
            where: { employee_id: employee.id },
        })

        if (!pto_details) {
            const new_pto_entry = await this.createNewPTORow(employee.id, today);

            if (!new_pto_entry.success) return { success: false, error: "FAILED_TO_CREATE_EMPLOYEE_PTO" };

            pto_details = new_pto_entry.data;
        }

        // check if employee has gotten his 3 days from completing his 30-day qualifying period from hiring date
        if (today.getTime() - employee.first_work_day.getTime() >= THIRTY_DAYS && employee.first_work_day.toISOString() === pto_details?.last_updated?.toISOString()) {
            const updated_pto = await this.addHoursToPTO(3 * 8, employee.id, today);

            if (!updated_pto.success) return { success: updated_pto.success, error: updated_pto.error }
        }

        const year_difference = today.getFullYear() - (pto_details!.last_updated?.getFullYear() ?? today.getFullYear());
        const months_since_last_update = (today.getMonth() + year_difference * 12) - (pto_details!.last_updated?.getMonth() ?? 0);

        if (months_since_last_update > 0) {
            const updated_pto = await this.addHoursToPTO(months_since_last_update * 8, employee.id, today);

            if (updated_pto.success) {
                return { success: true, data: true }
            }
        }

        return { success: false, error: 'UNKNOWN_PTO_UPDATE_ERROR' };
    }

    // create a new PTO row
    async createNewPTORow(employee_id: number, today: Date): Promise<Result<Prisma.Result<typeof this.prisma.paidTimeOff, Prisma.PaidTimeOffDefaultArgs, 'findUnique' | 'create'>, string>> {
        try {
            const new_pto_entry = await this.prisma.paidTimeOff.create({
                data: {
                    employee_id: employee_id,
                    last_updated: today,
                },
                select: {
                    id: true,
                    employee_id: true,
                    sick_hours: true,
                    vacation_hours: true,
                    banked_hours: true,
                    last_updated: true,
                }
            });

            return { success: true, data: new_pto_entry };
        } catch (error) {
            return { success: false, error };
        }
    }

    // add n number of sick PTO hours to employee PTO
    async addHoursToPTO(sick_hours: number, employee_id: number, last_updated: Date) {
        try {
            const update_pto = await this.prisma.paidTimeOff.update({
                where: {
                    employee_id,
                },
                data: {
                    sick_hours,
                    last_updated,
                }
            })

            return { success: true, data: update_pto };
        } catch (error) {
            return { success: false, error };
        }
    };

    takeSickLeaveHours = async (employee_id: number, asked_hours: number): Promise<Result<number, string>> => {
        if (asked_hours <= 0) return { success: false, error: 'INVALID_BANKING_HOURS' };
        
        try {
            const result = await this.prisma.$transaction(async (tx) => {
                const employee = await this.prisma.employees.findUnique({
                    where: { id: employee_id },
                    select: {
                        id: true,
                        paid_time_off: {
                            select: {
                                banked_hours: true
                            },
                        },
                    },
                });

                if (!employee) {
                    return { success: false, error: 'EMPLOYEE_NOT_FOUND' } as Result<number, string>;
                }
                if (!employee.paid_time_off) {
                    return { success: false, error: 'SICK_HOURS_BANK_NOT_FOUND' } as Result<number, string>;
                }
                const sick_bank = (employee.paid_time_off.banked_hours).toNumber();
                if (sick_bank <= 0) return { success: false, error: 'EMPTY_SICK_HOURS_BANK' } as Result<number, string>;

                if (asked_hours > sick_bank) {
                    return { success: true, data: sick_bank } as Result<number, string>;
                } else {
                    await tx.paidTimeOff.update({
                        where: { employee_id: employee.id },
                        data: {
                            banked_hours: { decrement: asked_hours },
                            last_updated: new Date(),
                        },
                        select: { banked_hours: true },
                    });
                    return { success: true, data: asked_hours } as Result<number, string>;
                }
            });
            return result;

        } catch (error) {
            return { success: false, error: 'INVALID_BANKING_SHIFT' };
        }

    }


    //LEAVE REQUEST FUNCTION - DEPRECATED
    // async calculateSickLeavePay(
    //     employee_id: number,
    //     reference_date: Date,
    //     days_requested: number,
    //     hours_per_day: number,
    //     modifier: number,
    // ): Promise<Result<number, string>> {
    //     if (days_requested <= 0 || hours_per_day <= 0 || modifier <= 0) {
    //         return { success: true, data: 0 };
    //     }

    //     //sets the year to jan 1st to dec 31st
    //     const period_start = getYearStart(reference_date);
    //     const period_end = reference_date;

    //     //fetches all shifts of a selected employee
    //     const shifts = await this.prisma.shifts.findMany({
    //         where: {
    //             timesheet: { employee_id: employee_id },
    //             date: { gte: period_start, lte: period_end },
    //         },
    //         select: { date: true },
    //     });

    //     //count the amount of worked days
    //     const worked_dates = new Set(
    //         shifts.map((shift) => shift.date.toISOString().slice(0, 10)),
    //     );
    //     const days_worked = worked_dates.size;

    //     //less than 30 worked days returns 0
    //     if (days_worked < 30) {
    //         return { success: true, data: 0 };
    //     }

    //     //default 3 days allowed after 30 worked days
    //     let acquired_days = 3;

    //     //identify the date of the 30th worked day
    //     const ordered_dates = Array.from(worked_dates).sort();
    //     const threshold_date = new Date(ordered_dates[29]); // index 29 is the 30th day

    //     //calculate each completed month, starting the 1st of the next month
    //     const first_bonus_date = new Date(
    //         threshold_date.getFullYear(),
    //         threshold_date.getMonth() + 1,
    //         1,
    //     );
    //     let months =
    //         (period_end.getFullYear() - first_bonus_date.getFullYear()) * 12 +
    //         (period_end.getMonth() - first_bonus_date.getMonth()) +
    //         1;
    //     if (months < 0) months = 0;
    //     acquired_days += months;

    //     //cap of 10 days
    //     if (acquired_days > 10) acquired_days = 10;

    //     const payable_days = Math.min(acquired_days, days_requested);
    //     const raw_hours = payable_days * hours_per_day * modifier;
    //     const rounded = roundToQuarterHour(raw_hours);
    //     return { success: true, data: rounded };
    // }
}
