import { buildSplitAllocationReadModel } from "@capakraken/application";
import { Prisma, VacationType } from "@capakraken/db";
import { z } from "zod";
import { asHolidayResolverDb, getResolvedCalendarHolidays } from "../lib/holiday-availability.js";
import { controllerProcedure, protectedProcedure } from "../trpc.js";
import {
  buildSelfServiceTimelineInput,
  createTimelineDateRange,
  createTimelineFilters,
  fmtDate,
  loadTimelineEntriesReadModel,
  TimelineEntriesDbClient,
  TimelineEntriesFilters,
  TimelineWindowFiltersSchema,
} from "./timeline-read-shared.js";

export function formatHolidayOverlays(
  overlays: Array<{
    id: string;
    resourceId: string;
    startDate: Date;
    endDate: Date;
    note?: string | null;
    scope?: string | null;
    calendarName?: string | null;
    sourceType?: string | null;
    countryCode?: string | null;
    countryName?: string | null;
    federalState?: string | null;
    metroCityName?: string | null;
  }>,
) {
  return overlays.map((overlay) => ({
    id: overlay.id,
    resourceId: overlay.resourceId,
    startDate: fmtDate(overlay.startDate),
    endDate: fmtDate(overlay.endDate),
    note: overlay.note ?? null,
    scope: overlay.scope ?? null,
    calendarName: overlay.calendarName ?? null,
    sourceType: overlay.sourceType ?? null,
    countryCode: overlay.countryCode ?? null,
    countryName: overlay.countryName ?? null,
    federalState: overlay.federalState ?? null,
    metroCityName: overlay.metroCityName ?? null,
  }));
}

export function summarizeHolidayOverlays(
  overlays: ReturnType<typeof formatHolidayOverlays>,
) {
  const resourceIds = new Set<string>();
  const byScope = new Map<string, number>();

  for (const overlay of overlays) {
    resourceIds.add(overlay.resourceId);
    const scope = overlay.scope ?? "UNKNOWN";
    byScope.set(scope, (byScope.get(scope) ?? 0) + 1);
  }

  return {
    overlayCount: overlays.length,
    holidayResourceCount: resourceIds.size,
    byScope: [...byScope.entries()]
      .sort(([left], [right]) => left.localeCompare(right))
      .map(([scope, count]) => ({ scope, count })),
  };
}

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 resourceIds = [...new Set(
    readModel.assignments
      .map((assignment) => assignment.resourceId)
      .filter((resourceId): resourceId is string => typeof resourceId === "string" && resourceId.length > 0),
  )];

  if (input.resourceIds && input.resourceIds.length > 0) {
    for (const resourceId of input.resourceIds) {
      if (resourceId && !resourceIds.includes(resourceId)) {
        resourceIds.push(resourceId);
      }
    }
  }

  const hasResourceFilters =
    (input.chapters?.length ?? 0) > 0 ||
    (input.eids?.length ?? 0) > 0 ||
    (input.countryCodes?.length ?? 0) > 0;

  if (hasResourceFilters) {
    const andConditions: Prisma.ResourceWhereInput[] = [];
    if (input.chapters && input.chapters.length > 0) {
      andConditions.push({ chapter: { in: input.chapters } });
    }
    if (input.eids && input.eids.length > 0) {
      andConditions.push({ eid: { in: input.eids } });
    }
    if (input.countryCodes && input.countryCodes.length > 0) {
      andConditions.push({ country: { code: { in: input.countryCodes } } });
    }

    const matchingResources = await db.resource.findMany({
      where: andConditions.length === 1 ? andConditions[0]! : { AND: andConditions },
      select: { id: true },
    });

    for (const resource of matchingResources) {
      if (!resourceIds.includes(resource.id)) {
        resourceIds.push(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 } },
    },
  });

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

      return holidays.map((holiday) => {
        const holidayDate = new Date(`${holiday.date}T00:00:00.000Z`);
        return {
          id: `calendar-holiday:${resource.id}:${holiday.date}`,
          resourceId: resource.id,
          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();
  });
}

export const timelineHolidayReadProcedures = {
  getHolidayOverlays: controllerProcedure
    .input(TimelineWindowFiltersSchema)
    .query(async ({ ctx, input }) => loadTimelineHolidayOverlays(ctx.db, input)),

  getMyHolidayOverlays: protectedProcedure
    .input(TimelineWindowFiltersSchema)
    .query(async ({ ctx, input }) => {
      const selfServiceInput = await buildSelfServiceTimelineInput(ctx, input);
      if (!selfServiceInput) {
        return [];
      }

      return loadTimelineHolidayOverlays(ctx.db, selfServiceInput);
    }),

  getHolidayOverlayDetail: controllerProcedure
    .input(
      z.object({
        startDate: z.string().optional(),
        endDate: z.string().optional(),
        durationDays: z.number().int().min(1).max(366).optional(),
        resourceIds: z.array(z.string()).optional(),
        projectIds: z.array(z.string()).optional(),
        clientIds: z.array(z.string()).optional(),
        chapters: z.array(z.string()).optional(),
        eids: z.array(z.string()).optional(),
        countryCodes: z.array(z.string()).optional(),
      }),
    )
    .query(async ({ ctx, input }) => {
      const { startDate, endDate } = createTimelineDateRange(input);
      const filters = createTimelineFilters(input);
      const holidayOverlays = await loadTimelineHolidayOverlays(ctx.db, {
        ...filters,
        startDate,
        endDate,
      });
      const formattedOverlays = formatHolidayOverlays(holidayOverlays);

      return {
        period: {
          startDate: fmtDate(startDate),
          endDate: fmtDate(endDate),
        },
        filters,
        summary: summarizeHolidayOverlays(formattedOverlays),
        overlays: formattedOverlays,
      };
    }),
};
