import { Injectable } from "@nestjs/common";
import { PrismaPostgresService } from "prisma/postgres/prisma-postgres.service";

import { sevenDaysFrom, toDateFromString, toStringFromDate, toStringFromHHmm } from "src/common/utils/date-utils";
import { BankCodesResolver } from "src/common/mappers/bank-type-id.mapper";
import { EmailToIdResolver } from "src/common/mappers/email-id.mapper";
import { Result } from "src/common/errors/result-error.factory";

import { ShiftsCreateService } from "src/time-and-attendance/shifts/services/shifts-create.service";
import { timesheet_select } from "src/time-and-attendance/utils/selects.utils";
import { ShiftDto } from "src/time-and-attendance/shifts/shift.dto";
import { WEEKDAY_MAP } from "src/time-and-attendance/schedule-presets/schedule-presets.dto";
import { $Enums, SchedulePresetShifts } from "@prisma/client";
import { PayPeriodEventService } from "src/time-and-attendance/pay-period/services/pay-period-event.service";


@Injectable()
export class SchedulePresetsApplyService {
    constructor(
        private readonly prisma: PrismaPostgresService,
        private readonly emailResolver: EmailToIdResolver,
        private readonly shiftService: ShiftsCreateService,
        private readonly typeResolver: BankCodesResolver,
        private readonly payPeriodEventService: PayPeriodEventService,
    ) { }

    async applyPresetToTimesheet(email: string, timesheet_id: number, employee_email?: string): Promise<Result<boolean, string>> {
        const user_email = employee_email ?? email;
        const employee_id = await this.emailResolver.findIdByEmail(user_email);
        if (!employee_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };

        const employee_default_schedule_preset = await this.prisma.employees.findFirst({
            where: { id: employee_id.data },
            select: {
                schedule_preset: {
                    select: {
                        id: true,
                        shifts: true,
                    },
                },
            },
        });
        if (!employee_default_schedule_preset) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
        if (!employee_default_schedule_preset.schedule_preset) return { success: false, error: 'SCHEDULE_PRESET_NOT_FOUND' };

        const default_preset_shifts = await this.prisma.schedulePresetShifts.findMany({
            where: { preset_id: employee_default_schedule_preset.schedule_preset.id },
            select: {
                bank_code_id: true,
                start_time: true,
                end_time: true,
                is_remote: true,
                week_day: true,
            },
        });
        if (default_preset_shifts.length <= 0) return { success: false, error: 'INVALID_SCHEDULE_PRESET' };

        const timesheet = await this.prisma.timesheets.findUnique({
            where: { id: timesheet_id },
            select: timesheet_select,
        });
        if (!timesheet) return { success: false, error: 'TIMESHEET_NOT_FOUND' };
        if (timesheet.is_approved) return { success: false, error: 'INVALID_TIMESHEET' };
        if (timesheet.shift.length > 0) return { success: false, error: 'INVALID_TIMESHEET' };

        const dated_map = await sevenDaysFrom(timesheet.start_date);

        let created_shifts: ShiftDto[] = [];

        for (const preset_shift of default_preset_shifts) {
            const date = dated_map.find(date => date.getUTCDay() === WEEKDAY_MAP[preset_shift.week_day])
            if (!date) return { success: false, error: 'INVALID_PRESET_DATE' };

            const shift = await this.createShiftFromPreset(preset_shift, date!, timesheet.id)
            if (!shift.success) return { success: false, error: shift.error };

            created_shifts.push(shift.data);
        }

        const response = await this.shiftService.createOneOrManyShifts(user_email, created_shifts, false);
        if (response.success) {
            // notify timesheet-approval observers of changes
            if (!employee_email) {
                this.payPeriodEventService.emit({
                    employee_email: user_email,
                    event_type: 'preset',
                    action: 'create',
                })
            }

            return { success: true, data: true };
        }
        else
            return { success: false, error: 'There was an error applying presets for this week' };
    }


    async applyPresetToDay(email: string, timesheet_id: number, week_day_index: number, date: string, employee_email?: string): Promise<Result<boolean, string>> {
        const user_email = employee_email ?? email;
        const employee_id = await this.emailResolver.findIdByEmail(user_email);
        if (!employee_id.success) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
        const week_day = Object.keys(WEEKDAY_MAP)[week_day_index];

        const preset_shift = await this.prisma.employees.findFirst({
            where: { id: employee_id.data, },
            select: {
                schedule_preset: {
                    select: {
                        id: true,
                        shifts: {
                            where: { week_day: $Enums.Weekday[week_day] },
                            select: {
                                bank_code_id: true,
                                start_time: true,
                                end_time: true,
                                is_remote: true,
                                week_day: true,
                            },
                        },
                    },
                },
            },
        });

        if (!preset_shift) return { success: false, error: 'EMPLOYEE_NOT_FOUND' };
        if (!preset_shift.schedule_preset) return { success: false, error: 'SCHEDULE_PRESET_NOT_FOUND' };

        for (const shift of preset_shift.schedule_preset.shifts) {
            const created_shift = await this.createShiftFromPreset(shift, toDateFromString(date), timesheet_id);

            if (!created_shift.success)
                return { success: false, error: 'SHIFT_CREATE_FAILED' }

            await this.shiftService.createShift(employee_id.data, created_shift.data);
        }

        // notify timesheet-approval observers of changes
        if (!employee_email) {
            this.payPeriodEventService.emit({
                employee_email: user_email,
                event_type: 'preset',
                action: 'create',
            })
        }

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

    private createShiftFromPreset = async (preset: Partial<SchedulePresetShifts>, date: Date, timesheet_id: number): Promise<Result<ShiftDto, string>> => {
        const type = await this.typeResolver.findTypeByBankCodeId(preset.bank_code_id!);
        if (!type.success) return { success: false, error: 'INVALID_PRESET_SHIFT' };

        const shift: ShiftDto = {
            timesheet_id: timesheet_id,
            type: type.data,
            date: toStringFromDate(date),
            start_time: toStringFromHHmm(preset.start_time!),
            end_time: toStringFromHHmm(preset.end_time!),
            is_approved: false,
            is_remote: preset.is_remote!,
        };

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