import { listAssignmentBookings } from "@capakraken/application";
import { PermissionKey, SystemRole } from "@capakraken/shared";
import { describe, expect, it, vi } from "vitest";
import { staffingRouter } from "../router/staffing.js";
import { createCallerFactory } from "../trpc.js";

// Mock the pure-logic packages — we focus on the router/DB layer
vi.mock("@capakraken/staffing", () => ({
  rankResources: vi.fn().mockImplementation((input: { resources: { id: string }[] }) =>
    input.resources.map((r: { id: string }, i: number) => ({
      resourceId: r.id,
      score: 80 - i * 10,
      breakdown: {
        skillScore: 70,
        availabilityScore: 90,
        costScore: 80,
        utilizationScore: 75,
      },
    })),
  ),
}));

vi.mock("@capakraken/application", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@capakraken/application")>();
  return {
    ...actual,
    listAssignmentBookings: vi.fn().mockResolvedValue([]),
  };
});

const createCaller = createCallerFactory(staffingRouter);

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

function createProtectedCaller(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: {
        granted: [PermissionKey.VIEW_PLANNING, PermissionKey.VIEW_COSTS],
      },
    },
  });
}

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

function createProtectedCallerWithOverrides(
  db: Record<string, unknown>,
  overrides: { granted?: PermissionKey[]; denied?: PermissionKey[] } | null,
) {
  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: overrides,
    },
  });
}

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

function sampleResource(overrides: Record<string, unknown> = {}) {
  return {
    id: "res_1",
    displayName: "Alice",
    eid: "alice",
    lcrCents: 7500,
    chargeabilityTarget: 80,
    availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
    skills: [
      { skill: "Compositing", proficiency: 4, isMainSkill: true },
      { skill: "Nuke", proficiency: 3, isMainSkill: false, category: "Software" },
    ],
    isActive: true,
    valueScore: 85,
    chapter: "VFX",
    countryId: "country_de",
    federalState: "BY",
    metroCityId: null,
    country: { code: "DE" },
    metroCity: null,
    ...overrides,
  };
}

