import { SystemRole } from "@capakraken/shared";
import { describe, expect, it, vi } from "vitest";
import { entitlementRouter } from "../router/entitlement.js";
import { createCallerFactory } from "../trpc.js";

// Mock @capakraken/db to provide the enums used in the router
vi.mock("@capakraken/db", () => ({
  VacationType: { ANNUAL: "ANNUAL", SICK: "SICK", OTHER: "OTHER", PUBLIC_HOLIDAY: "PUBLIC_HOLIDAY" },
  VacationStatus: { APPROVED: "APPROVED", PENDING: "PENDING", REJECTED: "REJECTED" },
}));

const createCaller = createCallerFactory(entitlementRouter);

// ── Caller factories ─────────────────────────────────────────────────────────

/** Injects a default resource ownership mock so the ownership check in getBalance passes */
function createProtectedCaller(db: Record<string, unknown>) {
  const withResourceOwnership = {
    resource: {
      findUnique: vi.fn().mockImplementation(async (args?: { select?: Record<string, unknown> }) => {
        const select = args?.select ?? {};
        return {
          ...(select.userId ? { userId: "user_1" } : {}),
          ...(select.federalState ? { federalState: "BY" } : {}),
          ...(select.country ? { country: { code: "DE" } } : {}),
          ...(select.metroCity ? { metroCity: null } : {}),
        };
      }),
    },
    ...db,
  };
  return createCaller({
    session: {
      user: { email: "user@example.com", name: "User", image: null },
      expires: "2099-01-01T00:00:00.000Z",
    },
    db: withResourceOwnership as never,
    dbUser: {
      id: "user_1",
      systemRole: SystemRole.USER,
      permissionOverrides: null,
    },
  });
}

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

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,
    },
  });
}

// ── Sample data ──────────────────────────────────────────────────────────────

function sampleEntitlement(overrides: Record<string, unknown> = {}) {
  return {
    id: "ent_1",
    resourceId: "res_1",
    year: 2026,
    entitledDays: 30,
    carryoverDays: 2,
    usedDays: 5,
    pendingDays: 3,
    createdAt: new Date(),
    updatedAt: new Date(),
    ...overrides,
  };
}

function mockEntitlementFindUniqueByYear(
  entitlementsByYear: Record<number, ReturnType<typeof sampleEntitlement> | null>,
) {
  return vi.fn().mockImplementation(async ({ where }: { where: { resourceId_year: { year: number } } }) => (
    entitlementsByYear[where.resourceId_year.year] ?? null
  ));
}

// ─── getBalance ──────────────────────────────────────────────────────────────

