import { getPrisma } from '../infrastructure/database/PrismaSingleton';
import { TeamResolutionService } from '../domain/team/TeamResolutionService';
import { NotificationFactory } from '../factories/NotificationFactory';
import { parseDateOnly, getYearMonth, isWeekend } from '../domain/leave/dateUtils';
import { NotFoundError, ValidationError } from '../types/errors';
import { HolidayRepository } from '../repositories/HolidayRepository';

export class CompensationFacade {
  constructor(
    private teamService = new TeamResolutionService(),
    private holidayRepo = new HolidayRepository(),
    private notifications?: NotificationFactory,
  ) {}

  private prisma = getPrisma();

  async evaluateOffDayWork(employeeId: string, dateStr: string, hours: number) {
    if (hours < 9) {
      if (hours >= 4.5) {
        /* partial credit OQ16 - simplified */
      }
      return null;
    }

    const isOffDay = isWeekend(dateStr) || (await this.isHoliday(dateStr));
    if (!isOffDay) return null;

    const team = await this.teamService.resolveByBusinessId(employeeId);
    const month = getYearMonth(dateStr);
    const schedule = await this.prisma.team_shift_schedules.findUnique({
      where: { team_id_effective_month: { team_id: team.teamId, effective_month: month } },
    });

    const day = new Date(dateStr).getUTCDay();
    if (day === 6 && schedule?.saturday_shift_enabled) return null;
    if (day === 0 && schedule?.sunday_shift_enabled) return null;
    if (!isWeekend(dateStr) && schedule?.holiday_shift_enabled) return null;

    const existing = await this.prisma.off_day_compensation.findFirst({
      where: { employee_id: employeeId, work_date: parseDateOnly(dateStr) },
    });
    if (existing) return existing;

    const choiceDueAt = new Date(Date.now() + 30 * 86400000);
    const record = await this.prisma.off_day_compensation.create({
      data: {
        employee_id: employeeId,
        work_date: parseDateOnly(dateStr),
        hours,
        status: 'PENDING_CHOICE',
        choice_due_at: choiceDueAt,
      },
    });

    const emp = await this.prisma.employees.findUnique({ where: { employee_id: employeeId } });
    if (emp && this.notifications) {
      await this.notifications.createEmail('OFF_DAY_COMP_CHOICE', {
        to: emp.email,
        workDate: dateStr,
      }).send();
    }
    return record;
  }

  private async isHoliday(dateStr: string): Promise<boolean> {
    const h = await this.holidayRepo.listInRange(parseDateOnly(dateStr), parseDateOnly(dateStr));
    return h.length > 0;
  }

  async listPending(employeeId: string) {
    const rows = await this.prisma.off_day_compensation.findMany({
      where: { employee_id: employeeId, status: 'PENDING_CHOICE' },
    });
    return { items: rows };
  }

  async elect(employeeId: string, id: string, choice: 'BONUS' | 'COMP_OFF') {
    const row = await this.prisma.off_day_compensation.findUnique({ where: { id } });
    if (!row || row.employee_id !== employeeId) throw new NotFoundError();
    if (row.status !== 'PENDING_CHOICE') throw new ValidationError('Already elected');

    if (choice === 'COMP_OFF') {
      await this.prisma.comp_off_credits.create({
        data: {
          employee_id: employeeId,
          earned_date: row.work_date,
          expires_at: new Date(Date.now() + 90 * 86400000),
          status: 'AVAILABLE',
          source_compensation_id: id,
        },
      });
      const updated = await this.prisma.off_day_compensation.update({
        where: { id },
        data: { status: 'COMP_OFF_CREDITED', choice: 'COMP_OFF' },
      });
      return updated;
    }

    const bonusAmount = 1500;
    const updated = await this.prisma.off_day_compensation.update({
      where: { id },
      data: { status: 'BONUS_SELECTED', choice: 'BONUS', bonus_amount: bonusAmount },
    });
    return updated;
  }

  async expireCredits() {
    const result = await this.prisma.comp_off_credits.updateMany({
      where: { status: 'AVAILABLE', expires_at: { lt: new Date() } },
      data: { status: 'EXPIRED' },
    });
    return { expired: result.count };
  }

  async autoElectAfter30Days() {
    const cutoff = new Date();
    const pending = await this.prisma.off_day_compensation.findMany({
      where: { status: 'PENDING_CHOICE', choice_due_at: { lt: cutoff } },
    });
    for (const p of pending) {
      await this.elect(p.employee_id, p.id, 'COMP_OFF');
    }
    return { processed: pending.length };
  }
}