describe("staffing router authorization", () => {
  const planningWindow = {
    resourceId: "res_1",
    startDate: new Date("2026-04-01T00:00:00.000Z"),
    endDate: new Date("2026-04-02T00:00:00.000Z"),
  };
  const projectRankingWindow = {
    projectId: "project_1",
    startDate: new Date("2026-04-01T00:00:00.000Z"),
    endDate: new Date("2026-04-02T00:00:00.000Z"),
  };

  it.each([
    {
      name: "getSuggestions",
      invoke: (caller: ReturnType<typeof createAuthenticatedCaller>) => caller.getSuggestions({
        requiredSkills: ["Compositing"],
        startDate: planningWindow.startDate,
        endDate: planningWindow.endDate,
        hoursPerDay: 8,
      }),
    },
    {
      name: "getProjectStaffingSuggestions",
      invoke: (caller: ReturnType<typeof createAuthenticatedCaller>) => caller.getProjectStaffingSuggestions({
        projectId: projectRankingWindow.projectId,
      }),
    },
    {
      name: "searchCapacity",
      invoke: (caller: ReturnType<typeof createAuthenticatedCaller>) => caller.searchCapacity({
        startDate: planningWindow.startDate,
        endDate: planningWindow.endDate,
      }),
    },
    {
      name: "analyzeUtilization",
      invoke: (caller: ReturnType<typeof createAuthenticatedCaller>) => caller.analyzeUtilization({
        resourceId: planningWindow.resourceId,
        startDate: planningWindow.startDate,
        endDate: planningWindow.endDate,
      }),
    },
    {
      name: "findCapacity",
      invoke: (caller: ReturnType<typeof createAuthenticatedCaller>) => caller.findCapacity({
        resourceId: planningWindow.resourceId,
        startDate: planningWindow.startDate,
        endDate: planningWindow.endDate,
      }),
    },
    {
      name: "findBestProjectResource",
      invoke: (caller: ReturnType<typeof createAuthenticatedCaller>) => caller.findBestProjectResource({
        projectId: projectRankingWindow.projectId,
        startDate: projectRankingWindow.startDate,
        endDate: projectRankingWindow.endDate,
      }),
    },
    {
      name: "getBestProjectResourceDetail",
      invoke: (caller: ReturnType<typeof createAuthenticatedCaller>) => caller.getBestProjectResourceDetail({
        projectId: projectRankingWindow.projectId,
        startDate: projectRankingWindow.startDate,
        endDate: projectRankingWindow.endDate,
      }),
    },
  ])("requires planning read access for $name", async ({ invoke }) => {
    const caller = createAuthenticatedCaller({});

    await expect(invoke(caller)).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Planning read access required",
    });
  });

  it("allows explicit viewPlanning overrides to search capacity", async () => {
    vi.mocked(listAssignmentBookings).mockResolvedValueOnce([]);
    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createProtectedCallerWithOverrides(db, {
      granted: [PermissionKey.VIEW_PLANNING],
    });
    const result = await caller.searchCapacity({
      startDate: planningWindow.startDate,
      endDate: planningWindow.endDate,
    });

    expect(result).toEqual({
      period: "2026-04-01 to 2026-04-02",
      minHoursFilter: 4,
      results: [],
      totalFound: 0,
    });
  });

  it("does not treat viewCosts as a substitute for viewPlanning on staffing reads", async () => {
    const caller = createProtectedCallerWithOverrides({}, {
      granted: [PermissionKey.VIEW_COSTS],
    });

    await expect(caller.searchCapacity({
      startDate: planningWindow.startDate,
      endDate: planningWindow.endDate,
    })).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: "Planning read access required",
    });
  });

  it.each([
    {
      name: "getSuggestions",
      invoke: (caller: ReturnType<typeof createProtectedCallerWithOverrides>) => caller.getSuggestions({
        requiredSkills: ["Compositing"],
        startDate: planningWindow.startDate,
        endDate: planningWindow.endDate,
        hoursPerDay: 8,
      }),
    },
    {
      name: "getProjectStaffingSuggestions",
      invoke: (caller: ReturnType<typeof createProtectedCallerWithOverrides>) => caller.getProjectStaffingSuggestions({
        projectId: projectRankingWindow.projectId,
      }),
    },
    {
      name: "findBestProjectResource",
      invoke: (caller: ReturnType<typeof createProtectedCallerWithOverrides>) => caller.findBestProjectResource({
        projectId: projectRankingWindow.projectId,
        startDate: projectRankingWindow.startDate,
        endDate: projectRankingWindow.endDate,
      }),
    },
    {
      name: "getBestProjectResourceDetail",
      invoke: (caller: ReturnType<typeof createProtectedCallerWithOverrides>) => caller.getBestProjectResourceDetail({
        projectId: projectRankingWindow.projectId,
        startDate: projectRankingWindow.startDate,
        endDate: projectRankingWindow.endDate,
      }),
    },
  ])("requires viewCosts for $name", async ({ invoke }) => {
    const caller = createProtectedCallerWithOverrides({}, {
      granted: [PermissionKey.VIEW_PLANNING],
    });

    await expect(invoke(caller)).rejects.toMatchObject({
      code: "FORBIDDEN",
      message: `Permission required: ${PermissionKey.VIEW_COSTS}`,
    });
  });
});

// ─── getSuggestions ──────────────────────────────────────────────────────────

