import { SystemRole } from "@capakraken/shared";
import { describe, expect, it, vi } from "vitest";
import { createCallerFactory } from "../trpc.js";
import { holidayCalendarRouter } from "../router/holiday-calendar.js";

vi.mock("../lib/audit.js", () => ({
  createAuditEntry: vi.fn().mockResolvedValue(undefined),
}));

const createCaller = createCallerFactory(holidayCalendarRouter);

function createProtectedCaller(db: Record<string, unknown>) {
  return createCaller({
    session: {
      user: { email: "user@example.com", name: "User", image: null },
      expires: "2026-12-31T00:00:00.000Z",
    },
    db: db as never,
    dbUser: {
      id: "user_1",
      systemRole: SystemRole.USER,
      permissionOverrides: null,
    },
  });
}

function createAdminCaller(db: Record<string, unknown>) {
  return createCaller({
    session: {
      user: { email: "admin@example.com", name: "Admin", image: null },
      expires: "2026-12-31T00:00:00.000Z",
    },
    db: db as never,
    dbUser: {
      id: "admin_1",
      systemRole: SystemRole.ADMIN,
      permissionOverrides: null,
    },
  });
}

describe("holiday calendar router", () => {
  it("requires admin access for holiday calendar catalog reads", async () => {
    const findMany = vi.fn();
    const findUnique = vi.fn();
    const findFirst = vi.fn();
    const caller = createProtectedCaller({
      holidayCalendar: {
        findMany,
        findUnique,
        findFirst,
      },
    });

    await expect(caller.listCalendars({ includeInactive: true })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Admin role required",
    });
    await expect(caller.listCalendarsDetail({ includeInactive: true })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Admin role required",
    });
    await expect(caller.getCalendarByIdentifier({ identifier: "Deutschland" })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Admin role required",
    });
    await expect(caller.getCalendarByIdentifierDetail({ identifier: "Deutschland" })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Admin role required",
    });
    await expect(caller.getCalendarById({ id: "cal_de" })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Admin role required",
    });

    expect(findMany).not.toHaveBeenCalled();
    expect(findUnique).not.toHaveBeenCalled();
    expect(findFirst).not.toHaveBeenCalled();
  });

  it("allows admins to read holiday calendar catalog routes", async () => {
    const listRows = [
      {
        id: "cal_de",
        name: "Deutschland",
        scopeType: "COUNTRY",
        stateCode: null,
        isActive: true,
        priority: 0,
        country: { id: "country_de", code: "DE", name: "Deutschland" },
        metroCity: null,
        _count: { entries: 1 },
        entries: [
          {
            id: "entry_1",
            date: new Date("2026-01-01T00:00:00.000Z"),
            name: "Neujahr",
            isRecurringAnnual: true,
            source: "builtin",
          },
        ],
      },
    ];
    const detailRow = {
      id: "cal_de",
      name: "Deutschland",
      scopeType: "COUNTRY",
      stateCode: null,
      isActive: true,
      priority: 0,
      country: { id: "country_de", code: "DE", name: "Deutschland" },
      metroCity: null,
      entries: [
        {
          id: "entry_1",
          date: new Date("2026-01-01T00:00:00.000Z"),
          name: "Neujahr",
          isRecurringAnnual: true,
          source: "builtin",
        },
      ],
    };
    const findMany = vi.fn().mockResolvedValue(listRows);
    const findUnique = vi
      .fn()
      .mockResolvedValueOnce(detailRow)
      .mockResolvedValueOnce(detailRow)
      .mockResolvedValueOnce(detailRow);
    const findFirst = vi.fn();
    const caller = createAdminCaller({
      holidayCalendar: {
        findMany,
        findUnique,
        findFirst,
      },
    });

    const listResult = await caller.listCalendars({ includeInactive: true });
    const detailResult = await caller.listCalendarsDetail({ includeInactive: true });
    const byIdentifierResult = await caller.getCalendarByIdentifier({ identifier: "cal_de" });
    const byIdentifierDetailResult = await caller.getCalendarByIdentifierDetail({ identifier: "cal_de" });
    const byIdResult = await caller.getCalendarById({ id: "cal_de" });

    expect(listResult).toEqual(listRows);
    expect(detailResult).toEqual({
      count: 1,
      calendars: [
        expect.objectContaining({
          id: "cal_de",
          entryCount: 1,
        }),
      ],
    });
    expect(byIdentifierResult).toEqual(detailRow);
    expect(byIdentifierDetailResult).toEqual(
      expect.objectContaining({
        id: "cal_de",
        entryCount: 1,
      }),
    );
    expect(byIdResult).toEqual(detailRow);
    expect(findMany).toHaveBeenCalledTimes(2);
    expect(findUnique).toHaveBeenCalledTimes(3);
    expect(findFirst).not.toHaveBeenCalled();
  });

  it("lists holiday calendars with assistant-facing detail formatting", async () => {
    const db = {
      holidayCalendar: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "cal_by",
            name: "Bayern Feiertage",
            scopeType: "STATE",
            stateCode: "BY",
            isActive: true,
            priority: 10,
            country: { id: "country_de", code: "DE", name: "Deutschland" },
            metroCity: null,
            _count: { entries: 2 },
            entries: [
              {
                id: "entry_1",
                date: new Date("2026-01-06T00:00:00.000Z"),
                name: "Heilige Drei Koenige",
                isRecurringAnnual: true,
                source: "state",
              },
            ],
          },
        ]),
      },
    };

    const caller = createAdminCaller(db);
    const result = await caller.listCalendarsDetail({
      countryCode: "DE",
      scopeType: "STATE",
      includeInactive: true,
    });

    expect(result).toEqual({
      count: 1,
      calendars: [
        expect.objectContaining({
          id: "cal_by",
          name: "Bayern Feiertage",
          scopeType: "STATE",
          stateCode: "BY",
          entryCount: 2,
          country: { id: "country_de", code: "DE", name: "Deutschland" },
          entries: [
            expect.objectContaining({
              id: "entry_1",
              date: "2026-01-06",
              name: "Heilige Drei Koenige",
              isRecurringAnnual: true,
              source: "state",
            }),
          ],
        }),
      ],
    });
  });

  it("resolves a holiday calendar by identifier with assistant-facing detail formatting", async () => {
    const db = {
      holidayCalendar: {
        findUnique: vi.fn().mockResolvedValue(null),
        findFirst: vi
          .fn()
          .mockResolvedValueOnce({
            id: "cal_augsburg",
            name: "Augsburg lokal",
            scopeType: "CITY",
            stateCode: null,
            isActive: true,
            priority: 5,
            country: { id: "country_de", code: "DE", name: "Deutschland" },
            metroCity: { id: "city_augsburg", name: "Augsburg" },
            entries: [
              {
                id: "entry_1",
                date: new Date("2026-08-08T00:00:00.000Z"),
                name: "Friedensfest lokal",
                isRecurringAnnual: true,
                source: "manual",
              },
            ],
          }),
      },
    };

    const caller = createAdminCaller(db);
    const result = await caller.getCalendarByIdentifierDetail({ identifier: "Augsburg lokal" });

    expect(result).toEqual(
      expect.objectContaining({
        id: "cal_augsburg",
        name: "Augsburg lokal",
        scopeType: "CITY",
        entryCount: 1,
        metroCity: { id: "city_augsburg", name: "Augsburg" },
        entries: [
          expect.objectContaining({
            id: "entry_1",
            date: "2026-08-08",
            name: "Friedensfest lokal",
          }),
        ],
      }),
    );
  });

  it("merges built-in and scoped custom holidays in preview", async () => {
    const db = {
      country: {
        findUnique: vi.fn().mockResolvedValue({ code: "DE" }),
      },
      metroCity: {
        findUnique: vi.fn().mockResolvedValue({ name: "Augsburg" }),
      },
      holidayCalendar: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "cal_city",
            name: "Augsburg lokal",
            scopeType: "CITY",
            priority: 10,
            createdAt: new Date("2026-01-01T00:00:00.000Z"),
            entries: [
              {
                date: new Date("2020-01-01T00:00:00.000Z"),
                name: "Augsburg Neujahr",
                isRecurringAnnual: true,
              },
              {
                date: new Date("2020-08-08T00:00:00.000Z"),
                name: "Friedensfest lokal",
                isRecurringAnnual: true,
              },
            ],
          },
        ]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.previewResolvedHolidays({
      countryId: "country_de",
      metroCityId: "city_augsburg",
      year: 2026,
    });

    expect(db.holidayCalendar.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          countryId: "country_de",
          isActive: true,
        }),
      }),
    );
    expect(result).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          date: "2026-01-01",
          name: "Augsburg Neujahr",
          scopeType: "CITY",
          calendarName: "Augsburg lokal",
        }),
        expect.objectContaining({
          date: "2026-08-08",
          name: "Friedensfest lokal",
          scopeType: "CITY",
          calendarName: "Augsburg lokal",
        }),
      ]),
    );
  });

  it("formats preview results for assistant consumption", async () => {
    const db = {
      country: {
        findUnique: vi.fn().mockResolvedValue({ id: "country_de", code: "DE", name: "Deutschland" }),
      },
      metroCity: {
        findUnique: vi.fn().mockResolvedValue({ id: "city_augsburg", name: "Augsburg", countryId: "country_de" }),
      },
      holidayCalendar: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "cal_city",
            name: "Augsburg lokal",
            scopeType: "CITY",
            priority: 10,
            createdAt: new Date("2026-01-01T00:00:00.000Z"),
            entries: [
              {
                id: "entry_1",
                date: new Date("2020-08-08T00:00:00.000Z"),
                name: "Friedensfest lokal",
                isRecurringAnnual: true,
                source: "manual",
              },
            ],
          },
        ]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.previewResolvedHolidaysDetail({
      countryId: "country_de",
      metroCityId: "city_augsburg",
      year: 2026,
    });

    expect(result.locationContext).toEqual({
      countryId: "country_de",
      countryCode: "DE",
      stateCode: null,
      metroCityId: "city_augsburg",
      metroCity: "Augsburg",
      year: 2026,
    });
    expect(result.summary.byScope).toEqual(
      expect.arrayContaining([expect.objectContaining({ scope: "CITY" })]),
    );
    expect(result.holidays).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          date: "2026-08-08",
          name: "Friedensfest lokal",
          scope: "CITY",
          calendarName: "Augsburg lokal",
          sourceType: "CUSTOM",
        }),
      ]),
    );
  });

  it("formats resolved holidays by region for assistant consumption", async () => {
    const db = {
      holidayCalendar: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.resolveHolidaysDetail({
      countryCode: "DE",
      stateCode: "BY",
      periodStart: new Date("2026-01-01T00:00:00.000Z"),
      periodEnd: new Date("2026-12-31T00:00:00.000Z"),
    });

    expect(result.locationContext).toEqual({
      countryId: null,
      countryCode: "DE",
      federalState: "BY",
      metroCityId: null,
      metroCity: null,
    });
    expect(result.count).toBeGreaterThan(0);
    expect(result.summary.byScope).toEqual(
      expect.arrayContaining([expect.objectContaining({ scope: "STATE" })]),
    );
    expect(result.holidays).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ name: "Heilige Drei Könige", date: "2026-01-06", scope: "STATE" }),
      ]),
    );
  });

  it("formats resolved holidays for a resource including local city holidays", async () => {
    const db = {
      resource: {
        findFirst: vi.fn().mockResolvedValue({ id: "res_1" }),
        findUnique: vi.fn().mockResolvedValue({
          id: "res_1",
          eid: "bruce.banner",
          displayName: "Bruce Banner",
          federalState: "BY",
          countryId: "country_de",
          metroCityId: "city_augsburg",
          country: { code: "DE", name: "Deutschland" },
          metroCity: { name: "Augsburg" },
        }),
      },
      holidayCalendar: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "cal_city",
            name: "Augsburg lokal",
            scopeType: "CITY",
            priority: 5,
            createdAt: new Date("2026-01-01T00:00:00.000Z"),
            entries: [
              {
                id: "entry_1",
                date: new Date("2020-08-08T00:00:00.000Z"),
                name: "Augsburger Friedensfest",
                isRecurringAnnual: true,
                source: "manual",
              },
            ],
          },
        ]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.resolveResourceHolidaysDetail({
      resourceId: "res_1",
      periodStart: new Date("2026-01-01T00:00:00.000Z"),
      periodEnd: new Date("2026-12-31T00:00:00.000Z"),
    });

    expect(result.resource).toEqual(
      expect.objectContaining({
        eid: "bruce.banner",
        federalState: "BY",
        metroCity: "Augsburg",
      }),
    );
    expect(result.summary.byScope).toEqual(
      expect.arrayContaining([expect.objectContaining({ scope: "CITY" })]),
    );
    expect(result.holidays).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          name: "Augsburger Friedensfest",
          date: "2026-08-08",
          scope: "CITY",
        }),
      ]),
    );
  });

  it("rejects duplicate calendar scopes on create", async () => {
    const db = {
      country: {
        findUnique: vi
          .fn()
          .mockResolvedValueOnce({ id: "country_de", name: "Deutschland" })
          .mockResolvedValueOnce({ id: "country_de", name: "Deutschland" }),
      },
      metroCity: {
        findUnique: vi.fn(),
      },
      holidayCalendar: {
        findFirst: vi.fn().mockResolvedValue({ id: "existing_scope" }),
        create: vi.fn(),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
    };

    const caller = createAdminCaller(db);

    await expect(caller.createCalendar({
      name: "Deutschland Standard",
      scopeType: "COUNTRY",
      countryId: "country_de",
      priority: 0,
      isActive: true,
    })).rejects.toThrow("A holiday calendar for this exact scope already exists");

    expect(db.holidayCalendar.create).not.toHaveBeenCalled();
  });

  it("rejects duplicate entry dates within the same calendar", async () => {
    const db = {
      holidayCalendar: {
        findUnique: vi.fn().mockResolvedValue({ id: "cal_1", name: "Deutschland Standard" }),
      },
      holidayCalendarEntry: {
        findFirst: vi.fn().mockResolvedValue({ id: "entry_existing" }),
        create: vi.fn(),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
    };

    const caller = createAdminCaller(db);

    await expect(caller.createEntry({
      holidayCalendarId: "cal_1",
      date: new Date("2026-12-24T00:00:00.000Z"),
      name: "Heiligabend lokal",
      isRecurringAnnual: true,
      source: "manual",
    })).rejects.toThrow("A holiday entry for this calendar and date already exists");

    expect(db.holidayCalendarEntry.create).not.toHaveBeenCalled();
  });
});
