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

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

function createContext(
  db: Record<string, unknown>,
  options: {
    role?: SystemRole;
    session?: boolean;
  } = {},
) {
  const { role = SystemRole.USER, session = true } = options;

  return {
    session: session
      ? {
          user: { email: "user@example.com", name: "User", image: null },
          expires: "2099-01-01T00:00:00.000Z",
        }
      : null,
    db: db as never,
    dbUser: session
      ? {
          id: role === SystemRole.ADMIN ? "user_admin" : role === SystemRole.MANAGER ? "user_mgr" : role === SystemRole.CONTROLLER ? "user_ctrl" : "user_1",
          systemRole: role,
          permissionOverrides: null,
        }
      : null,
  };
}

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

describe("entitlement router authorization", () => {
  it("requires authentication for vacation balance reads", async () => {
    const findUnique = vi.fn();
    const caller = createCaller(createContext({
      resource: {
        findUnique,
      },
    }, { session: false }));

    await expect(caller.getBalance({ resourceId: "res_1", year: 2026 })).rejects.toMatchObject({
      code: "UNAUTHORIZED",
      message: "Authentication required",
    });

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

  it("forbids regular users from reading another resource's balance", async () => {
    const systemSettingsFindUnique = vi.fn();
    const vacationEntitlementFindUnique = vi.fn();
    const caller = createCaller(createContext({
      resource: {
        findUnique: vi.fn().mockResolvedValue({ userId: "user_2" }),
      },
      systemSettings: {
        findUnique: systemSettingsFindUnique,
      },
      vacationEntitlement: {
        findUnique: vacationEntitlementFindUnique,
      },
    }));

    await expect(caller.getBalance({ resourceId: "res_2", year: 2026 })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "You can only view your own vacation balance",
    });

    expect(systemSettingsFindUnique).not.toHaveBeenCalled();
    expect(vacationEntitlementFindUnique).not.toHaveBeenCalled();
  });

  it("allows controllers to read broader balance detail without self-service ownership", async () => {
    const entitlement = sampleEntitlement({ pendingDays: 0 });
    const resourceFindUnique = vi.fn().mockImplementation(async ({ select }: { select?: Record<string, boolean | object> } = {}) => {
      if (select?.userId) {
        throw new Error("ownership check should not run for controller reads");
      }
      if (select?.displayName || select?.eid) {
        return {
          ...(select?.displayName ? { displayName: "Alice Example" } : {}),
          ...(select?.eid ? { eid: "EMP-002" } : {}),
        };
      }
      return {
        federalState: "BY",
        country: { code: "DE" },
        metroCity: null,
      };
    });
    const caller = createCaller(createContext({
      resource: {
        findUnique: resourceFindUnique,
      },
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue({ id: "singleton", vacationDefaultDays: 28 }),
      },
      vacationEntitlement: {
        findUnique: vi.fn().mockImplementation(async ({ where }: { where: { resourceId_year: { year: number } } }) => (
          where.resourceId_year.year === 2026 ? entitlement : null
        )),
        update: vi.fn().mockResolvedValue(entitlement),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    }, { role: SystemRole.CONTROLLER }));

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

    expect(result).toEqual({
      resource: "Alice Example",
      eid: "EMP-002",
      year: 2026,
      entitlement: 30,
      carryOver: 2,
      taken: 5,
      pending: 0,
      remaining: 25,
      sickDays: 0,
      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: [],
    });
    expect(resourceFindUnique).toHaveBeenCalledWith({
      where: { id: "res_2" },
      select: { displayName: true, eid: true },
    });
  });

  it("forbids regular users from reading entitlement year summaries", async () => {
    const findMany = vi.fn();
    const caller = createCaller(createContext({
      resource: {
        findMany,
      },
    }));

    await expect(caller.getYearSummary({ year: 2026 })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Manager or Admin role required",
    });

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

  it("forbids managers from bulk-setting entitlements", async () => {
    const findMany = vi.fn();
    const caller = createCaller(createContext({
      resource: {
        findMany,
      },
      vacationEntitlement: {
        upsert: vi.fn(),
      },
    }, { role: SystemRole.MANAGER }));

    await expect(caller.bulkSet({ year: 2026, entitledDays: 30 })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Admin role required",
    });

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