import { AllocationStatus, PermissionKey, SystemRole } from "@capakraken/shared";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { allocationRouter } from "../router/allocation.js";
import { emitAllocationCreated, emitAllocationDeleted, emitNotificationCreated } from "../sse/event-bus.js";
import { checkBudgetThresholds } from "../lib/budget-alerts.js";
import { generateAutoSuggestions } from "../lib/auto-staffing.js";
import { invalidateDashboardCache } from "../lib/cache.js";
import { logger } from "../lib/logger.js";
import { dispatchWebhooks } from "../lib/webhook-dispatcher.js";
import { createCallerFactory } from "../trpc.js";

vi.mock("../sse/event-bus.js", () => ({
  emitAllocationCreated: vi.fn(),
  emitAllocationDeleted: vi.fn(),
  emitAllocationUpdated: vi.fn(),
  emitNotificationCreated: vi.fn(),
}));

vi.mock("../lib/budget-alerts.js", () => ({
  checkBudgetThresholds: vi.fn(),
}));

vi.mock("../lib/cache.js", () => ({
  invalidateDashboardCache: vi.fn(),
}));

vi.mock("../lib/auto-staffing.js", () => ({
  generateAutoSuggestions: vi.fn(),
}));

vi.mock("../lib/webhook-dispatcher.js", () => ({
  dispatchWebhooks: vi.fn().mockResolvedValue(undefined),
}));

vi.mock("../lib/logger.js", () => ({
  logger: {
    error: vi.fn(),
    warn: vi.fn(),
    info: vi.fn(),
    debug: vi.fn(),
  },
}));

const createCaller = createCallerFactory(allocationRouter);

beforeEach(() => {
  vi.clearAllMocks();
});

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

function createControllerCaller(db: Record<string, unknown>) {
  return createCaller({
    session: {
      user: { email: "controller@example.com", name: "Controller", image: null },
      expires: "2026-03-13T00:00:00.000Z",
    },
    db: db as never,
    dbUser: {
      id: "user_controller",
      systemRole: SystemRole.CONTROLLER,
      permissionOverrides: null,
    },
  });
}

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