describe("staffing.getSuggestions", () => {
  it("returns ranked suggestions for a staffing demand", async () => {
    const resources = [
      sampleResource(),
      sampleResource({ id: "res_2", displayName: "Bob", eid: "bob", valueScore: 70 }),
    ];
    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue(resources),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getSuggestions({
      requiredSkills: ["Compositing"],
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-30"),
      hoursPerDay: 8,
    });

    expect(result).toHaveLength(2);
    expect(result[0]).toHaveProperty("resourceId");
    expect(result[0]).toHaveProperty("score");
    expect(result[0]).toMatchObject({
      resourceName: "Alice",
      eid: "alice",
      location: {
        countryCode: "DE",
        federalState: "BY",
      },
      capacity: expect.objectContaining({
        requestedHoursPerDay: 8,
        baseAvailableHours: expect.any(Number),
        effectiveAvailableHours: expect.any(Number),
        remainingHoursPerDay: expect.any(Number),
        holidayHoursDeduction: expect.any(Number),
      }),
      conflicts: {
        count: expect.any(Number),
        conflictDays: expect.any(Array),
        details: expect.any(Array),
      },
      ranking: expect.objectContaining({
        rank: 1,
        components: expect.any(Array),
      }),
    });
  });

  it("filters resources by chapter when provided", async () => {
    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createProtectedCaller(db);
    await caller.getSuggestions({
      requiredSkills: ["Compositing"],
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-30"),
      hoursPerDay: 8,
      chapter: "VFX",
    });

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

  it("returns empty array when no resources match", async () => {
    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getSuggestions({
      requiredSkills: ["Compositing"],
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-30"),
      hoursPerDay: 8,
    });

    expect(result).toHaveLength(0);
  });

  it("passes budget constraint to ranking", async () => {
    const resources = [sampleResource()];
    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue(resources),
      },
    };

    const { rankResources } = await import("@capakraken/staffing");
    const caller = createProtectedCaller(db);
    await caller.getSuggestions({
      requiredSkills: ["Compositing"],
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-30"),
      hoursPerDay: 8,
      budgetLcrCentsPerHour: 10000,
    });

    expect(rankResources).toHaveBeenCalledWith(
      expect.objectContaining({
        budgetLcrCentsPerHour: 10000,
      }),
    );
  });

  it("uses value score as a transparent tiebreaker within two score points", async () => {
    const resources = [
      sampleResource({ id: "res_1", displayName: "Alice", eid: "alice", valueScore: 60 }),
      sampleResource({ id: "res_2", displayName: "Bob", eid: "bob", valueScore: 95 }),
    ];
    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue(resources),
      },
    };

    const { rankResources } = await import("@capakraken/staffing");
    vi.mocked(rankResources).mockImplementationOnce((input: { resources: Array<{ id: string }> }) => ([
      {
        resourceId: input.resources[0]!.id,
        score: 80,
        breakdown: {
          skillScore: 80,
          availabilityScore: 80,
          costScore: 80,
          utilizationScore: 80,
        },
      },
      {
        resourceId: input.resources[1]!.id,
        score: 79,
        breakdown: {
          skillScore: 79,
          availabilityScore: 79,
          costScore: 79,
          utilizationScore: 79,
        },
      },
    ]));

    const caller = createProtectedCaller(db);
    const result = await caller.getSuggestions({
      requiredSkills: ["Compositing"],
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-30"),
      hoursPerDay: 8,
    });

    expect(result[0]?.resourceId).toBe("res_2");
    expect(result[0]?.ranking).toMatchObject({
      rank: 1,
      baseRank: 2,
      tieBreakerApplied: true,
    });
    expect(result[0]?.ranking.tieBreakerReason).toContain("value score");
  });
});

