import {
  CreateHolidayCalendarEntrySchema,
  CreateHolidayCalendarSchema,
  UpdateHolidayCalendarEntrySchema,
  UpdateHolidayCalendarSchema,
} from "@capakraken/shared";
import { z } from "zod";
import { findUniqueOrThrow } from "../db/helpers.js";
import { createAuditEntry } from "../lib/audit.js";
import type { TRPCContext } from "../trpc.js";
import { asHolidayCalendarDb } from "./holiday-calendar-shared.js";
import {
  buildHolidayCalendarCreateData,
  buildHolidayCalendarEntryCreateData,
  buildHolidayCalendarEntryUpdateData,
  buildHolidayCalendarUpdateData,
  holidayCalendarDetailInclude,
} from "./holiday-calendar-support.js";
import {
  assertHolidayCalendarEntryDateAvailable,
  assertHolidayCalendarScopeConsistency,
  clampHolidayCalendarDate,
  normalizeHolidayCalendarScopeInput,
  resolveHolidayCalendarUpdateScope,
} from "./holiday-calendar-write-support.js";

type HolidayCalendarProcedureContext = Pick<TRPCContext, "db" | "dbUser">;

function withAuditUser(userId: string | undefined) {
  return userId ? { userId } : {};
}

export const holidayCalendarIdInputSchema = z.object({
  id: z.string(),
});

export const holidayCalendarUpdateInputSchema = z.object({
  id: z.string(),
  data: UpdateHolidayCalendarSchema,
});

export const holidayCalendarEntryUpdateInputSchema = z.object({
  id: z.string(),
  data: UpdateHolidayCalendarEntrySchema,
});

type HolidayCalendarIdInput = z.infer<typeof holidayCalendarIdInputSchema>;
type HolidayCalendarCreateInput = z.infer<typeof CreateHolidayCalendarSchema>;
type HolidayCalendarUpdateInput = z.infer<typeof holidayCalendarUpdateInputSchema>;
type HolidayCalendarEntryCreateInput = z.infer<typeof CreateHolidayCalendarEntrySchema>;
type HolidayCalendarEntryUpdateInput = z.infer<typeof holidayCalendarEntryUpdateInputSchema>;

export async function createHolidayCalendar(
  ctx: HolidayCalendarProcedureContext,
  input: HolidayCalendarCreateInput,
) {
  const db = asHolidayCalendarDb(ctx.db);

  await findUniqueOrThrow(
    ctx.db.country.findUnique({
      where: { id: input.countryId },
      select: { id: true, name: true },
    }),
    "Country",
  );

  const normalizedScope = normalizeHolidayCalendarScopeInput({
    stateCode: input.stateCode,
    metroCityId: input.metroCityId,
  });

  await assertHolidayCalendarScopeConsistency(db, {
    scopeType: input.scopeType,
    countryId: input.countryId,
    ...normalizedScope,
  });

  const created = await db.holidayCalendar.create({
    data: buildHolidayCalendarCreateData({
      ...input,
      normalizedScope,
    }),
    include: holidayCalendarDetailInclude,
  });

  void createAuditEntry({
    db: ctx.db,
    entityType: "HolidayCalendar",
    entityId: created.id,
    entityName: created.name,
    action: "CREATE",
    ...withAuditUser(ctx.dbUser?.id),
    after: created as unknown as Record<string, unknown>,
    source: "ui",
  });

  return created;
}

export async function updateHolidayCalendar(
  ctx: HolidayCalendarProcedureContext,
  input: HolidayCalendarUpdateInput,
) {
  const db = asHolidayCalendarDb(ctx.db);
  const existing = await findUniqueOrThrow<any>(
    db.holidayCalendar.findUnique({ where: { id: input.id } }),
    "Holiday calendar",
  );

  const { stateCode, metroCityId } = resolveHolidayCalendarUpdateScope({
    existing,
    data: input.data,
  });

  await assertHolidayCalendarScopeConsistency(db, {
    scopeType: existing.scopeType,
    countryId: existing.countryId,
    stateCode,
    metroCityId,
  }, existing.id);

  const updated = await db.holidayCalendar.update({
    where: { id: input.id },
    data: buildHolidayCalendarUpdateData({
      data: input.data,
      resolvedScope: {
        stateCode,
        metroCityId,
      },
    }),
    include: holidayCalendarDetailInclude,
  });

  void createAuditEntry({
    db: ctx.db,
    entityType: "HolidayCalendar",
    entityId: updated.id,
    entityName: updated.name,
    action: "UPDATE",
    ...withAuditUser(ctx.dbUser?.id),
    before: existing as unknown as Record<string, unknown>,
    after: updated as unknown as Record<string, unknown>,
    source: "ui",
  });

  return updated;
}