describe("entitlement.getBalance", () => {
  it("returns vacation balance for a resource and year", async () => {
    const entitlement = sampleEntitlement();
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
      },
      vacationEntitlement: {
        findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
        update: vi.fn().mockResolvedValue(entitlement),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getBalance({ resourceId: "res_1", year: 2026 });

    expect(result.year).toBe(2026);
    expect(result.resourceId).toBe("res_1");
    expect(result.entitledDays).toBe(30);
    expect(result.remainingDays).toBe(22); // 30 - 5 - 3
    expect(result).toHaveProperty("sickDays");
  });

  it("creates entitlement with carryover when none exists", async () => {
    const prevEntitlement = sampleEntitlement({
      id: "ent_prev",
      year: 2025,
      entitledDays: 28,
      usedDays: 20,
      pendingDays: 0,
    });
    const createdEntitlement = sampleEntitlement({
      year: 2026,
      entitledDays: 36, // 28 default + 8 carryover
      carryoverDays: 8,
      usedDays: 0,
      pendingDays: 0,
    });

    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
      },
      vacationEntitlement: {
        findUnique: mockEntitlementFindUniqueByYear({
          2025: prevEntitlement,
        }),
        create: vi.fn().mockResolvedValue(createdEntitlement),
        update: vi.fn().mockResolvedValue(createdEntitlement),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getBalance({ resourceId: "res_1", year: 2026 });

    expect(result.entitledDays).toBe(36);
    expect(result.carryoverDays).toBe(8);
    expect(db.vacationEntitlement.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          resourceId: "res_1",
          year: 2026,
          carryoverDays: 8,
        }),
      }),
    );
  });

  it("uses default of 28 days when no system settings exist", async () => {
    const entitlement = sampleEntitlement({ entitledDays: 28, carryoverDays: 0 });
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
      vacationEntitlement: {
        findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
        update: vi.fn().mockResolvedValue(entitlement),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getBalance({ resourceId: "res_1", year: 2026 });

    expect(result.entitledDays).toBe(28);
  });

  it("counts sick days separately", async () => {
    const entitlement = sampleEntitlement({ usedDays: 0, pendingDays: 0 });
    const vacationFindMany = vi.fn().mockImplementation(async ({ where }: { where?: { type?: unknown } } = {}) => {
      if (where?.type === "SICK") {
        return [
          {
            startDate: new Date("2026-03-10"),
            endDate: new Date("2026-03-12"),
            isHalfDay: false,
          },
        ];
      }
      return [];
    });
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
      vacationEntitlement: {
        findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
        update: vi.fn().mockResolvedValue(entitlement),
      },
      vacation: {
        findMany: vacationFindMany,
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getBalance({ resourceId: "res_1", year: 2026 });

    expect(result.sickDays).toBe(3);
  });

  it("does not deduct city-specific public holidays from leave balance", async () => {
    const entitlement = sampleEntitlement({ usedDays: 0, pendingDays: 0, entitledDays: 30, carryoverDays: 0 });
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          userId: "user_1",
          federalState: "BY",
          country: { code: "DE" },
          metroCity: { name: "Augsburg" },
        }),
      },
      vacationEntitlement: {
        findUnique: mockEntitlementFindUniqueByYear({ 2028: entitlement }),
        update: vi.fn().mockImplementation(async ({ data }) => ({
          ...entitlement,
          ...data,
        })),
      },
      vacation: {
        findMany: vi
          .fn()
          .mockResolvedValueOnce([])
          .mockResolvedValueOnce([
            {
              startDate: new Date("2028-08-08T00:00:00.000Z"),
              endDate: new Date("2028-08-08T00:00:00.000Z"),
              status: "APPROVED",
              isHalfDay: false,
            },
          ])
          .mockResolvedValueOnce([])
          .mockResolvedValueOnce([]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getBalance({ resourceId: "res_1", year: 2028 });

    expect(result.usedDays).toBe(0);
    expect(result.remainingDays).toBe(30);
  });

  it("recomputes carryover from the previous year when the next year already exists", async () => {
    const entitlements = new Map([
      [2025, sampleEntitlement({
        id: "ent_2025",
        year: 2025,
        entitledDays: 28,
        carryoverDays: 0,
        usedDays: 8,
        pendingDays: 0,
      })],
      [2026, sampleEntitlement({
        id: "ent_2026",
        year: 2026,
        entitledDays: 28,
        carryoverDays: 0,
        usedDays: 0,
        pendingDays: 0,
      })],
    ]);
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
      },
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          userId: "user_1",
          federalState: "BY",
          countryId: "country_de",
          metroCityId: null,
          country: { code: "DE" },
          metroCity: null,
        }),
      },
      holidayCalendar: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      vacationEntitlement: {
        findUnique: vi.fn().mockImplementation(async ({ where }: { where: { resourceId_year: { year: number } } }) => (
          entitlements.get(where.resourceId_year.year) ?? null
        )),
        create: vi.fn(),
        update: vi.fn().mockImplementation(async ({ where, data }: {
          where: { id: string };
          data: Record<string, number>;
        }) => {
          const current = [...entitlements.values()].find((entry) => entry.id === where.id);
          if (!current) {
            throw new Error(`Unknown entitlement ${where.id}`);
          }
          const updated = { ...current, ...data };
          entitlements.set(updated.year, updated);
          return updated;
        }),
      },
      vacation: {
        findMany: vi.fn().mockImplementation(async ({ where }: {
          where?: { type?: unknown; startDate?: { lte?: Date }; endDate?: { gte?: Date } };
        } = {}) => {
          if (where?.type === "SICK" || where?.type === "PUBLIC_HOLIDAY") {
            return [];
          }

          if (where?.startDate?.lte?.toISOString().startsWith("2025-12-31")) {
            return [
              {
                startDate: new Date("2025-06-10T00:00:00.000Z"),
                endDate: new Date("2025-06-17T00:00:00.000Z"),
                status: "APPROVED",
                isHalfDay: false,
              },
            ];
          }

          return [];
        }),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getBalance({ resourceId: "res_1", year: 2026 });

    expect(result.carryoverDays).toBe(20);
    expect(result.entitledDays).toBe(48);
    expect(db.vacationEntitlement.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: "ent_2026" },
        data: expect.objectContaining({
          carryoverDays: 20,
          entitledDays: 48,
        }),
      }),
    );
  });

  it("deducts only the non-holiday days from mixed vacation ranges", async () => {
    const entitlement = sampleEntitlement({ usedDays: 0, pendingDays: 0, entitledDays: 30, carryoverDays: 0 });
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          userId: "user_1",
          countryId: "country_de",
          metroCityId: "city_muc",
          federalState: "BY",
          country: { code: "DE" },
          metroCity: { name: "Muenchen" },
        }),
      },
      holidayCalendar: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "cal_muc",
            name: "Muenchen lokal",
            scopeType: "CITY",
            priority: 10,
            createdAt: new Date("2026-01-01T00:00:00.000Z"),
            entries: [
              {
                date: new Date("2020-11-15T00:00:00.000Z"),
                name: "Lokaler Stadtfeiertag",
                isRecurringAnnual: true,
              },
            ],
          },
        ]),
      },
      vacationEntitlement: {
        findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
        update: vi.fn().mockImplementation(async ({ data }) => ({
          ...entitlement,
          ...data,
        })),
      },
      vacation: {
        findMany: vi.fn().mockImplementation(async ({ where }: { where?: { type?: unknown } } = {}) => {
          if (where?.type === "PUBLIC_HOLIDAY" || where?.type === "SICK") {
            return [];
          }
          return [
            {
              startDate: new Date("2026-11-14T00:00:00.000Z"),
              endDate: new Date("2026-11-16T00:00:00.000Z"),
              status: "APPROVED",
              isHalfDay: false,
            },
          ];
        }),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getBalance({ resourceId: "res_1", year: 2026 });

    expect(result.usedDays).toBe(2);
    expect(result.remainingDays).toBe(28);
  });

  it("prefers persisted holiday snapshots over the current holiday context", async () => {
    const entitlement = sampleEntitlement({ usedDays: 0, pendingDays: 0, entitledDays: 30, carryoverDays: 0 });
    const resourceFindUnique = vi.fn().mockImplementation(async ({ select }: { select?: Record<string, unknown> } = {}) => ({
      ...(select?.userId ? { userId: "user_1" } : {}),
      ...(select?.federalState ? { federalState: "BY" } : {}),
      ...(select?.country ? { country: { code: "DE" } } : {}),
      ...(select?.metroCity ? { metroCity: { name: "Augsburg" } } : {}),
    }));
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
      resource: {
        findUnique: resourceFindUnique,
      },
      holidayCalendar: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "cal_augsburg",
            name: "Augsburg lokal",
            scopeType: "CITY",
            priority: 10,
            createdAt: new Date("2026-01-01T00:00:00.000Z"),
            entries: [
              {
                date: new Date("2020-08-08T00:00:00.000Z"),
                name: "Hohes Friedensfest",
                isRecurringAnnual: true,
              },
            ],
          },
        ]),
      },
      vacationEntitlement: {
        findUnique: mockEntitlementFindUniqueByYear({ 2028: entitlement }),
        update: vi.fn().mockImplementation(async ({ data }) => ({
          ...entitlement,
          ...data,
        })),
      },
      vacation: {
        findMany: vi
          .fn()
          .mockResolvedValueOnce([
            {
              startDate: new Date("2028-08-08T00:00:00.000Z"),
              endDate: new Date("2028-08-08T00:00:00.000Z"),
              status: "APPROVED",
              isHalfDay: false,
              deductedDays: 1,
              holidayCountryCode: "DE",
              holidayFederalState: "BY",
              holidayMetroCityName: "Augsburg",
              holidayCalendarDates: [],
              holidayLegacyPublicHolidayDates: [],
            },
          ])
          .mockResolvedValueOnce([]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getBalance({ resourceId: "res_1", year: 2028 });

    expect(result.usedDays).toBe(1);
    expect(result.remainingDays).toBe(29);
    expect(db.holidayCalendar.findMany).not.toHaveBeenCalled();
  });

  it("falls back to the current holiday context for legacy vacations without snapshots", async () => {
    const entitlement = sampleEntitlement({ usedDays: 0, pendingDays: 0, entitledDays: 30, carryoverDays: 0 });
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          userId: "user_1",
          countryId: "country_de",
          metroCityId: "city_aug",
          federalState: "BY",
          country: { code: "DE" },
          metroCity: { name: "Augsburg" },
        }),
      },
      holidayCalendar: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "cal_augsburg",
            name: "Augsburg lokal",
            scopeType: "CITY",
            priority: 10,
            createdAt: new Date("2026-01-01T00:00:00.000Z"),
            entries: [
              {
                date: new Date("2020-08-08T00:00:00.000Z"),
                name: "Hohes Friedensfest",
                isRecurringAnnual: true,
              },
            ],
          },
        ]),
      },
      vacationEntitlement: {
        findUnique: mockEntitlementFindUniqueByYear({ 2028: entitlement }),
        update: vi.fn().mockImplementation(async ({ data }) => ({
          ...entitlement,
          ...data,
        })),
      },
      vacation: {
        findMany: vi
          .fn()
          .mockResolvedValueOnce([
            {
              startDate: new Date("2028-08-08T00:00:00.000Z"),
              endDate: new Date("2028-08-08T00:00:00.000Z"),
              status: "APPROVED",
              isHalfDay: false,
              deductedDays: null,
              holidayCountryCode: null,
              holidayFederalState: null,
              holidayMetroCityName: null,
              holidayCalendarDates: null,
              holidayLegacyPublicHolidayDates: null,
            },
          ])
          .mockResolvedValueOnce([])
          .mockResolvedValueOnce([]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getBalance({ resourceId: "res_1", year: 2028 });

    expect(result.usedDays).toBe(0);
    expect(result.remainingDays).toBe(30);
    expect(db.holidayCalendar.findMany).toHaveBeenCalled();
  });
});