describe("allocation router authorization", () => {
  const planningWindow = {
    resourceId: "resource_1",
    startDate: new Date("2026-04-01T00:00:00.000Z"),
    endDate: new Date("2026-04-02T00:00:00.000Z"),
    hoursPerDay: 8,
  };

  it("allows controllers to read assignment lists through the planning audience", async () => {
    const assignment = {
      id: "assignment_1",
      demandRequirementId: null,
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-04-01T00:00:00.000Z"),
      endDate: new Date("2026-04-02T00:00:00.000Z"),
      hoursPerDay: 8,
      percentage: 100,
      role: "Compositor",
      roleId: "role_comp",
      dailyCostCents: 40000,
      status: AllocationStatus.ACTIVE,
      metadata: {},
      resource: null,
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: null,
      demandRequirement: null,
    };
    const db = {
      assignment: {
        findMany: vi.fn().mockResolvedValue([assignment]),
      },
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
    };

    const caller = createControllerCaller(db);
    const result = await caller.listAssignments({});

    expect(result).toEqual([assignment]);
    expect(db.assignment.findMany).toHaveBeenCalledWith({
      where: {},
      include: expect.any(Object),
      orderBy: { startDate: "asc" },
    });
  });

  it("allows explicit viewPlanning overrides to read assignment lists", async () => {
    const assignment = {
      id: "assignment_1",
      demandRequirementId: null,
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-04-01T00:00:00.000Z"),
      endDate: new Date("2026-04-02T00:00:00.000Z"),
      hoursPerDay: 8,
      percentage: 100,
      role: "Compositor",
      roleId: "role_comp",
      dailyCostCents: 40000,
      status: AllocationStatus.ACTIVE,
      metadata: {},
      resource: null,
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: null,
      demandRequirement: null,
    };
    const db = {
      assignment: {
        findMany: vi.fn().mockResolvedValue([assignment]),
      },
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
    };

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

    expect(result).toEqual([assignment]);
  });

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

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

  it.each([
    { name: "list", invoke: (caller: ReturnType<typeof createProtectedCaller>) => caller.list({}) },
    { name: "listView", invoke: (caller: ReturnType<typeof createProtectedCaller>) => caller.listView({}) },
    { name: "listDemands", invoke: (caller: ReturnType<typeof createProtectedCaller>) => caller.listDemands({}) },
    { name: "listAssignments", invoke: (caller: ReturnType<typeof createProtectedCaller>) => caller.listAssignments({}) },
    {
      name: "getAssignmentById",
      invoke: (caller: ReturnType<typeof createProtectedCaller>) => caller.getAssignmentById({ id: "assignment_1" }),
    },
    {
      name: "resolveAssignment",
      invoke: (caller: ReturnType<typeof createProtectedCaller>) => caller.resolveAssignment({ assignmentId: "assignment_1" }),
    },
    {
      name: "getDemandRequirementById",
      invoke: (caller: ReturnType<typeof createProtectedCaller>) => caller.getDemandRequirementById({ id: "demand_1" }),
    },
    {
      name: "checkResourceAvailability",
      invoke: (caller: ReturnType<typeof createProtectedCaller>) => caller.checkResourceAvailability(planningWindow),
    },
    {
      name: "getResourceAvailabilityView",
      invoke: (caller: ReturnType<typeof createProtectedCaller>) => caller.getResourceAvailabilityView(planningWindow),
    },
    {
      name: "getResourceAvailabilitySummary",
      invoke: (caller: ReturnType<typeof createProtectedCaller>) => caller.getResourceAvailabilitySummary(planningWindow),
    },
  ])("requires planning read access for $name", async ({ invoke }) => {
    const caller = createProtectedCaller({});

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

function createDemandWorkflowDb(overrides: Record<string, unknown> = {}) {
  const db = {
    project: {
      findUnique: vi.fn().mockResolvedValue({ id: "project_1", name: "Project One" }),
    },
    role: {
      findUnique: vi.fn().mockResolvedValue({ name: "FX Artist" }),
    },
    user: {
      findMany: vi.fn().mockResolvedValue([{ id: "mgr_1" }, { id: "admin_1" }]),
    },
    notification: {
      create: vi.fn().mockImplementation(async ({ data }: { data: { userId: string } }) => ({
        id: `notif_${data.userId}`,
      })),
    },
    auditLog: {
      create: vi.fn().mockResolvedValue({}),
    },
  };

  return {
    ...db,
    ...overrides,
    project: { ...db.project, ...(overrides.project as Record<string, unknown> | undefined) },
    role: { ...db.role, ...(overrides.role as Record<string, unknown> | undefined) },
    user: { ...db.user, ...(overrides.user as Record<string, unknown> | undefined) },
    notification: {
      ...db.notification,
      ...(overrides.notification as Record<string, unknown> | undefined),
    },
    auditLog: { ...db.auditLog, ...(overrides.auditLog as Record<string, unknown> | undefined) },
  };
}

describe("allocation entry resolution router", () => {
  it("excludes regional holidays from resource availability coverage", async () => {
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          id: "resource_1",
          displayName: "Bruce Banner",
          eid: "E-001",
          fte: 1,
          availability: {
            monday: 8,
            tuesday: 8,
            wednesday: 8,
            thursday: 8,
            friday: 8,
            saturday: 0,
            sunday: 0,
          },
          countryId: "country_de",
          federalState: "BY",
          metroCityId: null,
          country: { dailyWorkingHours: 8, code: "DE" },
          metroCity: null,
        }),
      },
      assignment: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "assignment_1",
            startDate: new Date("2026-01-05T00:00:00.000Z"),
            endDate: new Date("2026-01-06T00:00:00.000Z"),
            hoursPerDay: 8,
            status: AllocationStatus.CONFIRMED,
            project: { name: "Gamma", shortCode: "GAM" },
          },
        ]),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const caller = createManagerCaller(db);
    const result = await caller.checkResourceAvailability({
      resourceId: "resource_1",
      startDate: new Date("2026-01-05T00:00:00.000Z"),
      endDate: new Date("2026-01-06T00:00:00.000Z"),
      hoursPerDay: 8,
    });

    expect(result).toMatchObject({
      dailyCapacity: 8,
      totalWorkingDays: 1,
      availableDays: 0,
      partialDays: 0,
      conflictDays: 1,
      totalAvailableHours: 0,
      totalRequestedHours: 8,
      coveragePercent: 0,
    });
  });

  it("returns the canonical resource availability summary shape", async () => {
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          id: "resource_1",
          displayName: "Bruce Banner",
          eid: "E-001",
          fte: 1,
          availability: {
            monday: 8,
            tuesday: 8,
            wednesday: 8,
            thursday: 8,
            friday: 8,
            saturday: 0,
            sunday: 0,
          },
          countryId: "country_de",
          federalState: "BY",
          metroCityId: null,
          country: { dailyWorkingHours: 8, code: "DE" },
          metroCity: null,
        }),
      },
      assignment: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "assignment_1",
            startDate: new Date("2026-04-01T00:00:00.000Z"),
            endDate: new Date("2026-04-01T00:00:00.000Z"),
            hoursPerDay: 4,
            status: "CONFIRMED",
            project: { name: "Gelddruckmaschine", shortCode: "GDM" },
          },
        ]),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "vac_1",
            type: "ANNUAL",
            status: "APPROVED",
            startDate: new Date("2026-04-02T00:00:00.000Z"),
            endDate: new Date("2026-04-02T00:00:00.000Z"),
            isHalfDay: true,
            halfDayPart: "AFTERNOON",
          },
        ]),
      },
    };

    const caller = createManagerCaller(db);
    const result = await caller.getResourceAvailabilitySummary({
      resourceId: "resource_1",
      startDate: new Date("2026-04-01T00:00:00.000Z"),
      endDate: new Date("2026-04-02T00:00:00.000Z"),
      hoursPerDay: 8,
    });

    expect(result).toEqual({
      resource: "Bruce Banner",
      period: "2026-04-01 to 2026-04-02",
      fte: null,
      workingDays: 2,
      periodAvailableHours: 16,
      periodBookedHours: 4,
      periodRemainingHours: 12,
      maxHoursPerDay: 8,
      currentBookedHoursPerDay: 2,
      availableHoursPerDay: 6,
      isFullyAvailable: false,
      existingAllocations: [
        {
          project: "Gelddruckmaschine (GDM)",
          hoursPerDay: 4,
          status: "CONFIRMED",
          start: "2026-04-01",
          end: "2026-04-01",
        },
      ],
      vacations: [
        {
          type: "ANNUAL",
          start: "2026-04-02",
          end: "2026-04-02",
          isHalfDay: true,
        },
      ],
    });
  });

  it("creates an open demand through allocation.create without requiring isPlaceholder", async () => {
    const createdDemandRequirement = {
      id: "demand_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 4,
      percentage: 50,
      role: "FX Artist",
      roleId: "role_fx",
      headcount: 2,
      status: AllocationStatus.PROPOSED,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_fx", name: "FX Artist", color: "#222222" },
    };

    const db = {
      project: {
        findUnique: vi.fn().mockResolvedValue({ id: "project_1" }),
      },
      demandRequirement: {
        create: vi.fn().mockResolvedValue(createdDemandRequirement),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };

    const caller = createManagerCaller(db);
    const result = await caller.create({
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 4,
      percentage: 50,
      role: "FX Artist",
      roleId: "role_fx",
      headcount: 2,
      status: AllocationStatus.PROPOSED,
      metadata: {},
    });

    expect(result.id).toBe("demand_1");
    expect(result.isPlaceholder).toBe(true);
    expect(db.demandRequirement.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          headcount: 2,
        }),
      }),
    );
  });

  it("creates an assignment through allocation.create without requiring isPlaceholder", async () => {
    const createdAssignment = {
      id: "assignment_1",
      demandRequirementId: null,
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 8,
      percentage: 100,
      role: "Compositor",
      roleId: "role_comp",
      dailyCostCents: 40000,
      status: AllocationStatus.ACTIVE,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      resource: {
        id: "resource_1",
        displayName: "Alice",
        eid: "E-001",
        lcrCents: 5000,
      },
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_comp", name: "Compositor", color: "#111111" },
      demandRequirement: null,
    };

    const db = {
      project: {
        findUnique: vi.fn().mockResolvedValue({ id: "project_1" }),
      },
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          id: "resource_1",
          displayName: "Alice",
          eid: "E-001",
          lcrCents: 5000,
          availability: {
            monday: 8,
            tuesday: 8,
            wednesday: 8,
            thursday: 8,
            friday: 8,
            saturday: 0,
            sunday: 0,
          },
        }),
      },
      allocation: {
        findMany: vi.fn().mockResolvedValue([]),
        create: vi.fn(),
      },
      assignment: {
        findMany: vi.fn().mockResolvedValue([]),
        create: vi.fn().mockResolvedValue(createdAssignment),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };

    const caller = createManagerCaller(db);
    const result = await caller.create({
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 8,
      percentage: 100,
      role: "Compositor",
      roleId: "role_comp",
      status: AllocationStatus.ACTIVE,
      metadata: {},
    });

    expect(result.id).toBe("assignment_1");
    expect(result.isPlaceholder).toBe(false);
    expect(db.allocation.create).not.toHaveBeenCalled();
    expect(db.assignment.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          resourceId: "resource_1",
        }),
      }),
    );
  });

  it("creates an explicit demand requirement without dual-writing a legacy allocation row", async () => {
    vi.mocked(emitAllocationCreated).mockClear();
    vi.mocked(emitNotificationCreated).mockClear();

    const createdDemandRequirement = {
      id: "demand_explicit_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 4,
      percentage: 50,
      role: "FX Artist",
      roleId: "role_fx",
      headcount: 2,
      status: AllocationStatus.PROPOSED,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_fx", name: "FX Artist", color: "#222222" },
    };

    const db = createDemandWorkflowDb({
      demandRequirement: {
        create: vi.fn().mockResolvedValue(createdDemandRequirement),
      },
    }) as Record<string, unknown>;
    Object.assign(db, {
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    });

    const caller = createManagerCaller(db);
    const result = await caller.createDemandRequirement({
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 4,
      percentage: 50,
      role: "FX Artist",
      roleId: "role_fx",
      headcount: 2,
      status: AllocationStatus.PROPOSED,
      metadata: {},
    });

    expect(result.id).toBe("demand_explicit_1");
    expect((db as { allocation?: { create?: unknown } }).allocation?.create).toBeUndefined();
    expect(db.demandRequirement.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          headcount: 2,
        }),
      }),
    );
    expect(emitAllocationCreated).toHaveBeenCalledWith({
      id: "demand_explicit_1",
      projectId: "project_1",
      resourceId: null,
    });
    expect(db.notification.create).toHaveBeenCalledTimes(2);
    expect(emitNotificationCreated).toHaveBeenCalledTimes(2);
  });

  it("creates a canonical demand draft with router-owned defaults", async () => {
    vi.mocked(emitAllocationCreated).mockClear();
    vi.mocked(emitNotificationCreated).mockClear();

    const createdDemandRequirement = {
      id: "demand_draft_1",
      projectId: "project_1",
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-15"),
      hoursPerDay: 6,
      percentage: 75,
      role: "Designer",
      roleId: "role_design",
      headcount: 2,
      status: AllocationStatus.PROPOSED,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_design", name: "Designer", color: "#0099FF" },
    };

    const db = createDemandWorkflowDb({
      demandRequirement: {
        create: vi.fn().mockResolvedValue(createdDemandRequirement),
      },
    }) as Record<string, unknown>;
    Object.assign(db, {
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    });

    const caller = createManagerCaller(db);
    const result = await caller.createDemand({
      projectId: "project_1",
      role: "Designer",
      roleId: "role_design",
      headcount: 2,
      hoursPerDay: 6,
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-15"),
    });

    expect(result.id).toBe("demand_draft_1");
    expect(db.demandRequirement.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          headcount: 2,
          percentage: 75,
          status: AllocationStatus.PROPOSED,
          metadata: {},
        }),
      }),
    );
    expect(emitAllocationCreated).toHaveBeenCalledWith({
      id: "demand_draft_1",
      projectId: "project_1",
      resourceId: null,
    });
  });

  it("logs and swallows background side-effect failures during demand creation", async () => {
    vi.mocked(invalidateDashboardCache).mockRejectedValueOnce(new Error("redis unavailable"));
    vi.mocked(checkBudgetThresholds).mockRejectedValueOnce(new Error("budget alerts unavailable"));
    vi.mocked(generateAutoSuggestions).mockRejectedValueOnce(new Error("auto suggestions unavailable"));

    const createdDemandRequirement = {
      id: "demand_safe_1",
      projectId: "project_1",
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-15"),
      hoursPerDay: 6,
      percentage: 75,
      role: "Designer",
      roleId: "role_design",
      headcount: 2,
      status: AllocationStatus.PROPOSED,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_design", name: "Designer", color: "#0099FF" },
    };

    const db = createDemandWorkflowDb({
      demandRequirement: {
        create: vi.fn().mockResolvedValue(createdDemandRequirement),
      },
    }) as Record<string, unknown>;
    Object.assign(db, {
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    });

    const caller = createManagerCaller(db);
    const result = await caller.createDemand({
      projectId: "project_1",
      role: "Designer",
      roleId: "role_design",
      headcount: 2,
      hoursPerDay: 6,
      startDate: new Date("2026-04-01"),
      endDate: new Date("2026-04-15"),
    });

    await Promise.resolve();
    await Promise.resolve();

    expect(result.id).toBe("demand_safe_1");
    expect(vi.mocked(logger.error)).toHaveBeenCalledWith(
      expect.objectContaining({ effectName: "invalidateDashboardCache" }),
      "Allocation background side effect failed",
    );
    expect(vi.mocked(logger.error)).toHaveBeenCalledWith(
      expect.objectContaining({ effectName: "checkBudgetThresholds", projectId: "project_1" }),
      "Allocation background side effect failed",
    );
    expect(vi.mocked(logger.error)).toHaveBeenCalledWith(
      expect.objectContaining({ effectName: "generateAutoSuggestions", demandRequirementId: "demand_safe_1" }),
      "Allocation background side effect failed",
    );
  });

  it("logs and swallows background webhook failures during allocation creation", async () => {
    vi.mocked(dispatchWebhooks).mockRejectedValueOnce(new Error("webhook unavailable"));

    const createdAssignment = {
      id: "assignment_safe_1",
      demandRequirementId: null,
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 8,
      percentage: 100,
      role: "Compositor",
      roleId: "role_comp",
      dailyCostCents: 40000,
      status: AllocationStatus.ACTIVE,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      resource: {
        id: "resource_1",
        displayName: "Alice",
        eid: "E-001",
        lcrCents: 5000,
      },
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_comp", name: "Compositor", color: "#111111" },
      demandRequirement: null,
    };

    const db = {
      project: {
        findUnique: vi.fn().mockResolvedValue({ id: "project_1" }),
      },
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          id: "resource_1",
          lcrCents: 5000,
          availability: {
            monday: 8,
            tuesday: 8,
            wednesday: 8,
            thursday: 8,
            friday: 8,
            saturday: 0,
            sunday: 0,
          },
        }),
      },
      allocation: {
        findMany: vi.fn().mockResolvedValue([]),
        create: vi.fn(),
      },
      assignment: {
        findMany: vi.fn().mockResolvedValue([]),
        create: vi.fn().mockResolvedValue(createdAssignment),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };

    const caller = createManagerCaller(db);
    const result = await caller.create({
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 8,
      percentage: 100,
      role: "Compositor",
      roleId: "role_comp",
      status: AllocationStatus.ACTIVE,
      metadata: {},
    });

    await Promise.resolve();
    await Promise.resolve();

    expect(result.id).toBe("assignment_safe_1");
    expect(vi.mocked(logger.error)).toHaveBeenCalledWith(
      expect.objectContaining({ effectName: "dispatchWebhooks", event: "allocation.created" }),
      "Allocation background side effect failed",
    );
  });

  it("creates an explicit assignment without dual-writing a legacy allocation row", async () => {
    vi.mocked(emitAllocationCreated).mockClear();

    const createdAssignment = {
      id: "assignment_explicit_1",
      demandRequirementId: null,
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 8,
      percentage: 100,
      role: "Compositor",
      roleId: "role_comp",
      dailyCostCents: 40000,
      status: AllocationStatus.ACTIVE,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      resource: {
        id: "resource_1",
        displayName: "Alice",
        eid: "E-001",
        lcrCents: 5000,
      },
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_comp", name: "Compositor", color: "#111111" },
      demandRequirement: null,
    };

    const db = {
      project: {
        findUnique: vi.fn().mockResolvedValue({ id: "project_1" }),
      },
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          id: "resource_1",
          lcrCents: 5000,
          availability: {
            monday: 8,
            tuesday: 8,
            wednesday: 8,
            thursday: 8,
            friday: 8,
            saturday: 0,
            sunday: 0,
          },
        }),
      },
      allocation: {
        findMany: vi.fn().mockResolvedValue([]),
        create: vi.fn(),
      },
      assignment: {
        findMany: vi.fn().mockResolvedValue([]),
        create: vi.fn().mockResolvedValue(createdAssignment),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };

    const caller = createManagerCaller(db);
    const result = await caller.createAssignment({
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 8,
      percentage: 100,
      role: "Compositor",
      roleId: "role_comp",
      status: AllocationStatus.ACTIVE,
      metadata: {},
    });

    expect(result.id).toBe("assignment_explicit_1");
    expect(db.allocation.create).not.toHaveBeenCalled();
    expect(db.assignment.create).toHaveBeenCalledWith(
      expect.objectContaining({
        data: expect.objectContaining({
          resourceId: "resource_1",
        }),
      }),
    );
    expect(emitAllocationCreated).toHaveBeenCalledWith({
      id: "assignment_explicit_1",
      projectId: "project_1",
      resourceId: "resource_1",
    });
  });

  it("assigns a resource to demand and returns the hydrated demand view", async () => {
    const demandView = {
      id: "demand_1",
      projectId: "project_1",
      startDate: new Date("2026-05-01T00:00:00.000Z"),
      endDate: new Date("2026-05-15T00:00:00.000Z"),
      hoursPerDay: 6,
      percentage: 75,
      role: "Designer",
      roleId: "role_1",
      headcount: 1,
      status: AllocationStatus.PROPOSED,
      metadata: {},
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_1", name: "Designer", color: "#00AAFF" },
      assignments: [],
    };

    const createdAssignment = {
      id: "assignment_1",
      demandRequirementId: "demand_1",
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-05-01T00:00:00.000Z"),
      endDate: new Date("2026-05-15T00:00:00.000Z"),
      hoursPerDay: 6,
      percentage: 75,
      role: "Designer",
      roleId: "role_1",
      dailyCostCents: 42000,
      status: AllocationStatus.PROPOSED,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      resource: {
        id: "resource_1",
        displayName: "Alice",
        eid: "E-001",
        lcrCents: 7000,
      },
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_1", name: "Designer", color: "#00AAFF" },
      demandRequirement: demandView,
    };

    const db = {
      project: {
        findUnique: vi.fn().mockResolvedValue({ id: "project_1" }),
      },
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          id: "resource_1",
          lcrCents: 7000,
          availability: {
            monday: 8,
            tuesday: 8,
            wednesday: 8,
            thursday: 8,
            friday: 8,
            saturday: 0,
            sunday: 0,
          },
        }),
      },
      demandRequirement: {
        findUnique: vi.fn()
          .mockResolvedValueOnce({
            id: "demand_1",
            projectId: "project_1",
            startDate: new Date("2026-05-01T00:00:00.000Z"),
            endDate: new Date("2026-05-15T00:00:00.000Z"),
            hoursPerDay: 6,
            role: "Designer",
            roleId: "role_1",
            headcount: 1,
            status: AllocationStatus.PROPOSED,
            metadata: {},
          })
          .mockResolvedValueOnce({
            id: "demand_1",
            projectId: "project_1",
          })
          .mockResolvedValueOnce(demandView),
        update: vi.fn().mockResolvedValue({
          id: "demand_1",
          projectId: "project_1",
          headcount: 1,
          status: AllocationStatus.COMPLETED,
        }),
      },
      assignment: {
        findMany: vi.fn().mockResolvedValue([]),
        create: vi.fn().mockResolvedValue(createdAssignment),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };

    const caller = createManagerCaller(db);
    const result = await caller.assignResourceToDemand({
      demandRequirementId: "demand_1",
      resourceId: "resource_1",
    });

    expect(result.assignment.id).toBe("assignment_1");
    expect(result.demandRequirement.project.shortCode).toBe("PRJ");
    expect(result.demandRequirement.roleEntity?.name).toBe("Designer");
    expect(db.assignment.create).toHaveBeenCalledTimes(1);
  });

  it("deletes an explicit demand requirement without routing through allocation.delete", async () => {
    vi.mocked(emitAllocationDeleted).mockClear();

    const existingDemandRequirement = {
      id: "demand_explicit_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 4,
      percentage: 50,
      role: "FX Artist",
      roleId: "role_fx",
      headcount: 2,
      status: AllocationStatus.PROPOSED,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      project: {
        id: "project_1",
        name: "Project One",
        shortCode: "PRJ",
        status: "ACTIVE",
        endDate: new Date("2026-03-20"),
      },
      roleEntity: { id: "role_fx", name: "FX Artist", color: "#222222" },
      assignments: [],
    };

    const db = {
      demandRequirement: {
        findUnique: vi.fn().mockResolvedValue(existingDemandRequirement),
        delete: vi.fn().mockResolvedValue(existingDemandRequirement),
      },
      assignment: {
        updateMany: vi.fn().mockResolvedValue({ count: 0 }),
      },
      allocation: {
        findUnique: vi.fn().mockResolvedValue(null),
        delete: vi.fn(),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };

    const caller = createManagerCaller(db);
    const result = await caller.deleteDemandRequirement({ id: "demand_explicit_1" });

    expect(result).toEqual({ success: true });
    expect(db.assignment.updateMany).toHaveBeenCalledWith({
      where: { demandRequirementId: "demand_explicit_1" },
      data: { demandRequirementId: null },
    });
    expect(db.demandRequirement.delete).toHaveBeenCalledWith({
      where: { id: "demand_explicit_1" },
    });
    expect(db.allocation.delete).not.toHaveBeenCalled();
    expect(emitAllocationDeleted).toHaveBeenCalledWith("demand_explicit_1", "project_1");
  });

  it("deletes an explicit assignment without routing through allocation.delete", async () => {
    vi.mocked(emitAllocationDeleted).mockClear();

    const existingAssignment = {
      id: "assignment_explicit_1",
      demandRequirementId: null,
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 8,
      percentage: 100,
      role: "Compositor",
      roleId: "role_comp",
      dailyCostCents: 40000,
      status: AllocationStatus.ACTIVE,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      resource: {
        id: "resource_1",
        displayName: "Alice",
        eid: "E-001",
        lcrCents: 5000,
      },
      project: {
        id: "project_1",
        name: "Project One",
        shortCode: "PRJ",
        status: "ACTIVE",
        endDate: new Date("2026-03-20"),
      },
      roleEntity: { id: "role_comp", name: "Compositor", color: "#111111" },
      demandRequirement: null,
    };

    const db = {
      assignment: {
        findUnique: vi.fn().mockResolvedValue(existingAssignment),
        delete: vi.fn().mockResolvedValue(existingAssignment),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };

    const caller = createManagerCaller(db);
    const result = await caller.deleteAssignment({ id: "assignment_explicit_1" });

    expect(result).toEqual({ success: true });
    expect(db.assignment.delete).toHaveBeenCalledWith({
      where: { id: "assignment_explicit_1" },
    });
    expect(emitAllocationDeleted).toHaveBeenCalledWith("assignment_explicit_1", "project_1", "resource_1");
  });

  it("updates an explicit demand row through allocation.update", async () => {
    const existingDemand = {
      id: "demand_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 4,
      percentage: 50,
      role: "FX Artist",
      roleId: "role_fx",
      headcount: 1,
      status: AllocationStatus.PROPOSED,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_fx", name: "FX Artist", color: "#222222" },
    };
    const updatedDemand = {
      ...existingDemand,
      headcount: 2,
      status: AllocationStatus.CONFIRMED,
      metadata: { source: "router-test" },
      updatedAt: new Date("2026-03-14"),
    };

    const db = {
      allocation: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
      demandRequirement: {
        findUnique: vi.fn().mockResolvedValue(existingDemand),
        update: vi.fn().mockResolvedValue(updatedDemand),
      },
      assignment: {
        findUnique: vi.fn().mockResolvedValue(null),
        update: vi.fn(),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };

    const caller = createManagerCaller(db);
    const result = await caller.update({
      id: "demand_1",
      data: {
        headcount: 2,
        status: AllocationStatus.CONFIRMED,
        metadata: { source: "router-test" },
      },
    });

    expect(result.id).toBe("demand_1");
    expect(result.isPlaceholder).toBe(true);
    expect(result.headcount).toBe(2);
    expect(db.demandRequirement.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: "demand_1" },
      }),
    );
  });

  it("updates a demand row by its direct id", async () => {
    const existingDemand = {
      id: "demand_stale",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 4,
      percentage: 50,
      role: "FX Artist",
      roleId: "role_fx",
      headcount: 1,
      status: AllocationStatus.PROPOSED,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_fx", name: "FX Artist", color: "#222222" },
    };
    const updatedDemand = {
      ...existingDemand,
      headcount: 2,
      status: AllocationStatus.CONFIRMED,
      updatedAt: new Date("2026-03-14"),
    };

    const db = {
      demandRequirement: {
        findUnique: vi.fn().mockImplementation(
          ({ where }: { where: { id?: string } }) => {
            if (where.id === "demand_stale") {
              return existingDemand;
            }

            return null;
          },
        ),
        update: vi.fn().mockResolvedValue(updatedDemand),
      },
      assignment: {
        findUnique: vi.fn().mockResolvedValue(null),
        update: vi.fn(),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };

    const caller = createManagerCaller(db);
    const result = await caller.update({
      id: "demand_stale",
      data: {
        headcount: 2,
        status: AllocationStatus.CONFIRMED,
      },
    });

    expect(result.id).toBe("demand_stale");
    expect(result.isPlaceholder).toBe(true);
    expect(db.demandRequirement.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: "demand_stale" },
      }),
    );
  });

  it("batch deletes explicit demand and assignment rows through allocation.batchDelete", async () => {
    const explicitDemand = {
      id: "demand_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 4,
      percentage: 50,
      role: "FX Artist",
      roleId: "role_fx",
      headcount: 1,
      status: AllocationStatus.PROPOSED,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_fx", name: "FX Artist", color: "#222222" },
    };
    const explicitAssignment = {
      id: "assignment_1",
      demandRequirementId: null,
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 8,
      percentage: 100,
      role: "Compositor",
      roleId: "role_comp",
      dailyCostCents: 32000,
      status: AllocationStatus.ACTIVE,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      resource: { id: "resource_1", displayName: "Alice", eid: "E-001", lcrCents: 5000 },
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_comp", name: "Compositor", color: "#111111" },
      demandRequirement: null,
    };

    const db = {
      allocation: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
      demandRequirement: {
        findUnique: vi.fn().mockImplementation(({ where }: { where: { id: string } }) =>
          where.id === "demand_1" ? explicitDemand : null,
        ),
        delete: vi.fn().mockResolvedValue({}),
      },
      assignment: {
        findUnique: vi.fn().mockImplementation(({ where }: { where: { id: string } }) =>
          where.id === "assignment_1" ? explicitAssignment : null,
        ),
        updateMany: vi.fn().mockResolvedValue({ count: 0 }),
        delete: vi.fn().mockResolvedValue({}),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };

    const caller = createManagerCaller(db);
    const result = await caller.batchDelete({
      ids: ["demand_1", "assignment_1"],
    });

    expect(result.count).toBe(2);
    expect(db.assignment.updateMany).toHaveBeenCalledWith({
      where: { demandRequirementId: "demand_1" },
      data: { demandRequirementId: null },
    });
    expect(db.demandRequirement.delete).toHaveBeenCalledWith({
      where: { id: "demand_1" },
    });
    expect(db.assignment.delete).toHaveBeenCalledWith({
      where: { id: "assignment_1" },
    });
  });

  it("deletes an assignment through allocation.delete by its direct id", async () => {
    const existingAssignment = {
      id: "assignment_stale",
      demandRequirementId: null,
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 8,
      percentage: 100,
      role: "Compositor",
      roleId: "role_comp",
      dailyCostCents: 32000,
      status: AllocationStatus.ACTIVE,
      metadata: {},
      createdAt: new Date("2026-03-13"),
      updatedAt: new Date("2026-03-13"),
      resource: { id: "resource_1", displayName: "Alice", eid: "E-001", lcrCents: 5000 },
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_comp", name: "Compositor", color: "#111111" },
      demandRequirement: null,
    };

    const db = {
      demandRequirement: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
      assignment: {
        findUnique: vi.fn().mockImplementation(
          ({ where }: { where: { id?: string } }) => {
            if (where.id === "assignment_stale") {
              return existingAssignment;
            }

            return null;
          },
        ),
        delete: vi.fn().mockResolvedValue({}),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };

    const caller = createManagerCaller(db);
    const result = await caller.delete({
      id: "assignment_stale",
    });

    expect(result).toEqual({ success: true });
    expect(db.assignment.delete).toHaveBeenCalledWith({
      where: { id: "assignment_stale" },
    });
  });
});
