
import { NUMBER_OF_TIMESHEETS_TO_RETURN } from "src/common/utils/constants.utils";
import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { Timesheets } from "src/time-and-attendance/timesheets/timesheet.dto";
import { Result } from "src/common/errors/result-error.factory";
import { toDateFromString } from "src/common/utils/date-utils";
import { mapOneTimesheet } from "src/time-and-attendance/timesheets/timesheet.mapper";

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

    //-----------------------------------------------------------------------------------
    //                     GET TIMESHEETS FOR A SELECTED EMPLOYEE
    //-----------------------------------------------------------------------------------
    async getTimesheetsForEmployeeByPeriod(email: string, pay_year: number, pay_period_no: number, employee_email?: string): Promise<Result<Timesheets, string>> {
        try {
            const account_email = employee_email ?? email;

            //find period using year and period_no
            const period = await this.prisma.payPeriods.findFirst({ where: { pay_year, pay_period_no } });
            if (!period) return { success: false, error: `PAY_PERIOD_NOT_FOUND` };

            //fetch the employee_id using the email
            const employee_id = await this.emailResolver.findIdByEmail(account_email);
            if (!employee_id.success) return { success: false, error: employee_id.error }

            //loads the timesheets related to the fetched pay-period
            let rows = await this.loadTimesheets(employee_id.data, period.period_start, period.period_end);

            //Normalized dates from pay-period strings
            const normalized_start = toDateFromString(period.period_start);
            const normalized_end = toDateFromString(period.period_end);

            //creates empty timesheet to make sure to return desired amount of timesheet 
            for (let i = 0; i < NUMBER_OF_TIMESHEETS_TO_RETURN; i++) {
                const week_start = new Date(normalized_start);
                week_start.setUTCDate(week_start.getUTCDate() + i * 7);

                if (week_start.getTime() > normalized_end.getTime()) break;

                const has_existing_timesheets = rows.some(
                    (row) => toDateFromString(row.start_date).getTime() === week_start.getTime()
                );
                if (!has_existing_timesheets) await this.ensureTimesheet(employee_id.data, week_start);
            }
            rows = await this.loadTimesheets(employee_id.data, period.period_start, period.period_end);

            //find user infos using the employee_id
            const employee = await this.prisma.employees.findUnique({
                where: { id: employee_id.data },
                include: { schedule_preset: true, user: true },
            });
            if (!employee) return { success: false, error: `EMPLOYEE_NOT_FOUND` }

            //builds employee details
            const has_preset_schedule = employee.schedule_preset !== null;
            const user = employee.user;
            const employee_fullname = `${user.first_name} ${user.last_name}`.trim();


            //maps all timesheet's infos
            const timesheets = await Promise.all(rows.map((timesheet) => mapOneTimesheet(timesheet)));
            if (!timesheets) return { success: false, error: 'INVALID_TIMESHEET' }

            return { success: true, data: { has_preset_schedule, employee_fullname, timesheets } };
        } catch (error) {
            return { success: false, error: 'TIMESHEET_NOT_FOUND' }
        }
    }

    //-----------------------------------------------------------------------------------
    //                                MAPPERS & HELPERS
    //-----------------------------------------------------------------------------------
    //fetch timesheet's infos
    private async loadTimesheets(employee_id: number, period_start: Date, period_end: Date) {
        return this.prisma.timesheets.findMany({
            where: { employee_id, start_date: { gte: period_start, lte: period_end } },
            include: {
                employee: { include: { user: true } },
                shift: { include: { bank_code: true }, orderBy: { start_time: 'asc' } },
                expense: { include: { bank_code: true, attachment_record: true }, orderBy: [{ date: 'asc' }, { bank_code_id: 'desc' }] },
            },
            orderBy: { start_date: 'asc' },
        });
    }

    private ensureTimesheet = async (employee_id: number, start_date: Date | string) => {
        const start = toDateFromString(start_date);

        let row = await this.prisma.timesheets.findFirst({
            where: { employee_id, start_date: start },
            include: {
                employee: { include: { user: true } },
                shift: { include: { bank_code: true } },
                expense: { include: { bank_code: true, attachment_record: true } },
            },
        });
        if (row) return row;

        await this.prisma.timesheets.create({
            data: {
                employee_id,
                start_date: start,
                is_approved: false
            },
        });

        row = await this.prisma.timesheets.findFirst({
            where: { employee_id, start_date: start },
            include: {
                employee: { include: { user: true } },
                shift: { include: { bank_code: true } },
                expense: { include: { bank_code: true, attachment_record: true, } },
            },
        });
        return row!;
    }
}
