import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
import { Filters, CsvRow, InternalCsvRow } from "src/time-and-attendance/exports/export-csv-options.dto";
import { computeHours } from "src/common/utils/date-utils";
import { applyHolidayRequalifications, applyOvertimeRequalifications, computeWeekNumber, consolidateRowHoursAndAmountByType, formatDate, resolveCompanyCodes } from "src/time-and-attendance/exports/csv-exports.utils";
import { OvertimeService } from "src/time-and-attendance/domains/services/overtime.service";
import { HolidayService } from "src/time-and-attendance/domains/services/holiday.service";

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

    async collectTransaction(
        year: number,
        period_no: number,
        filters: Filters,
        // approved: boolean = true
    ): Promise<CsvRow[]> {
        //fetch period
        const period = await this.prisma.payPeriods.findFirst({
            where: { pay_year: year, pay_period_no: period_no },
            select: { period_start: true, period_end: true },
        });
        if (!period) throw new NotFoundException(`Pay period ${year}-${period_no} not found`);

        const start = period.period_start;
        const end = period.period_end;

        //fetch company codes
        const company_codes = resolveCompanyCodes(filters.companies);
        if (company_codes.length === 0) throw new BadRequestException('No company selected');

        //Flag types
        const { shifts: want_shifts, expenses: want_expense, holiday: want_holiday, vacation: want_vacation } = filters.types;
        if (!want_shifts && !want_expense && !want_holiday && !want_vacation) {
            throw new BadRequestException(' No export type selected ');
        }

        const approved_filter = filters.approved ? { is_approved: true } : {};

        const holiday_code = await this.resolveHolidayTypeCode('HOLIDAY');
        const vacation_code = await this.resolveVacationTypeCode('VACATION');
        const leave_code_filter = [holiday_code, vacation_code];

        //Prisma queries
        const promises: Array<Promise<any[]>> = [];

        if (want_shifts) {
            promises.push(this.prisma.shifts.findMany({
                where: {
                    date: { gte: start, lte: end },
                    ...approved_filter,
                    bank_code: { bank_code: { notIn: leave_code_filter } },
                    timesheet: { employee: { company_code: { in: company_codes } } },
                },
                select: {
                    date: true,
                    start_time: true,
                    end_time: true,
                    bank_code: { select: { bank_code: true } },
                    timesheet: {
                        select: {
                            id: true,
                            employee: {
                                select: {
                                    company_code: true,
                                    external_payroll_id: true,
                                    user: { select: { first_name: true, last_name: true, email: true } },
                                }
                            },
                        }
                    },
                },
            }));
        } else {
            promises.push(Promise.resolve([]));
        }

        if (want_holiday) {
            promises.push(this.prisma.shifts.findMany({
                where: {
                    date: { gte: start, lte: end },
                    ...approved_filter,
                    bank_code: { bank_code: holiday_code },
                    timesheet: { employee: { company_code: { in: company_codes } } },
                },
                select: {
                    date: true,
                    start_time: true,
                    end_time: true,
                    bank_code: { select: { bank_code: true } },
                    timesheet: {
                        select: {
                            id: true,
                            employee: {
                                select: {
                                    company_code: true,
                                    external_payroll_id: true,
                                    user: { select: { first_name: true, last_name: true, email: true } },
                                }
                            },
                        }
                    },
                },
            }));
        } else {
            promises.push(Promise.resolve([]));
        }

        if (want_vacation) {
            promises.push(this.prisma.shifts.findMany({
                where: {
                    date: { gte: start, lte: end },
                    ...approved_filter,
                    bank_code: { bank_code: vacation_code },
                    timesheet: { employee: { company_code: { in: company_codes } } },
                },
                select: {
                    date: true,
                    start_time: true,
                    end_time: true,
                    bank_code: { select: { bank_code: true } },
                    timesheet: {
                        select: {
                            id: true,
                            employee: {
                                select: {
                                    company_code: true,
                                    external_payroll_id: true,
                                    user: { select: { first_name: true, last_name: true, email: true } },
                                }
                            },
                        }
                    },
                },
            }));
        } else {
            promises.push(Promise.resolve([]));
        }

        if (want_expense) {
            promises.push(this.prisma.expenses.findMany({
                where: {
                    date: { gte: start, lte: end },
                    ...approved_filter,
                    timesheet: { employee: { company_code: { in: company_codes } } },
                },
                select: {
                    date: true,
                    amount: true,
                    bank_code: { select: { bank_code: true } },
                    timesheet: {
                        select: {
                            id: true,
                            employee: {
                                select: {
                                    company_code: true,
                                    external_payroll_id: true,
                                    user: { select: { first_name: true, last_name: true, email: true } },
                                }
                            },
                        }
                    },
                },
            }));
        } else {
            promises.push(Promise.resolve([]));
        }

        //array of arrays
        const [base_shifts, holiday_shifts, vacation_shifts, expenses] = await Promise.all(promises);
        //mapping
        const rows: InternalCsvRow[] = [];

        const map_shifts = (shift: any, is_holiday: boolean): InternalCsvRow => {
            const employee = shift.timesheet.employee;
            const week = computeWeekNumber(start, shift.date);
            return {
                company_code: employee.company_code,
                external_payroll_id: employee.external_payroll_id,
                timesheet_id: shift.timesheet.id,
                email: shift.timesheet.employee.user.email,
                shift_date: shift.date,
                full_name: `${employee.user.first_name} ${employee.user.last_name}`,
                bank_code: shift.bank_code?.bank_code ?? '',
                quantity_hours: computeHours(shift.start_time, shift.end_time),
                amount: undefined,
                week_number: week,
                pay_date: formatDate(end),
                holiday_date: is_holiday ? formatDate(shift.date) : '',
            }
        };
        //final mapping of all shifts based filters
        for (const shift of base_shifts) rows.push(map_shifts(shift, false));
        for (const shift of holiday_shifts) rows.push(map_shifts(shift, true));
        for (const shift of vacation_shifts) rows.push(map_shifts(shift, false));

        for (const expense of expenses) {
            const employee = expense.timesheet.employee;
            const week = computeWeekNumber(start, expense.date);
            rows.push({
                company_code: employee.company_code,
                external_payroll_id: employee.external_payroll_id,
                timesheet_id: expense.timesheet.id,
                email: '',
                full_name: `${employee.user.first_name} ${employee.user.last_name}`,
                bank_code: expense.bank_code?.bank_code ?? '',
                quantity_hours: undefined,
                amount: Number(expense.amount),
                week_number: week,
                pay_date: formatDate(end),
                holiday_date: '',
                shift_date: expense.date,
            })
        }

        //Final mapping and sorts
        rows.sort((a, b) => {
            if (a.external_payroll_id !== b.external_payroll_id) {
                return a.external_payroll_id - b.external_payroll_id;
            }
            const bank_code = String(a.bank_code).localeCompare(String(b.bank_code));
            if (bank_code !== 0) return bank_code;
            if (a.bank_code !== b.bank_code) return a.bank_code.localeCompare(b.bank_code);
            return 0;
        });


        //requalifies the real amount paid for holidays
        const holiday_rows = await applyHolidayRequalifications(rows, this.holiday_service);

        //groups hours by bank_code
        const consolidated_rows = await consolidateRowHoursAndAmountByType(holiday_rows);


        //requalifies regular hours into overtime when needed
        const requalified_rows = await applyOvertimeRequalifications(consolidated_rows, this.overtime_service);

        return requalified_rows;
    }

    resolveHolidayTypeCode = async (holiday: string): Promise<string> => {
        const holiday_code = await this.prisma.bankCodes.findFirst({
            where: { type: holiday },
            select: {
                bank_code: true,
                shifts: {
                    select: {
                        date: true,
                    },
                },
            },
        });
        if (!holiday_code) throw new BadRequestException('Missing Holiday bank code');

        return holiday_code.bank_code;
    }

    resolveVacationTypeCode = async (vacation: string): Promise<string> => {
        const vacation_code = await this.prisma.bankCodes.findFirst({
            where: { type: vacation },
            select: {
                bank_code: true,
                shifts: {
                    select: {
                        date: true,
                    },
                },
            },
        });
        if (!vacation_code) throw new BadRequestException('Missing Vacation bank code');
        return vacation_code.bank_code;
    }

}