describe("entitlement.getBalanceDetail", () => {
  it("returns assistant-friendly balance detail from the canonical balance workflow", async () => {
    const entitlement = sampleEntitlement({ carryoverDays: 0, usedDays: 1, pendingDays: 0.5 });
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
      },
      resource: {
        findUnique: vi.fn().mockImplementation(async ({ select }: { select?: Record<string, unknown> } = {}) => ({
          ...(select?.userId ? { userId: "user_1" } : {}),
          ...(select?.federalState ? { federalState: "BY" } : {}),
          ...(select?.country ? { country: { code: "DE" } } : {}),
          ...(select?.metroCity ? { metroCity: null } : {}),
          ...(select?.displayName ? { displayName: "Alice Example" } : {}),
          ...(select?.eid ? { eid: "EMP-001" } : {}),
        })),
      },
      vacationEntitlement: {
        findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
        update: vi.fn().mockResolvedValue(entitlement),
      },
      vacation: {
        findMany: vi.fn().mockImplementation(async ({ where }: { where?: { type?: string } } = {}) => {
          if (where?.type === "SICK") {
            return [{ startDate: new Date("2026-02-01"), endDate: new Date("2026-02-01"), isHalfDay: false }];
          }
          return [];
        }),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getBalanceDetail({ resourceId: "res_1", year: 2026 });

    expect(result).toEqual({
      resource: "Alice Example",
      eid: "EMP-001",
      year: 2026,
      entitlement: 30,
      carryOver: 0,
      taken: 1,
      pending: 0.5,
      remaining: 28.5,
      sickDays: 1,
      deductionSummary: {
        formula: "remaining = entitlement - taken - pending",
        approvedVacationCount: 0,
        pendingVacationCount: 0,
        approvedRequestedDays: 0,
        pendingRequestedDays: 0,
        approvedDeductedDays: 0,
        pendingDeductedDays: 0,
        excludedHolidayDates: [],
        holidayBasisVariants: [],
        sources: {
          hasCalendarHolidays: false,
          hasLegacyPublicHolidayEntries: false,
        },
      },
      vacations: [],
    });
  });

  it("includes holiday-adjusted vacation breakdown for explainability", async () => {
    const entitlement = sampleEntitlement({ carryoverDays: 1, usedDays: 1, pendingDays: 0 });
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
      },
      resource: {
        findUnique: vi.fn().mockImplementation(async ({ select }: { select?: Record<string, unknown> } = {}) => ({
          ...(select?.userId ? { userId: "user_1" } : {}),
          ...(select?.displayName ? { displayName: "Alice Example" } : {}),
          ...(select?.eid ? { eid: "EMP-001" } : {}),
        })),
      },
      vacationEntitlement: {
        findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
        update: vi.fn().mockResolvedValue(entitlement),
      },
      vacation: {
        findMany: vi.fn().mockImplementation(async ({ where }: { where?: { type?: string | { in?: string[] } } } = {}) => {
          if (where?.type === "SICK") {
            return [];
          }
          if (typeof where?.type === "object" && Array.isArray(where.type.in)) {
            return [
              {
                type: "ANNUAL",
                startDate: new Date("2026-05-01T00:00:00.000Z"),
                endDate: new Date("2026-05-02T00:00:00.000Z"),
                status: "APPROVED",
                isHalfDay: false,
                deductedDays: 1,
                holidayCountryCode: "DE",
                holidayCountryName: "Germany",
                holidayFederalState: "BY",
                holidayMetroCityName: null,
                holidayCalendarDates: ["2026-05-01"],
                holidayLegacyPublicHolidayDates: [],
              },
            ];
          }
          return [];
        }),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getBalanceDetail({ resourceId: "res_1", year: 2026 });

    expect(result.deductionSummary).toEqual({
      formula: "remaining = entitlement - taken - pending",
      approvedVacationCount: 1,
      pendingVacationCount: 0,
      approvedRequestedDays: 2,
      pendingRequestedDays: 0,
      approvedDeductedDays: 1,
      pendingDeductedDays: 0,
      excludedHolidayDates: ["2026-05-01"],
      holidayBasisVariants: ["Germany / BY"],
      sources: {
        hasCalendarHolidays: true,
        hasLegacyPublicHolidayEntries: false,
      },
    });
    expect(result.vacations).toEqual([
      {
        type: "ANNUAL",
        status: "APPROVED",
        startDate: "2026-05-01",
        endDate: "2026-05-02",
        isHalfDay: false,
        requestedDays: 2,
        deductedDays: 1,
        holidayCountryCode: "DE",
        holidayCountryName: "Germany",
        holidayFederalState: "BY",
        holidayMetroCityName: null,
        holidayCalendarDates: ["2026-05-01"],
        holidayLegacyPublicHolidayDates: [],
        holidayDetails: [
          { date: "2026-05-01", source: "CALENDAR" },
        ],
        holidayContext: {
          countryCode: "DE",
          countryName: "Germany",
          federalState: "BY",
          metroCityName: null,
          sources: {
            hasCalendarHolidays: true,
            hasLegacyPublicHolidayEntries: false,
          },
        },
      },
    ]);
  });
});

