import { buildSplitAllocationReadModel } from "@nexus/application";
import { VacationType } from "@nexus/db";
import { asHolidayResolverDb, getResolvedCalendarHolidays } from "../lib/holiday-availability.js";
import {
  buildTimelineHolidayResourceIds,
  buildTimelineHolidayResourceWhere,
} from "./timeline-holiday-support.js";
import {
  loadTimelineEntriesReadModel,
  TimelineEntriesDbClient,
  TimelineEntriesFilters,
} from "./timeline-read-shared.js";

export async function loadTimelineHolidayOverlays(
  db: TimelineEntriesDbClient,
  input: TimelineEntriesFilters,
) {
  const readModel = await loadTimelineEntriesReadModel(db, input);
  return loadTimelineHolidayOverlaysForReadModel(db, input, readModel);
}

export async function loadTimelineHolidayOverlaysForReadModel(
  db: TimelineEntriesDbClient,
  input: TimelineEntriesFilters,
  readModel: ReturnType<typeof buildSplitAllocationReadModel>,
) {
  const resourceWhere = buildTimelineHolidayResourceWhere({
    chapters: input.chapters,
    eids: input.eids,
    countryCodes: input.countryCodes,
  });
  const matchingResources = resourceWhere
    ? await db.resource.findMany({
        where: resourceWhere,
        select: { id: true },
      })
    : [];
  const resourceIds = buildTimelineHolidayResourceIds({
    assignmentResourceIds: readModel.assignments.map((assignment) => assignment.resourceId),
    requestedResourceIds: input.resourceIds,
    matchingFilterResourceIds: matchingResources.map((resource) => resource.id),
  });

  if (resourceIds.length === 0) {
    return [];
  }

  const resources = await db.resource.findMany({
    where: { id: { in: resourceIds } },
    select: {
      id: true,
      countryId: true,
      federalState: true,
      metroCityId: true,
      country: { select: { code: true, name: true } },
      metroCity: { select: { name: true } },
    },
  });

  // Group resources by location key to deduplicate holiday resolution.
  // Resources sharing the same (countryId, federalState, metroCityId) get
  // identical holidays, so we resolve once per location instead of once per resource.
  const locationGroups = new Map<
    string,
    { locationResource: (typeof resources)[0]; resourceIds: string[] }
  >();
  for (const resource of resources) {
    const key = `${resource.countryId ?? ""}:${resource.federalState ?? ""}:${resource.metroCityId ?? ""}`;
    const existing = locationGroups.get(key);
    if (existing) {
      existing.resourceIds.push(resource.id);
    } else {
      locationGroups.set(key, { locationResource: resource, resourceIds: [resource.id] });
    }
  }

  const resolverDb = asHolidayResolverDb(db);
  const overlays = await Promise.all(
    [...locationGroups.values()].map(async ({ locationResource, resourceIds }) => {
      const holidays = await getResolvedCalendarHolidays(resolverDb, {
        periodStart: input.startDate,
        periodEnd: input.endDate,
        countryId: locationResource.countryId,
        countryCode: locationResource.country?.code ?? null,
        federalState: locationResource.federalState,
        metroCityId: locationResource.metroCityId,
        metroCityName: locationResource.metroCity?.name ?? null,
      });

      return resourceIds.flatMap((resourceId) => {
        const resource = resources.find((r) => r.id === resourceId)!;
        return holidays.map((holiday) => {
          const holidayDate = new Date(`${holiday.date}T00:00:00.000Z`);
          return {
            id: `calendar-holiday:${resourceId}:${holiday.date}`,
            resourceId,
            type: VacationType.PUBLIC_HOLIDAY,
            status: "APPROVED" as const,
            startDate: holidayDate,
            endDate: holidayDate,
            note: holiday.name,
            scope: holiday.scope,
            calendarName: holiday.calendarName,
            sourceType: holiday.sourceType,
            countryCode: resource.country?.code ?? null,
            countryName: resource.country?.name ?? null,
            federalState: resource.federalState ?? null,
            metroCityName: resource.metroCity?.name ?? null,
          };
        });
      });
    }),
  );

  return overlays.flat().sort((left, right) => {
    if (left.resourceId !== right.resourceId) {
      return left.resourceId.localeCompare(right.resourceId);
    }
    return left.startDate.getTime() - right.startDate.getTime();
  });
}
