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

// Pass-through so the db mock reaches the procedures unchanged
vi.mock("../router/holiday-calendar-shared.js", () => ({
  asHolidayCalendarDb: (db: unknown) => db,
}));

// Provide the same shape the real support module exports
vi.mock("../router/holiday-calendar-support.js", () => ({
  holidayCalendarListInclude: {
    country: { select: { id: true, code: true, name: true } },
    metroCity: { select: { id: true, name: true } },
    entries: { orderBy: [{ date: "asc" }, { name: "asc" }] },
    _count: { select: { entries: true } },
  },
  holidayCalendarDetailInclude: {
    country: { select: { id: true, code: true, name: true } },
    metroCity: { select: { id: true, name: true } },
    entries: { orderBy: [{ date: "asc" }, { name: "asc" }] },
  },
}));

const router = createTRPCRouter(holidayCalendarCatalogReadProcedures);
const createCaller = createCallerFactory(router);

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

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

const mockCalendar = {
  id: "cal_1",
  name: "German Federal Holidays",
  scopeType: "COUNTRY",
  stateCode: null,
  isActive: true,
  priority: 0,
  country: { id: "country_de", code: "DE", name: "Germany" },
  metroCity: null,
  _count: { entries: 12 },
  entries: [
    {
      id: "entry_1",
      date: new Date("2026-01-01"),
      name: "New Year",
      isRecurringAnnual: true,
      source: null,
    },
  ],
};

// ─── listCalendars ────────────────────────────────────────────────────────────

describe("listCalendars", () => {
  it("returns all active calendars when called with no input", async () => {
    const findMany = vi.fn().mockResolvedValue([mockCalendar]);
    const caller = createAdminCaller({ holidayCalendar: { findMany } });

    const result = await caller.listCalendars();

    expect(result).toEqual([mockCalendar]);
    expect(findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ isActive: true }),
      }),
    );
  });

  it("applies countryCode filter uppercased with case-insensitive mode", async () => {
    const findMany = vi.fn().mockResolvedValue([mockCalendar]);
    const caller = createAdminCaller({ holidayCalendar: { findMany } });

    await caller.listCalendars({ countryCode: "de" });

    expect(findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          country: { code: { equals: "DE", mode: "insensitive" } },
        }),
      }),
    );
  });

  it("includes inactive calendars when includeInactive is true", async () => {
    const inactiveCalendar = { ...mockCalendar, id: "cal_inactive", isActive: false };
    const findMany = vi.fn().mockResolvedValue([mockCalendar, inactiveCalendar]);
    const caller = createAdminCaller({ holidayCalendar: { findMany } });

    const result = await caller.listCalendars({ includeInactive: true });

    expect(result).toHaveLength(2);
    // isActive filter must NOT be present in the where clause
    const whereArg = findMany.mock.calls[0][0].where;
    expect(whereArg).not.toHaveProperty("isActive");
  });
});

// ─── listCalendarsDetail ──────────────────────────────────────────────────────

describe("listCalendarsDetail", () => {
  it("returns count and formatted calendar array", async () => {
    const findMany = vi.fn().mockResolvedValue([mockCalendar]);
    const caller = createAdminCaller({ holidayCalendar: { findMany } });

    const result = await caller.listCalendarsDetail();

    expect(result.count).toBe(1);
    expect(result.calendars).toHaveLength(1);
    expect(result.calendars[0]).toMatchObject({
      id: "cal_1",
      name: "German Federal Holidays",
      scopeType: "COUNTRY",
      stateCode: null,
      isActive: true,
      priority: 0,
      country: { id: "country_de", code: "DE", name: "Germany" },
      metroCity: null,
      entryCount: 12,
    });
  });

  it("formats entry dates as ISO date strings (YYYY-MM-DD)", async () => {
    const findMany = vi.fn().mockResolvedValue([mockCalendar]);
    const caller = createAdminCaller({ holidayCalendar: { findMany } });

    const result = await caller.listCalendarsDetail();

    expect(result.calendars[0].entries[0]).toMatchObject({
      id: "entry_1",
      date: "2026-01-01",
      name: "New Year",
      isRecurringAnnual: true,
      source: null,
    });
  });
});

// ─── getCalendarByIdentifier ──────────────────────────────────────────────────