// ─── get ─────────────────────────────────────────────────────────────────────

describe("entitlement.get", () => {
  it("returns existing entitlement (manager role)", async () => {
    const entitlement = sampleEntitlement({
      entitledDays: 30,
      carryoverDays: 0,
      usedDays: 0,
      pendingDays: 0,
    });
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 30 }),
      },
      vacationEntitlement: {
        findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
        update: vi.fn().mockImplementation(async ({ data }: { data: Record<string, number> }) => ({
          ...entitlement,
          ...data,
        })),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createManagerCaller(db);
    const result = await caller.get({ resourceId: "res_1", year: 2026 });

    expect(result.id).toBe("ent_1");
    expect(result.entitledDays).toBe(30);
  });

  it("rejects access by a regular user (FORBIDDEN)", async () => {
    const db = {
      systemSettings: {
        findUnique: vi.fn(),
      },
      vacationEntitlement: {
        findUnique: vi.fn(),
      },
    };

    const caller = createProtectedCaller(db);
    await expect(caller.get({ resourceId: "res_1", year: 2026 })).rejects.toThrow();
  });
});

// ─── set ─────────────────────────────────────────────────────────────────────

describe("entitlement.set", () => {
  it("updates existing entitlement", async () => {
    const existing = sampleEntitlement();
    const updated = { ...existing, entitledDays: 35 };
    const db = {
      vacationEntitlement: {
        findUnique: vi.fn().mockResolvedValue(existing),
        update: vi.fn().mockResolvedValue(updated),
        create: vi.fn(),
      },
      auditLog: { create: vi.fn().mockResolvedValue({}) },
    };

    const caller = createManagerCaller(db);
    const result = await caller.set({
      resourceId: "res_1",
      year: 2026,
      entitledDays: 35,
    });

    expect(result.entitledDays).toBe(35);
    expect(db.vacationEntitlement.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: "ent_1" },
        data: { entitledDays: 35 },
      }),
    );
    expect(db.vacationEntitlement.create).not.toHaveBeenCalled();
  });

  it("creates new entitlement when none exists", async () => {
    const created = sampleEntitlement({ entitledDays: 30, carryoverDays: 0 });
    const db = {
      vacationEntitlement: {
        findUnique: vi.fn().mockResolvedValue(null),
        update: vi.fn(),
        create: vi.fn().mockResolvedValue(created),
      },
      auditLog: { create: vi.fn().mockResolvedValue({}) },
    };

    const caller = createManagerCaller(db);
    const result = await caller.set({
      resourceId: "res_1",
      year: 2026,
      entitledDays: 30,
    });

    expect(result.entitledDays).toBe(30);
    expect(db.vacationEntitlement.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          resourceId: "res_1",
          year: 2026,
          entitledDays: 30,
          carryoverDays: 0,
          usedDays: 0,
          pendingDays: 0,
        }),
      }),
    );
    expect(db.vacationEntitlement.update).not.toHaveBeenCalled();
  });
});

