import { VacationStatus } from "@capakraken/db";
import type { WeekdayAvailability } from "@capakraken/shared";
import {
  asHolidayResolverDb,
  collectHolidayAvailability,
  getResolvedCalendarHolidays,
} from "../lib/holiday-availability.js";
import {
  calculateEffectiveAvailableHours,
  countEffectiveWorkingDays,
  loadResourceDailyAvailabilityContexts,
} from "../lib/resource-capacity.js";

type ResourceLocation = {
  countryId: string | null;
  federalState: string | null;
  metroCityId: string | null;
  country?: { code?: string | null; name?: string | null } | null;
  metroCity?: { name?: string | null } | null;
};

type ResourceAvailabilityInput = {
  db: any;
  resource: ResourceLocation & {
    id: string;
  };
  weeklyAvailability: WeekdayAvailability;
  monthStart: Date;
  monthEnd: Date;
};

function getAvailabilityHoursForDate(
  availability: WeekdayAvailability,
  date: Date,
): number {
  const dayKey = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"][date.getUTCDay()] as keyof WeekdayAvailability;
  return availability[dayKey] ?? 0;
}

function sumAvailabilityHoursForDates(
  availability: WeekdayAvailability,
  dates: Date[],
): number {
  return dates.reduce((sum, date) => sum + getAvailabilityHoursForDate(availability, date), 0);
}

export async function loadResourceGraphAvailability(input: ResourceAvailabilityInput) {
  const { db, resource, weeklyAvailability, monthStart, monthEnd } = input;

  const vacations = await db.vacation.findMany({
    where: {
      resourceId: resource.id,
      status: VacationStatus.APPROVED,
      startDate: { lte: monthEnd },
      endDate: { gte: monthStart },
    },
    select: { startDate: true, endDate: true, type: true, isHalfDay: true },
  });
  const resolvedHolidays = await getResolvedCalendarHolidays(asHolidayResolverDb(db), {
    periodStart: monthStart,
    periodEnd: monthEnd,
    countryId: resource.countryId,
    countryCode: resource.country?.code,
    federalState: resource.federalState,
    metroCityId: resource.metroCityId,
    metroCityName: resource.metroCity?.name,
  });
  const holidayAvailability = collectHolidayAvailability({
    vacations,
    periodStart: monthStart,
    periodEnd: monthEnd,
    countryCode: resource.country?.code,
    federalState: resource.federalState,
    metroCityName: resource.metroCity?.name,
    resolvedHolidayStrings: resolvedHolidays.map((holiday) => holiday.date),
  });
  const publicHolidayStrings = holidayAvailability.publicHolidayStrings;
  const absenceDateStrings = holidayAvailability.absenceDateStrings;
  const absenceDays = holidayAvailability.absenceDays;
  const halfDayCount = absenceDays.filter((absence) => absence.isHalfDay).length;
  const vacationDayCount = absenceDays.filter((absence) => absence.type === "VACATION").length;
  const sickDayCount = absenceDays.filter((absence) => absence.type === "SICK").length;
  const publicHolidayCount = resolvedHolidays.length;
  const absenceDayEquivalent = absenceDays.reduce((sum, absence) => {
    if (absence.type === "PUBLIC_HOLIDAY") {
      return sum;
    }
    return sum + (absence.isHalfDay ? 0.5 : 1);
  }, 0);

  const contexts = await loadResourceDailyAvailabilityContexts(
    db,
    [{
      id: resource.id,
      availability: weeklyAvailability,
      countryId: resource.countryId,
      countryCode: resource.country?.code,
      federalState: resource.federalState,
      metroCityId: resource.metroCityId,
      metroCityName: resource.metroCity?.name,
    }],
    monthStart,
    monthEnd,
  );
  const availabilityContext = contexts.get(resource.id);

  const baseWorkingDays = countEffectiveWorkingDays({
    availability: weeklyAvailability,
    periodStart: monthStart,
    periodEnd: monthEnd,
    context: undefined,
  });
  const effectiveWorkingDays = countEffectiveWorkingDays({
    availability: weeklyAvailability,
    periodStart: monthStart,
    periodEnd: monthEnd,
    context: availabilityContext,
  });
  const baseAvailableHours = calculateEffectiveAvailableHours({
    availability: weeklyAvailability,
    periodStart: monthStart,
    periodEnd: monthEnd,
    context: undefined,
  });
  const effectiveAvailableHours = calculateEffectiveAvailableHours({
    availability: weeklyAvailability,
    periodStart: monthStart,
    periodEnd: monthEnd,
    context: availabilityContext,
  });
  const publicHolidayDates = resolvedHolidays.map((holiday) => new Date(`${holiday.date}T00:00:00.000Z`));
  const publicHolidayWorkdayCount = publicHolidayDates.reduce((count, date) => (
    count + (getAvailabilityHoursForDate(weeklyAvailability, date) > 0 ? 1 : 0)
  ), 0);
  const publicHolidayHoursDeduction = sumAvailabilityHoursForDates(
    weeklyAvailability,
    publicHolidayDates,
  );
  const absenceHoursDeduction = absenceDays.reduce((sum, absence) => {
    if (absence.type === "PUBLIC_HOLIDAY") {
      return sum;
    }
    const baseHours = getAvailabilityHoursForDate(weeklyAvailability, absence.date);
    return sum + baseHours * (absence.isHalfDay ? 0.5 : 1);
  }, 0);
  const effectiveHoursPerWorkingDay = effectiveWorkingDays > 0
    ? effectiveAvailableHours / effectiveWorkingDays
    : 0;
  const holidayScopeSummary = [
    resource.country?.code ?? "—",
    resource.federalState ?? "—",
    resource.metroCity?.name ?? "—",
  ].join(" / ");
  const holidayExamples = resolvedHolidays.length > 0
    ? resolvedHolidays.slice(0, 4).map((holiday) => `${holiday.date} ${holiday.name}`).join(", ")
    : "none";
  const holidayScopeBreakdown = resolvedHolidays.reduce<Record<string, number>>((counts, holiday) => {
    counts[holiday.scope] = (counts[holiday.scope] ?? 0) + 1;
    return counts;
  }, {});

  return {
    absenceDateStrings,
    absenceDayEquivalent,
    absenceDays,
    absenceHoursDeduction,
    availabilityContext,
    baseAvailableHours,
    baseWorkingDays,
    effectiveAvailableHours,
    effectiveHoursPerWorkingDay,
    effectiveWorkingDays,
    halfDayCount,
    holidayExamples,
    holidayScopeBreakdown,
    holidayScopeSummary,
    publicHolidayCount,
    publicHolidayHoursDeduction,
    publicHolidayStrings,
    publicHolidayWorkdayCount,
    resolvedHolidays,
    sickDayCount,
    vacationDayCount,
  };
}
