import { LeaveRepository } from '../repositories/LeaveRepository';
import { HolidayRepository } from '../repositories/HolidayRepository';
import { getPrisma } from '../infrastructure/database/PrismaSingleton';
import { parseDateOnly, toDateOnly, todayInCompanyTz } from '../domain/leave/dateUtils';
import { ValidationError } from '../types/errors';
import { isDisplayableWorker } from '../domain/employee/EmployeeRole';
import { TeamResolutionService } from '../domain/team/TeamResolutionService';

const MAX_RANGE_DAYS = 93;

export class CalendarFacade {
  constructor(
    private leaveRepo = new LeaveRepository(),
    private holidayRepo = new HolidayRepository(),
    private teamService = new TeamResolutionService(),
  ) {}

  private validateRange(from: string, to: string) {
    const fromD = parseDateOnly(from);
    const toD = parseDateOnly(to);
    const diff = (toD.getTime() - fromD.getTime()) / 86400000;
    if (diff > MAX_RANGE_DAYS) {
      throw new ValidationError('Date range cannot exceed 93 days');
    }
  }

  async listLeaves(
    from: string,
    to: string,
    filters: { employeeId?: string; teamId?: string; leaveType?: string; viewerEmployeeId?: string },
  ) {
    this.validateRange(from, to);
    const rows = await this.leaveRepo.findApprovedInRange(parseDateOnly(from), parseDateOnly(to));
    const prisma = getPrisma();
    let filtered = rows;

    if (filters.employeeId) {
      filtered = filtered.filter((r) => r.employee_id === filters.employeeId);
    }
    if (filters.leaveType) {
      filtered = filtered.filter((r) => r.leave_type === filters.leaveType);
    }
    if (filters.teamId) {
      if (filters.viewerEmployeeId) {
        const visible = await this.teamService.listVisibleTeamMembers(
          filters.teamId,
          filters.viewerEmployeeId,
        );
        const ids = new Set(visible.map((m) => m.employeeId));
        filtered = filtered.filter((r) => ids.has(r.employee_id));
      } else {
        const members = await prisma.l10_team_members.findMany({ where: { team_id: filters.teamId } });
        const pks = members.map((m) => m.employee_id);
        const employees = await prisma.employees.findMany({
          where: { id: { in: pks.map((id) => parseInt(id, 10)).filter((n) => !Number.isNaN(n)) } },
        });
        const ids = new Set(
          employees.filter((e) => isDisplayableWorker(e.is_manager)).map((e) => e.employee_id),
        );
        filtered = filtered.filter((r) => ids.has(r.employee_id));
      }
    }

    const employees = await prisma.employees.findMany({
      where: { employee_id: { in: filtered.map((r) => r.employee_id) } },
    });
    const nameMap = new Map(employees.map((e) => [e.employee_id, e.name]));
    const levelMap = new Map(employees.map((e) => [e.employee_id, e.is_manager ?? 0]));

    filtered = filtered.filter((r) => isDisplayableWorker(levelMap.get(r.employee_id)));

    return {
      from,
      to,
      items: filtered.map((r) => ({
        employeeId: r.employee_id,
        employeeName: nameMap.get(r.employee_id) ?? r.employee_id,
        leaveType: r.leave_type,
        duration: r.duration,
        halfDaySlot: r.half_day_slot,
        startDate: toDateOnly(r.start_date),
        endDate: toDateOnly(r.end_date),
        leaveDays: Number(r.leave_days),
      })),
    };
  }

  async leavesToday(date?: string) {
    const d = date ?? todayInCompanyTz();
    return this.listLeaves(d, d, {});
  }

  async listHolidays(from?: string, to?: string) {
    if (from && to) {
      return this.holidayRepo.listInRange(parseDateOnly(from), parseDateOnly(to));
    }
    return this.holidayRepo.list();
  }

  async summary(from: string, to: string) {
    const leaves = await this.listLeaves(from, to, {});
    const holidays = await this.listHolidays(from, to);
    return {
      from,
      to,
      approvedLeaveCount: leaves.items.length,
      holidayCount: holidays.length,
    };
  }
}