describe("staffing.getProjectStaffingSuggestions", () => {
  it("returns canonical project-scoped staffing suggestions with defaults and role filter", async () => {
    vi.mocked(listAssignmentBookings).mockResolvedValueOnce([]);

    const db = {
      project: {
        findUnique: vi.fn().mockResolvedValue({
          id: "project_1",
          shortCode: "GDM",
          name: "Gelddruckmaschine",
          startDate: new Date("2026-01-06T00:00:00.000Z"),
          endDate: new Date("2026-01-06T00:00:00.000Z"),
        }),
      },
      resource: {
        findMany: vi.fn().mockResolvedValue([
          sampleResource({
            id: "res_by",
            displayName: "Bavaria",
            eid: "BY-1",
            areaRole: { name: "Consultant" },
            country: { code: "DE", name: "Germany" },
          }),
          sampleResource({
            id: "res_hh",
            displayName: "Hamburg",
            eid: "HH-1",
            federalState: "HH",
            areaRole: { name: "Artist" },
            country: { code: "DE", name: "Germany" },
          }),
        ]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getProjectStaffingSuggestions({
      projectId: "project_1",
      roleName: "artist",
      limit: 5,
    });

    expect(result).toEqual({
      project: "Gelddruckmaschine (GDM)",
      period: "2026-01-06 to 2026-01-06",
      suggestions: [
        {
          id: "res_hh",
          name: "Hamburg",
          eid: "HH-1",
          role: "Artist",
          chapter: "VFX",
          fte: expect.any(Number),
          lcr: "75,00 EUR",
          workingDays: expect.any(Number),
          availableHours: expect.any(Number),
          bookedHours: 0,
          availableHoursPerDay: expect.any(Number),
          utilization: 0,
        },
      ],
    });
    expect(db.project.findUnique).toHaveBeenCalledWith({
      where: { id: "project_1" },
      select: {
        id: true,
        shortCode: true,
        name: true,
        startDate: true,
        endDate: true,
      },
    });
  });
});

describe("staffing.getBestProjectResourceDetail", () => {
  it("returns canonical project resource ranking with holiday-aware capacity details", async () => {
    const assignmentFindMany = vi
      .fn()
      .mockResolvedValueOnce([
        {
          resourceId: "res_carol",
          hoursPerDay: 2,
          startDate: new Date("2026-01-05T00:00:00.000Z"),
          endDate: new Date("2026-01-16T00:00:00.000Z"),
          status: "PROPOSED",
          resource: {
            id: "res_carol",
            eid: "carol.danvers",
            displayName: "Carol Danvers",
            chapter: "Delivery",
            lcrCents: 7664,
            availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
            countryId: "country_de",
            federalState: "HH",
            metroCityId: "city_hamburg",
            country: { code: "DE", name: "Deutschland" },
            metroCity: { name: "Hamburg" },
            areaRole: { name: "Artist" },
          },
        },
        {
          resourceId: "res_steve",
          hoursPerDay: 4,
          startDate: new Date("2026-01-05T00:00:00.000Z"),
          endDate: new Date("2026-01-16T00:00:00.000Z"),
          status: "CONFIRMED",
          resource: {
            id: "res_steve",
            eid: "steve.rogers",
            displayName: "Steve Rogers",
            chapter: "Delivery",
            lcrCents: 13377,
            availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
            countryId: "country_de",
            federalState: "BY",
            metroCityId: "city_augsburg",
            country: { code: "DE", name: "Deutschland" },
            metroCity: { name: "Augsburg" },
            areaRole: { name: "Artist" },
          },
        },
      ])
      .mockResolvedValueOnce([
        {
          resourceId: "res_carol",
          projectId: "project_lari",
          hoursPerDay: 2,
          startDate: new Date("2026-01-05T00:00:00.000Z"),
          endDate: new Date("2026-01-16T00:00:00.000Z"),
          status: "PROPOSED",
          project: { name: "Gelddruckmaschine", shortCode: "LARI" },
        },
        {
          resourceId: "res_steve",
          projectId: "project_lari",
          hoursPerDay: 4,
          startDate: new Date("2026-01-05T00:00:00.000Z"),
          endDate: new Date("2026-01-16T00:00:00.000Z"),
          status: "CONFIRMED",
          project: { name: "Gelddruckmaschine", shortCode: "LARI" },
        },
      ]);

    const db = {
      project: {
        findUnique: vi.fn().mockResolvedValue({
          id: "project_lari",
          name: "Gelddruckmaschine",
          shortCode: "LARI",
          status: "ACTIVE",
          responsiblePerson: "Larissa Joos",
        }),
      },
      assignment: {
        findMany: assignmentFindMany,
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.getBestProjectResourceDetail({
      projectId: "project_lari",
      startDate: new Date("2026-01-05T00:00:00.000Z"),
      endDate: new Date("2026-01-16T00:00:00.000Z"),
      minHoursPerDay: 3,
      rankingMode: "lowest_lcr",
    });

    expect(result.project).toEqual({
      id: "project_lari",
      name: "Gelddruckmaschine",
      shortCode: "LARI",
      status: "ACTIVE",
      responsiblePerson: "Larissa Joos",
    });
    expect(result.period).toEqual({
      startDate: "2026-01-05",
      endDate: "2026-01-16",
      minHoursPerDay: 3,
      rankingMode: "lowest_lcr",
    });
    expect(result.filters).toEqual({
      chapter: null,
      roleName: null,
    });
    expect(result.candidateCount).toBe(2);
    expect(result.bestMatch).toEqual(
      expect.objectContaining({
        name: "Carol Danvers",
        remainingHoursPerDay: 6,
        lcrCents: 7664,
        federalState: "HH",
        metroCity: "Hamburg",
        baseAvailableHours: 80,
        holidaySummary: expect.objectContaining({ count: 0 }),
      }),
    );
    expect(result.candidates).toEqual([
      expect.objectContaining({
        name: "Carol Danvers",
        remainingHoursPerDay: 6,
        workingDays: 10,
        baseAvailableHours: 80,
        holidaySummary: expect.objectContaining({ count: 0, hoursDeduction: 0 }),
        capacityBreakdown: expect.objectContaining({ holidayHoursDeduction: 0 }),
      }),
      expect.objectContaining({
        name: "Steve Rogers",
        remainingHoursPerDay: 4,
        workingDays: 9,
        baseAvailableHours: 80,
        holidaySummary: expect.objectContaining({ count: 1, hoursDeduction: 8 }),
        capacityBreakdown: expect.objectContaining({ holidayHoursDeduction: 8 }),
      }),
    ]);
  });
});

describe("staffing.searchCapacity", () => {
  it("returns holiday-aware capacity across multiple resources", async () => {
    vi.mocked(listAssignmentBookings).mockResolvedValueOnce([]);

    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue([
          sampleResource({
            id: "res_by",
            displayName: "Bavaria",
            eid: "BY-1",
            chapter: "CGI",
            areaRole: { name: "Consultant" },
            federalState: "BY",
          }),
          sampleResource({
            id: "res_hh",
            displayName: "Hamburg",
            eid: "HH-1",
            chapter: "CGI",
            areaRole: { name: "Consultant" },
            federalState: "HH",
          }),
        ]),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.searchCapacity({
      startDate: new Date("2026-01-06"),
      endDate: new Date("2026-01-06"),
      minHoursPerDay: 1,
    });

    expect(result.results).toHaveLength(1);
    expect(result.results[0]).toEqual(
      expect.objectContaining({
        name: "Hamburg",
        availableHours: 8,
        availableHoursPerDay: 8,
      }),
    );
  });

  it("applies role and chapter filters in the resource query", async () => {
    vi.mocked(listAssignmentBookings).mockResolvedValueOnce([]);

    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createProtectedCaller(db);
    await caller.searchCapacity({
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-02"),
      minHoursPerDay: 4,
      roleName: "Consult",
      chapter: "CG",
      limit: 5,
    });

    expect(db.resource.findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          isActive: true,
          areaRole: { name: { contains: "Consult", mode: "insensitive" } },
          chapter: { contains: "CG", mode: "insensitive" },
        }),
        take: 100,
      }),
    );
  });
});

// ─── analyzeUtilization ──────────────────────────────────────────────────────

describe("staffing.analyzeUtilization", () => {
  it("returns utilization analysis for an existing resource", async () => {
    const resource = {
      id: "res_1",
      displayName: "Alice",
      chargeabilityTarget: 80,
      availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
      countryId: "country_de",
      federalState: "BY",
      metroCityId: null,
      country: { code: "DE" },
      metroCity: null,
    };
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(resource),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.analyzeUtilization({
      resourceId: "res_1",
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-30"),
    });

    expect(result).toHaveProperty("currentChargeability");
    expect(result.resourceId).toBe("res_1");
  });

  it("excludes Bavarian public holidays from chargeability analysis", async () => {
    const resource = {
      id: "res_1",
      displayName: "Alice",
      chargeabilityTarget: 80,
      availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
      countryId: "country_de",
      federalState: "BY",
      metroCityId: null,
      country: { code: "DE" },
      metroCity: null,
    };
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(resource),
      },
    };

    const { listAssignmentBookings } = await import("@capakraken/application");
    vi.mocked(listAssignmentBookings).mockResolvedValue([
      {
        id: "a1",
        projectId: "project_1",
        resourceId: "res_1",
        startDate: new Date("2026-01-05T00:00:00.000Z"),
        endDate: new Date("2026-01-06T00:00:00.000Z"),
        hoursPerDay: 8,
        dailyCostCents: 0,
        status: "CONFIRMED",
        project: { id: "project_1", name: "Chargeable", shortCode: "CHG", status: "ACTIVE", orderType: "CHARGEABLE", clientId: null, dynamicFields: null },
        resource: { id: "res_1", displayName: "Alice", chapter: "VFX" },
      },
    ]);

    const caller = createProtectedCaller(db);
    const result = await caller.analyzeUtilization({
      resourceId: "res_1",
      startDate: new Date("2026-01-05T00:00:00.000Z"),
      endDate: new Date("2026-01-06T00:00:00.000Z"),
    });

    expect(result.currentChargeability).toBe(100);
    expect(result.overallocatedDays).toEqual([]);
    expect(result.underutilizedDays).toEqual([]);
  });

  it("throws NOT_FOUND when resource does not exist", async () => {
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
    };

    const caller = createProtectedCaller(db);
    await expect(
      caller.analyzeUtilization({
        resourceId: "nonexistent",
        startDate: new Date("2026-04-01"),
        endDate: new Date("2026-04-30"),
      }),
    ).rejects.toThrow("Resource not found");
  });
});

