import { Injectable } from "@nestjs/common";
import { Result } from "src/common/errors/result-error.factory";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";

@Injectable()
export class VacationService {
    constructor(
        private readonly prisma: PrismaPostgresService,
        private readonly emailResolver: EmailToIdResolver,
    ) { }

    //switch employeeId for email
    async calculateVacationPay(email: string, start_date: Date, days_requested: number, modifier: number): Promise<Result<number, string>> {
        const employee_id = await this.emailResolver.findIdByEmail(email);
        if (!employee_id.success) return { success: false, error: employee_id.error }

        //fetch hiring date
        const employee = await this.prisma.employees.findUnique({
            where: { id: employee_id.data },
            select: { first_work_day: true },
        });
        if (!employee) return { success: false, error: `Employee #${employee_id} not found` }

        const hire_date = employee.first_work_day;

        //sets "year" to may 1st to april 30th
        //check if hiring date is in may or later, we use hiring year, otherwise we use the year before
        const year_of_request = start_date.getMonth() >= 4
            ? start_date.getFullYear() : start_date.getFullYear() - 1;
        const period_start = new Date(year_of_request, 4, 1); //may = 4
        const period_end = new Date(year_of_request + 1, 4, 0); //day 0 of may == april 30th

        //steps to reach to get more vacation weeks in years 
        const checkpoint = [5, 10, 15];
        const anniversaries = checkpoint.map(years => {
            const anniversary_date = new Date(hire_date);
            anniversary_date.setFullYear(anniversary_date.getFullYear() + years);
            return anniversary_date;
        }).filter(d => d >= period_start && d <= period_end).sort((a, b) => a.getTime() - b.getTime());

        const boundaries = [period_start, ...anniversaries, period_end];
        //calculate prorata per segment
        let total_vacation_days = 0;
        const ms_per_day = 1000 * 60 * 60 * 24;

        for (let i = 0; i < boundaries.length - 1; i++) {
            const segment_start = boundaries[i];
            const segment_end = boundaries[i + 1];

            //number of days in said segment
            const days_in_segment = Math.round((segment_end.getTime() - segment_start.getTime()) / ms_per_day);
            const years_since_hire = (segment_start.getFullYear() - hire_date.getFullYear()) -
                (segment_start < new Date(segment_start.getFullYear(), hire_date.getMonth()) ? 1 : 0);
            let alloc_days: number;
            if (years_since_hire < 5) alloc_days = 10;
            else if (years_since_hire < 10) alloc_days = 15;
            else if (years_since_hire < 15) alloc_days = 20;
            else alloc_days = 25;

            //prorata for said segment
            const prorata = (alloc_days / 365) * days_in_segment;
            total_vacation_days += prorata;
        }
        //compares allowed vacation pools with requested days
        const payable_days = Math.min(total_vacation_days, days_requested);


        const raw_hours = payable_days * 8 * modifier;
        const rounded_hours = Math.round(raw_hours * 4) / 4;

        return { success: true, data: rounded_hours };
    }

    //updates hours in vacation bank.
    manageVacationHoursBank = async (employee_id: number, asked_hours: number): Promise<Result<number, string>> => {
        if (asked_hours <= 0) return { success: false, error: 'INVALID_VACATION_SHIFT' };

        try {
            const result = await this.prisma.$transaction(async (tx) => {
                //checks for remaining hours in vacation bank
                const employee = await tx.employees.findUnique({
                    where: { id: employee_id },
                    select: { paid_time_off: { select: { vacation_hours: true } } },
                });
                if (!employee) {
                    return { success: false, error: 'EMPLOYEE_NOT_FOUND' } as Result<number, string>;
                }
                if (!employee.paid_time_off) {
                    return { success: false, error: 'VACATION_HOURS_BANK_NOT_FOUND' } as Result<number, string>;
                }
                const vacation_bank = employee.paid_time_off.vacation_hours.toNumber()
                if (vacation_bank <= 0) return { success: false, error: 'EMPTY_VACATION_BANK' } as Result<number, string>;

                //check if remaining hours are less than asked and return maximum available hours
                if (asked_hours > vacation_bank) {
                    return { success: true, data: vacation_bank } as Result<number, string>;
                } else {
                    //update vacation_bank
                    await tx.paidTimeOff.update({
                        where: { employee_id: employee_id, vacation_hours: { gte: asked_hours } },
                        data: {
                            vacation_hours: { decrement: asked_hours },
                            last_updated: new Date(),
                        },
                    });
                    //returns asked hours if enough hours are left in the bank
                    return { success: true, data: asked_hours } as Result<number, string>;
                }
            });
            return result;
        } catch (error) {
            return { success: false, error: 'INVALID_VACATION_SHIFT' }
        }
    }



}