// ─── bulkSet ─────────────────────────────────────────────────────────────────

describe("entitlement.bulkSet", () => {
  it("upserts entitlements for all active resources (admin role)", async () => {
    const resources = [{ id: "res_1" }, { id: "res_2" }, { id: "res_3" }];
    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue(resources),
      },
      vacationEntitlement: {
        upsert: vi.fn().mockResolvedValue(sampleEntitlement()),
      },
      auditLog: { create: vi.fn().mockResolvedValue({}) },
    };

    const caller = createAdminCaller(db);
    const result = await caller.bulkSet({
      year: 2026,
      entitledDays: 30,
    });

    expect(result.updated).toBe(3);
    expect(db.vacationEntitlement.upsert).toHaveBeenCalledTimes(3);
    expect(db.resource.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({ isActive: true }),
      }),
    );
  });

  it("filters by resourceIds when provided", async () => {
    const resources = [{ id: "res_1" }];
    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue(resources),
      },
      vacationEntitlement: {
        upsert: vi.fn().mockResolvedValue(sampleEntitlement()),
      },
      auditLog: { create: vi.fn().mockResolvedValue({}) },
    };

    const caller = createAdminCaller(db);
    await caller.bulkSet({
      year: 2026,
      entitledDays: 30,
      resourceIds: ["res_1"],
    });

    expect(db.resource.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          isActive: true,
          id: { in: ["res_1"] },
        }),
      }),
    );
  });

  it("rejects bulk set by a manager (admin only)", async () => {
    const db = {
      resource: { findMany: vi.fn() },
      vacationEntitlement: { upsert: vi.fn() },
    };

    const caller = createManagerCaller(db);
    await expect(
      caller.bulkSet({ year: 2026, entitledDays: 30 }),
    ).rejects.toThrow();
  });
});