// ─── findCapacity ────────────────────────────────────────────────────────────

describe("staffing.findCapacity", () => {
  it("returns capacity windows for an existing resource", async () => {
    const resource = {
      id: "res_1",
      displayName: "Alice",
      availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
      countryId: "country_de",
      federalState: "BY",
      metroCityId: null,
      country: { code: "DE" },
      metroCity: null,
    };
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(resource),
      },
    };

    const caller = createProtectedCaller(db);
    const result = await caller.findCapacity({
      resourceId: "res_1",
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-30"),
    });

    expect(result.length).toBeGreaterThan(0);
    expect(result[0]).toHaveProperty("availableHoursPerDay");
    expect(result.every((window) => window.availableHoursPerDay > 0)).toBe(true);
    expect(result.reduce((sum, window) => sum + window.availableDays, 0)).toBeGreaterThan(0);
  });

  it("splits capacity windows around Bavarian public holidays", async () => {
    const resource = {
      id: "res_1",
      displayName: "Alice",
      availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
      countryId: "country_de",
      federalState: "BY",
      metroCityId: null,
      country: { code: "DE" },
      metroCity: null,
    };
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(resource),
      },
    };

    const { listAssignmentBookings } = await import("@capakraken/application");
    vi.mocked(listAssignmentBookings).mockResolvedValue([]);

    const caller = createProtectedCaller(db);
    const result = await caller.findCapacity({
      resourceId: "res_1",
      startDate: new Date("2026-01-05T00:00:00.000Z"),
      endDate: new Date("2026-01-07T00:00:00.000Z"),
      minAvailableHoursPerDay: 4,
    });

    expect(result).toHaveLength(2);
    expect(result[0]).toEqual(
      expect.objectContaining({
        startDate: new Date("2026-01-05T00:00:00.000Z"),
        endDate: new Date("2026-01-05T00:00:00.000Z"),
      }),
    );
    expect(result[1]).toEqual(
      expect.objectContaining({
        startDate: new Date("2026-01-07T00:00:00.000Z"),
        endDate: new Date("2026-01-07T00:00:00.000Z"),
      }),
    );
  });

  it("throws NOT_FOUND when resource does not exist", async () => {
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
    };

    const caller = createProtectedCaller(db);
    await expect(
      caller.findCapacity({
        resourceId: "nonexistent",
        startDate: new Date("2026-04-01"),
        endDate: new Date("2026-04-30"),
      }),
    ).rejects.toThrow("Resource not found");
  });

  it("honors minAvailableHoursPerDay when computing holiday-aware windows", async () => {
    const resource = {
      id: "res_1",
      displayName: "Alice",
      availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
      countryId: "country_de",
      federalState: "BY",
      metroCityId: null,
      country: { code: "DE" },
      metroCity: null,
    };
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue(resource),
      },
    };

    const { listAssignmentBookings } = await import("@capakraken/application");
    vi.mocked(listAssignmentBookings).mockResolvedValue([
      {
        id: "a1",
        projectId: "project_1",
        resourceId: "res_1",
        startDate: new Date("2026-04-01T00:00:00.000Z"),
        endDate: new Date("2026-04-30T00:00:00.000Z"),
        hoursPerDay: 3,
        dailyCostCents: 0,
        status: "CONFIRMED",
        project: { id: "project_1", name: "Project", shortCode: "PRJ", status: "ACTIVE", orderType: "CHARGEABLE", clientId: null, dynamicFields: null },
        resource: { id: "res_1", displayName: "Alice", chapter: "VFX" },
      },
    ]);

    const caller = createProtectedCaller(db);
    const result = await caller.findCapacity({
      resourceId: "res_1",
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-30"),
      minAvailableHoursPerDay: 6,
    });

    expect(result.every((window) => window.availableHoursPerDay >= 6)).toBe(true);
  });
});