export async function deleteHolidayCalendar(
  ctx: HolidayCalendarProcedureContext,
  input: HolidayCalendarIdInput,
) {
  const db = asHolidayCalendarDb(ctx.db);
  const existing = await findUniqueOrThrow<any>(
    db.holidayCalendar.findUnique({
      where: { id: input.id },
      include: { entries: true },
    }),
    "Holiday calendar",
  );

  await db.holidayCalendar.delete({ where: { id: input.id } });

  void createAuditEntry({
    db: ctx.db,
    entityType: "HolidayCalendar",
    entityId: existing.id,
    entityName: existing.name,
    action: "DELETE",
    ...withAuditUser(ctx.dbUser?.id),
    before: existing as unknown as Record<string, unknown>,
    source: "ui",
  });

  return { success: true, id: existing.id, name: existing.name };
}

export async function createHolidayCalendarEntry(
  ctx: HolidayCalendarProcedureContext,
  input: HolidayCalendarEntryCreateInput,
) {
  const db = asHolidayCalendarDb(ctx.db);

  await findUniqueOrThrow(
    db.holidayCalendar.findUnique({
      where: { id: input.holidayCalendarId },
      select: { id: true, name: true },
    }),
    "Holiday calendar",
  );

  await assertHolidayCalendarEntryDateAvailable(db, {
    holidayCalendarId: input.holidayCalendarId,
    date: input.date,
  });

  const created = await db.holidayCalendarEntry.create({
    data: buildHolidayCalendarEntryCreateData({
      data: input,
      date: clampHolidayCalendarDate(input.date),
    }),
  });

  void createAuditEntry({
    db: ctx.db,
    entityType: "HolidayCalendarEntry",
    entityId: created.id,
    entityName: created.name,
    action: "CREATE",
    ...withAuditUser(ctx.dbUser?.id),
    after: created as unknown as Record<string, unknown>,
    source: "ui",
  });

  return created;
}

export async function updateHolidayCalendarEntry(
  ctx: HolidayCalendarProcedureContext,
  input: HolidayCalendarEntryUpdateInput,
) {
  const db = asHolidayCalendarDb(ctx.db);
  const existing = await findUniqueOrThrow<any>(
    db.holidayCalendarEntry.findUnique({ where: { id: input.id } }),
    "Holiday calendar entry",
  );
  const nextDate = input.data.date !== undefined
    ? clampHolidayCalendarDate(input.data.date)
    : existing.date;

  await assertHolidayCalendarEntryDateAvailable(db, {
    holidayCalendarId: existing.holidayCalendarId,
    date: nextDate,
  }, existing.id);

  const updated = await db.holidayCalendarEntry.update({
    where: { id: input.id },
    data: buildHolidayCalendarEntryUpdateData({
      data: input.data,
      date: nextDate,
    }),
  });

  void createAuditEntry({
    db: ctx.db,
    entityType: "HolidayCalendarEntry",
    entityId: updated.id,
    entityName: updated.name,
    action: "UPDATE",
    ...withAuditUser(ctx.dbUser?.id),
    before: existing as unknown as Record<string, unknown>,
    after: updated as unknown as Record<string, unknown>,
    source: "ui",
  });

  return updated;
}

export async function deleteHolidayCalendarEntry(
  ctx: HolidayCalendarProcedureContext,
  input: HolidayCalendarIdInput,
) {
  const db = asHolidayCalendarDb(ctx.db);
  const existing = await findUniqueOrThrow<any>(
    db.holidayCalendarEntry.findUnique({ where: { id: input.id } }),
    "Holiday calendar entry",
  );

  await db.holidayCalendarEntry.delete({ where: { id: input.id } });

  void createAuditEntry({
    db: ctx.db,
    entityType: "HolidayCalendarEntry",
    entityId: existing.id,
    entityName: existing.name,
    action: "DELETE",
    ...withAuditUser(ctx.dbUser?.id),
    before: existing as unknown as Record<string, unknown>,
    source: "ui",
  });

  return { success: true, id: existing.id, name: existing.name };
}