// ─── getYearSummary ──────────────────────────────────────────────────────────

describe("entitlement.getYearSummary", () => {
  it("returns summary for all active resources (manager role)", async () => {
    const resources = [
      {
        id: "res_1",
        displayName: "Alice",
        eid: "alice",
        chapter: "VFX",
        federalState: "BY",
        country: { code: "DE", name: "Germany" },
        metroCity: { name: "Muenchen" },
      },
      {
        id: "res_2",
        displayName: "Bob",
        eid: "bob",
        chapter: "Animation",
        federalState: "HH",
        country: { code: "DE", name: "Germany" },
        metroCity: { name: "Hamburg" },
      },
    ];
    const entitlement = sampleEntitlement({ usedDays: 5, pendingDays: 2 });
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
      },
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          federalState: "BY",
          country: { code: "DE" },
          metroCity: null,
        }),
        findMany: vi.fn().mockResolvedValue(resources),
      },
      vacationEntitlement: {
        findUnique: mockEntitlementFindUniqueByYear({ 2026: entitlement }),
        update: vi.fn().mockResolvedValue(entitlement),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createManagerCaller(db);
    const result = await caller.getYearSummary({ year: 2026 });

    expect(result).toHaveLength(2);
    expect(result[0]).toHaveProperty("resourceId");
    expect(result[0]).toHaveProperty("displayName");
    expect(result[0]).toHaveProperty("remainingDays");
    expect(result[0]).toMatchObject({
      countryCode: "DE",
      countryName: "Germany",
      federalState: "BY",
      metroCityName: "Muenchen",
    });
  });

  it("filters by chapter when provided", async () => {
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
      resource: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      vacationEntitlement: {
        findUnique: vi.fn(),
        update: vi.fn(),
      },
      vacation: {
        findMany: vi.fn(),
      },
    };

    const caller = createManagerCaller(db);
    await caller.getYearSummary({ year: 2026, chapter: "VFX" });

    expect(db.resource.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          isActive: true,
          chapter: "VFX",
        }),
      }),
    );
  });
});

