import { Prisma } from '@prisma/client';
import { getPrisma } from '../../infrastructure/database/PrismaSingleton';
import { LeaveBalanceService } from './LeaveBalanceService';
import { LeaveType } from '../../types/prisma';
import { ValidationError } from '../../types/errors';
import { getYearMonth } from './dateUtils';

/**
 * Policy:
 * - Applied half-day leave: away max 2 hours; every 3 half-days = 1 leave deducted.
 * - Short leave (late join or early leave): every 3 in a calendar month = 1 leave deducted.
 */
export class LeaveMonthCounterService {
  private prisma = getPrisma();
  private balances = new LeaveBalanceService();

  async getCounters(employeeId: string, yearMonth: string) {
    const row = await this.prisma.leave_month_counters.findUnique({
      where: { employee_id_year_month: { employee_id: employeeId, year_month: yearMonth } },
    });
    return {
      yearMonth,
      halfDayCount: row?.half_day_count ?? 0,
      shortLeaveCount: row?.short_leave_count ?? 0,
      halfDaysUntilLeave: 3 - ((row?.half_day_count ?? 0) % 3),
      shortLeavesUntilLeave: 3 - ((row?.short_leave_count ?? 0) % 3),
    };
  }

  /**
   * Record an approved half-day leave application.
   * Does not deduct immediately unless this is the 3rd / 6th / … half-day in the month.
   */
  async recordHalfDayLeave(
    employeeId: string,
    leaveType: LeaveType,
    leaveDate: string,
    refId: string,
    tx?: Prisma.TransactionClient,
  ) {
    const db = tx ?? this.prisma;
    const yearMonth = getYearMonth(leaveDate);
    const row = await this.upsertCounter(db, employeeId, yearMonth);
    const next = row.half_day_count + 1;
    await db.leave_month_counters.update({
      where: { id: row.id },
      data: { half_day_count: next },
    });

    let deducted = 0;
    if (next % 3 === 0) {
      await this.balances.deduct(
        employeeId,
        leaveType,
        1,
        `3 half-day leaves → 1 leave (${yearMonth})`,
        'half_day_conversion',
        refId,
        tx,
      );
      deducted = 1;
    }

    return { halfDayCount: next, deducted };
  }

  /**
   * Before approving a half-day, ensure balance exists if this will trigger a full-leave deduction.
   */
  async assertHalfDayBalanceAvailable(employeeId: string, leaveType: LeaveType, leaveDate: string) {
    const yearMonth = getYearMonth(leaveDate);
    const row = await this.prisma.leave_month_counters.findUnique({
      where: { employee_id_year_month: { employee_id: employeeId, year_month: yearMonth } },
    });
    const next = (row?.half_day_count ?? 0) + 1;
    if (next % 3 !== 0) return;

    const bal = await this.balances.getBalances(employeeId);
    const available =
      leaveType === 'CASUAL'
        ? bal.casualLeave
        : leaveType === 'COMP_OFF'
          ? bal.compOff
          : bal.sickLeave + (leaveType === 'SICK' || leaveType === 'EMERGENCY_MEDICAL' ? bal.casualLeave : 0);
    if (available < 1) {
      throw new ValidationError(
        'This half-day is the 3rd in the month and requires 1 leave balance (3 half-days = 1 leave)',
      );
    }
  }

  /**
   * Late join or early leave → short leave. Every 3rd in the month deducts 1 Casual Leave.
   */
  async recordShortLeave(
    employeeId: string,
    dateStr: string,
    reason: 'LATE_JOIN' | 'EARLY_LEAVE',
    refId: string,
  ) {
    const yearMonth = getYearMonth(dateStr);
    const existingEvent = await this.prisma.lateness_penalties.findFirst({
      where: {
        employee_id: employeeId,
        month: yearMonth,
        reason: `${reason}:${dateStr}`,
        waived: false,
      },
    });
    if (existingEvent) {
      return { shortLeaveCount: existingEvent.penalty_days ? Number(existingEvent.penalty_days) : 0, deducted: 0, skipped: true };
    }

    const row = await this.upsertCounter(this.prisma, employeeId, yearMonth);
    const next = row.short_leave_count + 1;
    await this.prisma.leave_month_counters.update({
      where: { id: row.id },
      data: { short_leave_count: next },
    });

    let deducted = 0;
    if (next % 3 === 0) {
      try {
        await this.balances.deduct(
          employeeId,
          'CASUAL',
          1,
          `3 short leaves → 1 Casual Leave (${yearMonth}, ${reason})`,
          'short_leave_conversion',
          refId,
        );
        deducted = 1;
      } catch {
        try {
          await this.balances.deduct(
            employeeId,
            'SICK',
            1,
            `3 short leaves → 1 Sick Leave (${yearMonth}, ${reason})`,
            'short_leave_conversion',
            refId,
          );
          deducted = 1;
        } catch {
          /* insufficient balance — still counted */
        }
      }
    }

    await this.prisma.lateness_penalties.create({
      data: {
        employee_id: employeeId,
        month: yearMonth,
        penalty_days: deducted,
        reason: `${reason}:${dateStr}`,
      },
    });

    return { shortLeaveCount: next, deducted, skipped: false };
  }

  private async upsertCounter(db: Prisma.TransactionClient | ReturnType<typeof getPrisma>, employeeId: string, yearMonth: string) {
    const existing = await db.leave_month_counters.findUnique({
      where: { employee_id_year_month: { employee_id: employeeId, year_month: yearMonth } },
    });
    if (existing) return existing;
    return db.leave_month_counters.create({
      data: { employee_id: employeeId, year_month: yearMonth, half_day_count: 0, short_leave_count: 0 },
    });
  }
}
