import { Injectable } from '@nestjs/common';
import { Prisma, PrismaClient } from 'prisma/postgres/generated/prisma/client/postgres/client';
import { getWeekStart, getWeekEnd, computeHours } from 'src/common/utils/date-utils';
import { PrismaPostgresService } from 'prisma/postgres/prisma-postgres.service';
import { DAILY_LIMIT_HOURS, WEEKLY_LIMIT_HOURS } from 'src/common/utils/constants.utils';
import { Result } from 'src/common/errors/result-error.factory';


type Tx = Prisma.TransactionClient | PrismaClient;

type WeekOvertimeSummary = {
    week_start: string;
    week_end: string;
    week_total_hours: number;
    weekly_overtime: number;
    daily_overtime_kept: number;
    total_overtime: number;
    breakdown: Array<{
        date: string;
        day_hours: number;
        day_overtime: number;
        daily_kept: number;
        running_total_before: number;
    }>;
};

@Injectable()
export class OvertimeService {

    private INCLUDED_TYPES = ['EMERGENCY', 'EVENING', 'OVERTIME', 'REGULAR', 'HOLIDAY', 'BANKING'] as const; // included types for weekly overtime calculation

    constructor(private prisma: PrismaPostgresService) { }

    async getWeekOvertimeSummary(timesheet_id: number, date: Date, tx?: Tx): Promise<Result<WeekOvertimeSummary, string>> {
        const db = (tx ?? this.prisma) as PrismaClient;

        const week_start = getWeekStart(date);
        const week_end = getWeekEnd(week_start);

        const shifts = await db.shifts.findMany({
            where: {
                timesheet_id,
                date: { gte: week_start, lte: week_end },
                bank_code: { type: { in: this.INCLUDED_TYPES as unknown as string[] } },
            },
            select: { date: true, start_time: true, end_time: true },
            orderBy: [{ date: 'asc' }, { start_time: 'asc' }],
        });

        const day_totals = new Map<string, number>();
        for (const shift of shifts) {
            const key = shift.date.toISOString().slice(0, 10);
            const hours = computeHours(shift.start_time, shift.end_time, 5);
            day_totals.set(key, (day_totals.get(key) ?? 0) + hours);
        }

        const days: string[] = [];
        for (let i = 0; i < 7; i++) {
            const day = new Date(week_start.getTime() + i * 24 * 60 * 60 * 1000);
            days.push(day.toISOString().slice(0, 10));
        }

        const week_total_hours = [...day_totals.values()].reduce((a, b) => a + b, 0);
        const weekly_overtime = Math.max(0, week_total_hours - WEEKLY_LIMIT_HOURS);

        let running = 0;
        let daily_kept_sum = 0;
        const breakdown: WeekOvertimeSummary['breakdown'] = [];

        for (const key of days) {
            const day_hours = day_totals.get(key) ?? 0;
            const day_overtime = Math.max(0, day_hours - DAILY_LIMIT_HOURS);

            const cap_before_40 = Math.max(0, WEEKLY_LIMIT_HOURS - running);
            const daily_kept = Math.min(day_overtime, cap_before_40);

            breakdown.push({
                date: key,
                day_hours,
                day_overtime,
                daily_kept,
                running_total_before: running,
            });

            daily_kept_sum += daily_kept;
            running += day_hours;
        }
        const total_overtime = weekly_overtime + daily_kept_sum;

        return {
            success: true,
            data: {
                week_start: week_start.toISOString().slice(0, 10),
                week_end: week_end.toISOString().slice(0, 10),
                week_total_hours,
                weekly_overtime,
                daily_overtime_kept: daily_kept_sum,
                total_overtime,
                breakdown,
            }
        };
    }
}
