import { VacationStatus, VacationType } from "@capakraken/db";
import { asHolidayResolverDb, getResolvedCalendarHolidays } from "../lib/holiday-availability.js";
import type { TRPCContext } from "../trpc.js";

type VacationDb = TRPCContext["db"];

export type BatchCreatePublicHolidaysInput = {
  year: number;
  federalState?: string | undefined;
  chapter?: string | undefined;
  replaceExisting: boolean;
};

export async function batchCreatePublicHolidayVacations(
  db: VacationDb,
  input: BatchCreatePublicHolidaysInput,
  adminUserId: string,
): Promise<{ created: number; holidays: number; resources: number }> {
  const resources = await db.resource.findMany({
    where: {
      isActive: true,
      ...(input.chapter ? { chapter: input.chapter } : {}),
    },
    select: {
      id: true,
      federalState: true,
      countryId: true,
      metroCityId: true,
      country: { select: { code: true } },
      metroCity: { select: { name: true } },
    },
  });

  if (resources.length === 0) {
    return { created: 0, holidays: 0, resources: 0 };
  }

  const periodStart = new Date(`${input.year}-01-01T00:00:00.000Z`);
  const periodEnd = new Date(`${input.year}-12-31T00:00:00.000Z`);

  // Step 1: Group resources by their holiday-resolution key so we call
  // getResolvedCalendarHolidays once per unique location combo instead of
  // once per resource (~5-10 calls vs. 500+).
  type HolidayGroup = {
    countryId: string | null;
    countryCode: string | null | undefined;
    federalState: string | null | undefined;
    metroCityId: string | null;
    metroCityName: string | null | undefined;
    resourceIds: string[];
  };

  const groups = new Map<string, HolidayGroup>();
  for (const resource of resources) {
    const effectiveFederalState = input.federalState ?? resource.federalState;
    const key = [
      resource.countryId ?? "",
      resource.country?.code ?? "",
      effectiveFederalState ?? "",
      resource.metroCityId ?? "",
    ].join("|");

    if (!groups.has(key)) {
      groups.set(key, {
        countryId: resource.countryId,
        countryCode: resource.country?.code,
        federalState: effectiveFederalState,
        metroCityId: resource.metroCityId,
        metroCityName: resource.metroCity?.name,
        resourceIds: [],
      });
    }
    groups.get(key)!.resourceIds.push(resource.id);
  }

  // Step 2: Resolve holidays once per group (parallel).
  const resourceHolidays = new Map<string, Array<{ date: string; name: string }>>();
  await Promise.all(
    [...groups.values()].map(async (group) => {
      const holidays = await getResolvedCalendarHolidays(asHolidayResolverDb(db), {
        periodStart,
        periodEnd,
        countryId: group.countryId,
        countryCode: group.countryCode,
        federalState: group.federalState,
        metroCityId: group.metroCityId,
        metroCityName: group.metroCityName,
      });
      for (const resourceId of group.resourceIds) {
        resourceHolidays.set(resourceId, holidays);
      }
    }),
  );

  // Step 3: Build the full list of (resourceId, date) pairs.
  const allPairs: Array<{ resourceId: string; startDate: Date; endDate: Date; name: string }> = [];
  for (const resource of resources) {
    const holidays = resourceHolidays.get(resource.id) ?? [];
    for (const holiday of holidays) {
      const date = new Date(holiday.date);
      allPairs.push({ resourceId: resource.id, startDate: date, endDate: date, name: holiday.name });
    }
  }

  const holidayCount = allPairs.length;

  if (holidayCount === 0) {
    return { created: 0, holidays: 0, resources: resources.length };
  }

  const resourceIds = resources.map((r) => r.id);

  // Step 4: Batch delete for the whole period if replaceExisting (1 query).
  if (input.replaceExisting) {
    await db.vacation.deleteMany({
      where: {
        resourceId: { in: resourceIds },
        type: VacationType.PUBLIC_HOLIDAY,
        startDate: { gte: periodStart, lte: periodEnd },
      },
    });
  }

  // Step 5: Batch fetch all existing entries for the period (1 query).
  const existing = await db.vacation.findMany({
    where: {
      resourceId: { in: resourceIds },
      type: VacationType.PUBLIC_HOLIDAY,
      startDate: { gte: periodStart, lte: periodEnd },
    },
    select: { resourceId: true, startDate: true },
  });

  const existingSet = new Set(
    existing.map((e) => `${e.resourceId}::${new Date(e.startDate).toISOString()}`),
  );

  // Step 6: createMany for all new entries (1 query).
  const toCreate = allPairs.filter(
    (p) => !existingSet.has(`${p.resourceId}::${p.startDate.toISOString()}`),
  );

  if (toCreate.length > 0) {
    const now = new Date();
    await db.vacation.createMany({
      data: toCreate.map((p) => ({
        resourceId: p.resourceId,
        type: VacationType.PUBLIC_HOLIDAY,
        status: VacationStatus.APPROVED,
        startDate: p.startDate,
        endDate: p.endDate,
        note: p.name,
        requestedById: adminUserId,
        approvedById: adminUserId,
        approvedAt: now,
      })),
      skipDuplicates: true,
    });
  }

  return { created: toCreate.length, holidays: holidayCount, resources: resources.length };
}