describe("getCalendarByIdentifier", () => {
  it("finds a calendar by exact id on the first lookup", async () => {
    const findUnique = vi.fn().mockResolvedValue(mockCalendar);
    const findFirst = vi.fn();
    const caller = createAdminCaller({ holidayCalendar: { findUnique, findFirst } });

    const result = await caller.getCalendarByIdentifier({ identifier: "cal_1" });

    expect(result).toEqual(mockCalendar);
    expect(findUnique).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "cal_1" } }));
    expect(findFirst).not.toHaveBeenCalled();
  });

  it("falls back to exact name match when id lookup returns null", async () => {
    const findUnique = vi.fn().mockResolvedValue(null);
    const findFirst = vi.fn().mockResolvedValueOnce(mockCalendar);
    const caller = createAdminCaller({ holidayCalendar: { findUnique, findFirst } });

    const result = await caller.getCalendarByIdentifier({ identifier: "German Federal Holidays" });

    expect(result).toEqual(mockCalendar);
    expect(findFirst).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { name: { equals: "German Federal Holidays", mode: "insensitive" } },
      }),
    );
  });

  it("falls back to contains name match when exact name lookup returns null", async () => {
    const findUnique = vi.fn().mockResolvedValue(null);
    const findFirst = vi
      .fn()
      .mockResolvedValueOnce(null) // exact name miss
      .mockResolvedValueOnce(mockCalendar); // contains name hit
    const caller = createAdminCaller({ holidayCalendar: { findUnique, findFirst } });

    const result = await caller.getCalendarByIdentifier({ identifier: "Federal" });

    expect(result).toEqual(mockCalendar);
    expect(findFirst).toHaveBeenCalledTimes(2);
    expect(findFirst).toHaveBeenLastCalledWith(
      expect.objectContaining({
        where: { name: { contains: "Federal", mode: "insensitive" } },
      }),
    );
  });

  it("throws NOT_FOUND when no match is found at all", async () => {
    const findUnique = vi.fn().mockResolvedValue(null);
    const findFirst = vi.fn().mockResolvedValue(null);
    const caller = createAdminCaller({ holidayCalendar: { findUnique, findFirst } });

    await expect(
      caller.getCalendarByIdentifier({ identifier: "does-not-exist" }),
    ).rejects.toMatchObject({
      code: "NOT_FOUND",
      message: "Holiday calendar not found: does-not-exist",
    });
  });
});

// ─── getCalendarByIdentifierDetail ────────────────────────────────────────────

describe("getCalendarByIdentifierDetail", () => {
  it("returns formatted detail for a matching calendar", async () => {
    const findUnique = vi.fn().mockResolvedValue(mockCalendar);
    const caller = createAdminCaller({ holidayCalendar: { findUnique, findFirst: vi.fn() } });

    const result = await caller.getCalendarByIdentifierDetail({ identifier: "cal_1" });

    expect(result).toMatchObject({
      id: "cal_1",
      name: "German Federal Holidays",
      scopeType: "COUNTRY",
      isActive: true,
      entryCount: 12,
      entries: [expect.objectContaining({ date: "2026-01-01" })],
    });
  });

  it("throws NOT_FOUND when identifier does not match any calendar", async () => {
    const findUnique = vi.fn().mockResolvedValue(null);
    const findFirst = vi.fn().mockResolvedValue(null);
    const caller = createAdminCaller({ holidayCalendar: { findUnique, findFirst } });

    await expect(
      caller.getCalendarByIdentifierDetail({ identifier: "no-such-calendar" }),
    ).rejects.toMatchObject({ code: "NOT_FOUND" });
  });
});

// ─── getCalendarById ──────────────────────────────────────────────────────────

describe("getCalendarById", () => {
  it("returns the calendar when found by id", async () => {
    const findUnique = vi.fn().mockResolvedValue(mockCalendar);
    const caller = createAdminCaller({ holidayCalendar: { findUnique } });

    const result = await caller.getCalendarById({ id: "cal_1" });

    expect(result).toEqual(mockCalendar);
    expect(findUnique).toHaveBeenCalledWith(expect.objectContaining({ where: { id: "cal_1" } }));
  });

  it("throws NOT_FOUND when no calendar exists with the given id", async () => {
    const findUnique = vi.fn().mockResolvedValue(null);
    const caller = createAdminCaller({ holidayCalendar: { findUnique } });

    await expect(caller.getCalendarById({ id: "cal_missing" })).rejects.toMatchObject({
      code: "NOT_FOUND",
      message: "Holiday calendar not found",
    });
  });
});

// ─── Authorization ────────────────────────────────────────────────────────────

describe("Authorization", () => {
  it("rejects a non-admin user (SystemRole.USER) with FORBIDDEN on all procedures", async () => {
    const findMany = vi.fn();
    const findUnique = vi.fn();
    const findFirst = vi.fn();
    const caller = createUserCaller({ holidayCalendar: { findMany, findUnique, findFirst } });

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

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