describe("entitlement.getYearSummaryDetail", () => {
  it("returns assistant-friendly year summary detail from the canonical summary workflow", async () => {
    const resources = [
      {
        id: "res_1",
        displayName: "Alice Example",
        eid: "EMP-001",
        chapter: "Delivery",
        federalState: "BY",
        country: { code: "DE", name: "Germany" },
        metroCity: { name: "Muenchen" },
      },
      {
        id: "res_2",
        displayName: "Bob Example",
        eid: "EMP-002",
        chapter: "CGI",
        federalState: "HH",
        country: { code: "DE", name: "Germany" },
        metroCity: { name: "Hamburg" },
      },
    ];
    const db = {
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
      },
      resource: {
        findMany: vi.fn().mockResolvedValue(resources),
        findUnique: vi.fn().mockResolvedValue({
          federalState: "BY",
          country: { code: "DE" },
          metroCity: null,
        }),
      },
      vacationEntitlement: {
        findUnique: vi.fn().mockImplementation(async ({ where }: { where: { resourceId_year: { resourceId: string; year: number } } }) => {
          if (where.resourceId_year.year !== 2026) {
            return null;
          }
          return sampleEntitlement({
            id: `ent_${where.resourceId_year.resourceId}`,
            resourceId: where.resourceId_year.resourceId,
            year: 2026,
            entitledDays: 28,
            carryoverDays: 0,
            usedDays: 0,
            pendingDays: 0,
          });
        }),
        create: vi.fn(),
        update: vi.fn().mockImplementation(async (args?: { data?: Record<string, unknown>; where?: { id?: string } }) => ({
          ...sampleEntitlement({ entitledDays: 28, carryoverDays: 0, usedDays: 0, pendingDays: 0 }),
          id: args?.where?.id ?? "ent_updated",
          ...(args?.data ?? {}),
        })),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createManagerCaller(db);
    const result = await caller.getYearSummaryDetail({ year: 2026, resourceName: "alice" });

    expect(result).toEqual([
      {
        resource: "Alice Example",
        eid: "EMP-001",
        chapter: "Delivery",
        countryCode: "DE",
        countryName: "Germany",
        federalState: "BY",
        metroCityName: "Muenchen",
        year: 2026,
        entitled: 28,
        carryover: 0,
        used: 0,
        pending: 0,
        remaining: 28,
      },
    ]);
  });
});
