import { describe, expect, it, vi } from "vitest";
import { AllocationStatus, PermissionKey, SystemRole } from "@capakraken/shared";

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

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

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

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

import { executeTool, type ToolContext } from "../router/assistant-tools.js";

function createToolContext(
  db: Record<string, unknown>,
  permissions: PermissionKey[] = [],
  userRole: SystemRole = SystemRole.ADMIN,
): ToolContext {
  return {
    db: db as ToolContext["db"],
    userId: "user_1",
    userRole,
    permissions: new Set(permissions),
    session: {
      user: { email: "assistant@example.com", name: "Assistant User", image: null },
      expires: "2026-03-29T00:00:00.000Z",
    },
    dbUser: {
      id: "user_1",
      systemRole: userRole,
      permissionOverrides: null,
    },
    roleDefaults: null,
  };
}

describe("assistant advanced tools and scoping", () => {
  it("finds the best project resource with holiday-aware remaining capacity and LCR ranking", 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 ctx = createToolContext(
      {
        project: {
          findUnique: vi
            .fn()
            .mockResolvedValueOnce(null)
            .mockResolvedValueOnce({
              id: "project_lari",
              name: "Gelddruckmaschine",
              shortCode: "LARI",
              status: "ACTIVE",
              responsiblePerson: "Larissa Joos",
              startDate: new Date("2026-01-05T00:00:00.000Z"),
              endDate: new Date("2026-01-16T00:00:00.000Z"),
            })
            .mockResolvedValueOnce({
              id: "project_lari",
              name: "Gelddruckmaschine",
              shortCode: "LARI",
              status: "ACTIVE",
              responsiblePerson: "Larissa Joos",
            }),
          findFirst: vi.fn(),
        },
        assignment: {
          findMany: assignmentFindMany,
        },
        vacation: {
          findMany: vi.fn().mockResolvedValue([]),
        },
      },
      [PermissionKey.VIEW_COSTS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
    );

    const result = await executeTool(
      "find_best_project_resource",
      JSON.stringify({
        projectIdentifier: "LARI",
        startDate: "2026-01-05",
        endDate: "2026-01-16",
        minHoursPerDay: 3,
        rankingMode: "lowest_lcr",
      }),
      ctx,
    );

    const parsed = JSON.parse(result.content) as {
      project: { shortCode: string };
      candidateCount: number;
      bestMatch: {
        name: string;
        remainingHoursPerDay: number;
        lcrCents: number | null;
        federalState: string | null;
        metroCity: string | null;
        baseAvailableHours: number;
        holidaySummary: { count: number };
      };
      candidates: Array<{
        name: string;
        remainingHoursPerDay: number;
        workingDays: number;
        baseAvailableHours: number;
        holidaySummary: { count: number; hoursDeduction: number };
        capacityBreakdown: { holidayHoursDeduction: number };
      }>;
    };

    expect(parsed.project.shortCode).toBe("LARI");
    expect(parsed.candidateCount).toBe(2);
    expect(parsed.bestMatch).toEqual(
      expect.objectContaining({
        name: "Carol Danvers",
        remainingHoursPerDay: 6,
        lcrCents: 7664,
        federalState: "HH",
        metroCity: "Hamburg",
        baseAvailableHours: 80,
        holidaySummary: expect.objectContaining({ count: 0 }),
      }),
    );
    expect(parsed.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 }),
      }),
    ]);
  });

  it("requires the dedicated advanced assistant permission for the high-level resource tool", async () => {
    const ctx = createToolContext({}, [PermissionKey.VIEW_COSTS]);

    const result = await executeTool(
      "find_best_project_resource",
      JSON.stringify({ projectIdentifier: "LARI" }),
      ctx,
    );

    expect(JSON.parse(result.content)).toEqual(
      expect.objectContaining({
        error: expect.stringContaining(PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS),
      }),
    );
  });

  it("returns project shift preview details from the canonical timeline router", async () => {
    const projectFindUnique = vi.fn().mockImplementation((args: { where?: { id?: string; shortCode?: string }; select?: Record<string, unknown> }) => {
      if (args.where?.id === "GDM") {
        return Promise.resolve(null);
      }
      if (args.where?.shortCode === "GDM") {
        return Promise.resolve({
          id: "project_shift",
          name: "Gelddruckmaschine",
          shortCode: "GDM",
          status: "ACTIVE",
          responsiblePerson: "Larissa",
          startDate: new Date("2026-01-05T00:00:00.000Z"),
          endDate: new Date("2026-01-16T00:00:00.000Z"),
        });
      }
      if (args.select && "budgetCents" in args.select) {
        return Promise.resolve({
          id: "project_shift",
          budgetCents: 100000,
          winProbability: 100,
          startDate: new Date("2026-01-05T00:00:00.000Z"),
          endDate: new Date("2026-01-16T00:00:00.000Z"),
        });
      }

      return Promise.resolve({
        id: "project_shift",
        name: "Gelddruckmaschine",
        shortCode: "GDM",
        status: "ACTIVE",
        responsiblePerson: "Larissa",
        startDate: new Date("2026-01-05T00:00:00.000Z"),
        endDate: new Date("2026-01-16T00:00:00.000Z"),
      });
    });

    const ctx = createToolContext(
      {
        project: {
          findUnique: projectFindUnique,
          findFirst: vi.fn(),
        },
        demandRequirement: {
          findMany: vi.fn().mockResolvedValue([]),
        },
        assignment: {
          findMany: vi.fn().mockResolvedValue([]),
        },
      },
      [PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
    );

    const result = await executeTool(
      "preview_project_shift",
      JSON.stringify({
        projectIdentifier: "GDM",
        newStartDate: "2026-01-19",
        newEndDate: "2026-01-30",
      }),
      ctx,
    );

    expect(JSON.parse(result.content)).toEqual({
      project: {
        id: "project_shift",
        name: "Gelddruckmaschine",
        shortCode: "GDM",
        status: "ACTIVE",
        responsiblePerson: "Larissa",
        startDate: "2026-01-05",
        endDate: "2026-01-16",
      },
      requestedShift: {
        newStartDate: "2026-01-19",
        newEndDate: "2026-01-30",
      },
      preview: {
        valid: true,
        errors: [],
        warnings: [],
        conflictDetails: [],
        costImpact: {
          currentTotalCents: 0,
          newTotalCents: 0,
          deltaCents: 0,
          budgetCents: 100000,
          budgetUtilizationBefore: 0,
          budgetUtilizationAfter: 0,
          wouldExceedBudget: false,
        },
      },
    });
  });

  it("returns timeline entries view with demand, assignment, and holiday overlay context", async () => {
    const ctx = createToolContext(
      {
        demandRequirement: {
          findMany: vi.fn().mockResolvedValue([
            {
              id: "dem_1",
              projectId: "project_1",
              resourceId: null,
              role: "Artist",
              hoursPerDay: 8,
              startDate: new Date("2026-01-05T00:00:00.000Z"),
              endDate: new Date("2026-01-09T00:00:00.000Z"),
              status: "OPEN",
              metadata: null,
              project: {
                id: "project_1",
                name: "Gelddruckmaschine",
                shortCode: "GDM",
                orderType: "CHARGEABLE",
                clientId: "client_1",
                budgetCents: 0,
                winProbability: 100,
                status: "ACTIVE",
                startDate: new Date("2026-01-05T00:00:00.000Z"),
                endDate: new Date("2026-01-16T00:00:00.000Z"),
                staffingReqs: null,
                responsiblePerson: "Larissa",
                color: "#fff",
              },
              roleEntity: null,
            },
          ]),
        },
        assignment: {
          findMany: vi.fn().mockResolvedValue([
            {
              id: "asg_by",
              projectId: "project_1",
              resourceId: "res_by",
              role: "Artist",
              hoursPerDay: 8,
              startDate: new Date("2026-01-05T00:00:00.000Z"),
              endDate: new Date("2026-01-09T00:00:00.000Z"),
              status: "CONFIRMED",
              metadata: null,
              resource: {
                id: "res_by",
                displayName: "Bayern User",
                eid: "EMP-BY",
                chapter: "Delivery",
                lcrCents: 10000,
              },
              project: {
                id: "project_1",
                name: "Gelddruckmaschine",
                shortCode: "GDM",
                orderType: "CHARGEABLE",
                clientId: "client_1",
                budgetCents: 0,
                winProbability: 100,
                status: "ACTIVE",
                startDate: new Date("2026-01-05T00:00:00.000Z"),
                endDate: new Date("2026-01-16T00:00:00.000Z"),
                staffingReqs: null,
                responsiblePerson: "Larissa",
                color: "#fff",
              },
              roleEntity: null,
            },
            {
              id: "asg_hh",
              projectId: "project_1",
              resourceId: "res_hh",
              role: "Artist",
              hoursPerDay: 8,
              startDate: new Date("2026-01-05T00:00:00.000Z"),
              endDate: new Date("2026-01-09T00:00:00.000Z"),
              status: "CONFIRMED",
              metadata: null,
              resource: {
                id: "res_hh",
                displayName: "Hamburg User",
                eid: "EMP-HH",
                chapter: "Delivery",
                lcrCents: 10000,
              },
              project: {
                id: "project_1",
                name: "Gelddruckmaschine",
                shortCode: "GDM",
                orderType: "CHARGEABLE",
                clientId: "client_1",
                budgetCents: 0,
                winProbability: 100,
                status: "ACTIVE",
                startDate: new Date("2026-01-05T00:00:00.000Z"),
                endDate: new Date("2026-01-16T00:00:00.000Z"),
                staffingReqs: null,
                responsiblePerson: "Larissa",
                color: "#fff",
              },
              roleEntity: null,
            },
          ]),
        },
        resource: {
          findMany: vi.fn().mockResolvedValue([
            {
              id: "res_by",
              countryId: "country_de",
              federalState: "BY",
              metroCityId: "city_munich",
              country: { code: "DE" },
              metroCity: { name: "Muenchen" },
            },
            {
              id: "res_hh",
              countryId: "country_de",
              federalState: "HH",
              metroCityId: "city_hamburg",
              country: { code: "DE" },
              metroCity: { name: "Hamburg" },
            },
          ]),
        },
        project: {
          findMany: vi.fn(),
        },
      },
      [PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
    );

    const result = await executeTool(
      "get_timeline_entries_view",
      JSON.stringify({
        startDate: "2026-01-05",
        endDate: "2026-01-09",
        projectIds: ["project_1"],
      }),
      ctx,
    );

    const parsed = JSON.parse(result.content) as {
      summary: {
        demandCount: number;
        assignmentCount: number;
        overlayCount: number;
        resourceCount: number;
      };
      demands: Array<{ id: string }>;
      assignments: Array<{ id: string }>;
      holidayOverlays: Array<{ resourceId: string; startDate: string; note: string; scope: string }>;
    };

    expect(parsed.summary).toEqual(
      expect.objectContaining({
        demandCount: 1,
        assignmentCount: 2,
        overlayCount: 1,
        resourceCount: 2,
      }),
    );
    expect(parsed.demands).toHaveLength(1);
    expect(parsed.assignments).toHaveLength(2);
    expect(parsed.holidayOverlays).toEqual([
      expect.objectContaining({
        resourceId: "res_by",
        startDate: "2026-01-06",
        note: "Heilige Drei Könige",
        scope: "STATE",
      }),
    ]);
  });

  it("returns project timeline context with cross-project overlap summaries", async () => {
    const project = {
      id: "project_ctx",
      name: "Gelddruckmaschine",
      shortCode: "GDM",
      orderType: "CHARGEABLE",
      budgetCents: 100000,
      winProbability: 100,
      status: "ACTIVE",
      startDate: new Date("2026-01-05T00:00:00.000Z"),
      endDate: new Date("2026-01-16T00:00:00.000Z"),
      staffingReqs: null,
    };

    const { listAssignmentBookings } = await import("@capakraken/application");
    vi.mocked(listAssignmentBookings).mockResolvedValue([
      {
        id: "asg_project",
        projectId: "project_ctx",
        resourceId: "res_1",
        startDate: new Date("2026-01-05T00:00:00.000Z"),
        endDate: new Date("2026-01-16T00:00:00.000Z"),
        hoursPerDay: 6,
        dailyCostCents: 0,
        status: "CONFIRMED",
        project: { id: "project_ctx", name: "Gelddruckmaschine", shortCode: "GDM", status: "ACTIVE", orderType: "CHARGEABLE", dynamicFields: null },
        resource: { id: "res_1", displayName: "Alice", chapter: "Delivery" },
      },
      {
        id: "asg_other",
        projectId: "project_other",
        resourceId: "res_1",
        startDate: new Date("2026-01-08T00:00:00.000Z"),
        endDate: new Date("2026-01-10T00:00:00.000Z"),
        hoursPerDay: 4,
        dailyCostCents: 0,
        status: "CONFIRMED",
        project: { id: "project_other", name: "Other Project", shortCode: "OTH", status: "ACTIVE", orderType: "CHARGEABLE", dynamicFields: null },
        resource: { id: "res_1", displayName: "Alice", chapter: "Delivery" },
      },
    ]);

    const ctx = createToolContext(
      {
        project: {
          findUnique: vi
            .fn()
            .mockResolvedValueOnce(project)
            .mockResolvedValueOnce(project),
        },
        demandRequirement: {
          findMany: vi.fn().mockResolvedValue([
            {
              id: "dem_ctx",
              projectId: "project_ctx",
              resourceId: null,
              role: "Artist",
              hoursPerDay: 6,
              startDate: new Date("2026-01-05T00:00:00.000Z"),
              endDate: new Date("2026-01-16T00:00:00.000Z"),
              status: "OPEN",
              metadata: null,
              project,
              roleEntity: null,
            },
          ]),
        },
        assignment: {
          findMany: vi.fn().mockResolvedValue([
            {
              id: "asg_project",
              projectId: "project_ctx",
              resourceId: "res_1",
              role: "Artist",
              hoursPerDay: 6,
              startDate: new Date("2026-01-05T00:00:00.000Z"),
              endDate: new Date("2026-01-16T00:00:00.000Z"),
              status: "CONFIRMED",
              metadata: null,
              resource: {
                id: "res_1",
                displayName: "Alice",
                eid: "EMP-1",
                chapter: "Delivery",
                lcrCents: 10000,
                availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
              },
              project,
              roleEntity: null,
            },
          ]),
        },
        resource: {
          findMany: vi.fn().mockResolvedValue([
            {
              id: "res_1",
              countryId: "country_de",
              federalState: "BY",
              metroCityId: "city_munich",
              country: { code: "DE" },
              metroCity: { name: "Muenchen" },
            },
          ]),
        },
      },
      [PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
    );

    const result = await executeTool(
      "get_project_timeline_context",
      JSON.stringify({
        projectIdentifier: "project_ctx",
      }),
      ctx,
    );

    const parsed = JSON.parse(result.content) as {
      project: { id: string; shortCode: string };
      summary: {
        demandCount: number;
        assignmentCount: number;
        conflictedAssignmentCount: number;
        overlayCount: number;
      };
      assignmentConflicts: Array<{
        assignmentId: string;
        crossProjectOverlapCount: number;
        overlaps: Array<{ projectShortCode: string; sameProject: boolean }>;
      }>;
      holidayOverlays: Array<{ startDate: string }>;
    };

    expect(parsed.project).toEqual(
      expect.objectContaining({
        id: "project_ctx",
        shortCode: "GDM",
      }),
    );
    expect(parsed.summary).toEqual(
      expect.objectContaining({
        demandCount: 1,
        assignmentCount: 1,
        conflictedAssignmentCount: 1,
        overlayCount: 1,
      }),
    );
    expect(parsed.assignmentConflicts).toEqual([
      expect.objectContaining({
        assignmentId: "asg_project",
        crossProjectOverlapCount: 1,
        overlaps: expect.arrayContaining([
          expect.objectContaining({
            projectShortCode: "OTH",
            sameProject: false,
          }),
        ]),
      }),
    ]);
    expect(parsed.holidayOverlays).toEqual([
      expect.objectContaining({
        startDate: "2026-01-06",
      }),
    ]);
  });

  it("updates timeline allocations inline through the real timeline router mutation", async () => {
    const existingAssignment = {
      id: "assignment_1",
      demandRequirementId: null,
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 4,
      percentage: 50,
      role: "Compositor",
      roleId: "role_comp",
      dailyCostCents: 20000,
      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: 5000,
        availability: {
          monday: 8,
          tuesday: 8,
          wednesday: 8,
          thursday: 8,
          friday: 8,
          saturday: 0,
          sunday: 0,
        },
      },
      project: { id: "project_1", name: "Project One", shortCode: "PRJ" },
      roleEntity: { id: "role_comp", name: "Compositor", color: "#111111" },
      demandRequirement: null,
    };
    const updatedAssignment = {
      ...existingAssignment,
      hoursPerDay: 6,
      endDate: new Date("2026-03-21"),
      percentage: 75,
      dailyCostCents: 30000,
      metadata: { includeSaturday: true },
      updatedAt: new Date("2026-03-14"),
    };

    const db = {
      allocation: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
      demandRequirement: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
      assignment: {
        findUnique: vi.fn().mockResolvedValue(existingAssignment),
        update: vi.fn().mockResolvedValue(updatedAssignment),
      },
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          id: "resource_1",
          eid: "E-001",
          displayName: "Alice",
          lcrCents: 5000,
          availability: {
            monday: 8,
            tuesday: 8,
            wednesday: 8,
            thursday: 8,
            friday: 8,
            saturday: 0,
            sunday: 0,
          },
        }),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };
    const ctx = createToolContext(
      db,
      [PermissionKey.MANAGE_ALLOCATIONS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
      SystemRole.MANAGER,
    );

    const result = await executeTool(
      "update_timeline_allocation_inline",
      JSON.stringify({
        allocationId: "assignment_1",
        hoursPerDay: 6,
        endDate: "2026-03-21",
        includeSaturday: true,
      }),
      ctx,
    );

    expect(result.action).toEqual({ type: "invalidate", scope: ["allocation", "timeline", "project"] });
    expect(JSON.parse(result.content)).toEqual(
      expect.objectContaining({
        success: true,
        allocation: expect.objectContaining({
          id: "assignment_1",
          hoursPerDay: 6,
          endDate: "2026-03-21",
        }),
      }),
    );
    expect(db.assignment.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: "assignment_1" },
      }),
    );
  });

  it("quick-assigns a timeline resource through the real timeline router mutation", async () => {
    const createdAssignment = {
      id: "assignment_quick_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: "Team Member",
      roleId: null,
      dailyCostCents: 40000,
      status: AllocationStatus.PROPOSED,
      metadata: { source: "quickAssign" },
      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: "Gelddruckmaschine", shortCode: "GDM" },
      roleEntity: null,
      demandRequirement: null,
    };

    const db = {
      project: {
        findUnique: vi.fn().mockResolvedValue({
          id: "project_1",
          name: "Gelddruckmaschine",
          shortCode: "GDM",
          status: "ACTIVE",
          responsiblePerson: null,
        }),
      },
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          id: "resource_1",
          eid: "E-001",
          displayName: "Alice",
          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 ctx = createToolContext(
      db,
      [PermissionKey.MANAGE_ALLOCATIONS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
      SystemRole.MANAGER,
    );

    const result = await executeTool(
      "quick_assign_timeline_resource",
      JSON.stringify({
        resourceIdentifier: "resource_1",
        projectIdentifier: "project_1",
        startDate: "2026-03-16",
        endDate: "2026-03-20",
        hoursPerDay: 8,
      }),
      ctx,
    );

    expect(result.action).toEqual({ type: "invalidate", scope: ["allocation", "timeline", "project"] });
    expect(JSON.parse(result.content)).toEqual(
      expect.objectContaining({
        success: true,
        allocation: expect.objectContaining({
          id: "assignment_quick_1",
          projectId: "project_1",
          resourceId: "resource_1",
          hoursPerDay: 8,
        }),
      }),
    );
    expect(db.assignment.create).toHaveBeenCalled();
  });

  it("batch quick-assigns timeline resources through the real timeline router mutation", async () => {
    const db = {
      project: {
        findUnique: vi.fn().mockResolvedValue({
          id: "project_1",
          name: "Gelddruckmaschine",
          shortCode: "GDM",
          status: "ACTIVE",
          responsiblePerson: null,
        }),
      },
      resource: {
        findUnique: vi.fn().mockImplementation(async ({ where }: { where: { id: string } }) => ({
          id: where.id,
          eid: `E-${where.id}`,
          displayName: `Resource ${where.id}`,
          lcrCents: 5000,
          availability: {
            monday: 8,
            tuesday: 8,
            wednesday: 8,
            thursday: 8,
            friday: 8,
            saturday: 0,
            sunday: 0,
          },
        })),
      },
      assignment: {
        findMany: vi.fn().mockResolvedValue([]),
        create: vi
          .fn()
          .mockResolvedValueOnce({ id: "assignment_batch_1" })
          .mockResolvedValueOnce({ id: "assignment_batch_2" }),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };
    const ctx = createToolContext(
      db,
      [PermissionKey.MANAGE_ALLOCATIONS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
      SystemRole.MANAGER,
    );

    const result = await executeTool(
      "batch_quick_assign_timeline_resources",
      JSON.stringify({
        assignments: [
          {
            resourceIdentifier: "resource_1",
            projectIdentifier: "project_1",
            startDate: "2026-03-16",
            endDate: "2026-03-20",
            hoursPerDay: 8,
          },
          {
            resourceIdentifier: "resource_2",
            projectIdentifier: "project_1",
            startDate: "2026-03-23",
            endDate: "2026-03-27",
            hoursPerDay: 6,
          },
        ],
      }),
      ctx,
    );

    expect(result.action).toEqual({ type: "invalidate", scope: ["allocation", "timeline", "project"] });
    expect(JSON.parse(result.content)).toEqual(
      expect.objectContaining({
        success: true,
        count: 2,
      }),
    );
    expect(db.assignment.create).toHaveBeenCalledTimes(2);
  });

  it("applies timeline project shifts through the real timeline router mutation", async () => {
    const { listAssignmentBookings } = await import("@capakraken/application");
    vi.mocked(listAssignmentBookings).mockResolvedValueOnce([]);

    const db = {
      project: {
        findUnique: vi.fn().mockResolvedValue({
          id: "project_1",
          name: "Gelddruckmaschine",
          shortCode: "GDM",
          status: "ACTIVE",
          responsiblePerson: null,
          budgetCents: 100000,
          winProbability: 100,
          startDate: new Date("2026-03-16"),
          endDate: new Date("2026-03-20"),
        }),
        update: vi.fn().mockResolvedValue({
          id: "project_1",
          startDate: new Date("2026-03-23"),
          endDate: new Date("2026-03-27"),
        }),
      },
      demandRequirement: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      assignment: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };
    const ctx = createToolContext(
      db,
      [PermissionKey.MANAGE_ALLOCATIONS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
      SystemRole.MANAGER,
    );

    const result = await executeTool(
      "apply_timeline_project_shift",
      JSON.stringify({
        projectIdentifier: "project_1",
        newStartDate: "2026-03-23",
        newEndDate: "2026-03-27",
      }),
      ctx,
    );

    expect(result.action).toEqual({ type: "invalidate", scope: ["allocation", "timeline", "project"] });
    expect(JSON.parse(result.content)).toEqual(
      expect.objectContaining({
        success: true,
        project: expect.objectContaining({
          id: "project_1",
          startDate: "2026-03-23",
          endDate: "2026-03-27",
        }),
        validation: expect.objectContaining({
          valid: true,
        }),
      }),
    );
    expect(db.project.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: "project_1" },
      }),
    );
  });

  it("batch-shifts timeline allocations through the real timeline router mutation", async () => {
    const existingAssignment = {
      id: "assignment_1",
      demandRequirementId: null,
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-03-16"),
      endDate: new Date("2026-03-20"),
      hoursPerDay: 4,
      percentage: 50,
      role: "Compositor",
      roleId: "role_comp",
      dailyCostCents: 20000,
      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: 5000,
        availability: {
          monday: 8,
          tuesday: 8,
          wednesday: 8,
          thursday: 8,
          friday: 8,
          saturday: 0,
          sunday: 0,
        },
      },
      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().mockResolvedValue(null),
      },
      assignment: {
        findUnique: vi.fn().mockResolvedValue(existingAssignment),
        update: vi.fn().mockResolvedValue({
          ...existingAssignment,
          startDate: new Date("2026-03-18"),
          endDate: new Date("2026-03-22"),
        }),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      $transaction: vi.fn(async (callback: (tx: unknown) => unknown) => callback(db)),
    };
    const ctx = createToolContext(
      db,
      [PermissionKey.MANAGE_ALLOCATIONS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
      SystemRole.MANAGER,
    );

    const result = await executeTool(
      "batch_shift_timeline_allocations",
      JSON.stringify({
        allocationIds: ["assignment_1"],
        daysDelta: 2,
        mode: "move",
      }),
      ctx,
    );

    expect(result.action).toEqual({ type: "invalidate", scope: ["allocation", "timeline", "project"] });
    expect(JSON.parse(result.content)).toEqual(
      expect.objectContaining({
        success: true,
        count: 1,
      }),
    );
    expect(db.assignment.update).toHaveBeenCalledWith(
      expect.objectContaining({
        where: { id: "assignment_1" },
      }),
    );
  });

  it("returns the chargeability report readmodel through the assistant", async () => {
    const { listAssignmentBookings } = await import("@capakraken/application");
    vi.mocked(listAssignmentBookings).mockResolvedValue([
      {
        id: "assignment_confirmed",
        projectId: "project_confirmed",
        resourceId: "resource_1",
        startDate: new Date("2026-03-02T00:00:00.000Z"),
        endDate: new Date("2026-03-06T00:00:00.000Z"),
        hoursPerDay: 4,
        dailyCostCents: 0,
        status: "CONFIRMED",
        project: {
          id: "project_confirmed",
          name: "Confirmed Project",
          shortCode: "CP",
          status: "ACTIVE",
          orderType: "CLIENT",
          dynamicFields: null,
        },
        resource: { id: "resource_1", displayName: "Alice", chapter: "CGI" },
      },
    ]);

    const ctx = createToolContext(
      {
        resource: {
          findMany: vi.fn().mockResolvedValue([
            {
              id: "resource_1",
              eid: "E-001",
              displayName: "Alice",
              fte: 1,
              availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
              countryId: "country_es",
              federalState: null,
              metroCityId: "city_1",
              chargeabilityTarget: 80,
              country: {
                id: "country_es",
                code: "ES",
                dailyWorkingHours: 8,
                scheduleRules: null,
              },
              orgUnit: { id: "org_1", name: "CGI" },
              managementLevelGroup: { id: "mgmt_1", name: "Senior", targetPercentage: 0.8 },
              managementLevel: { id: "level_1", name: "L7" },
              metroCity: { id: "city_1", name: "Barcelona" },
            },
          ]),
        },
        project: {
          findMany: vi.fn().mockResolvedValue([
            { id: "project_confirmed", utilizationCategory: { code: "Chg" } },
          ]),
        },
        vacation: {
          findMany: vi.fn().mockResolvedValue([]),
        },
        holidayCalendar: {
          findMany: vi.fn().mockResolvedValue([]),
        },
      },
      [PermissionKey.VIEW_COSTS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
    );

    const result = await executeTool(
      "get_chargeability_report",
      JSON.stringify({
        startMonth: "2026-03",
        endMonth: "2026-03",
        resourceLimit: 10,
      }),
      ctx,
    );

    const parsed = JSON.parse(result.content) as {
      monthKeys: string[];
      groupTotals: Array<{ monthKey: string; chargeabilityPct: number; targetPct: number }>;
      resourceCount: number;
      returnedResourceCount: number;
      truncated: boolean;
      resources: Array<{
        displayName: string;
        targetPct: number;
        months: Array<{ monthKey: string; sah: number; chargeabilityPct: number }>;
      }>;
    };

    expect(parsed.monthKeys).toEqual(["2026-03"]);
    expect(parsed.groupTotals).toEqual([
      expect.objectContaining({
        monthKey: "2026-03",
        chargeabilityPct: expect.any(Number),
        targetPct: 80,
      }),
    ]);
    expect(parsed.resourceCount).toBe(1);
    expect(parsed.returnedResourceCount).toBe(1);
    expect(parsed.truncated).toBe(false);
    expect(parsed.resources).toEqual([
      expect.objectContaining({
        displayName: "Alice",
        targetPct: 80,
        months: [
          expect.objectContaining({
            monthKey: "2026-03",
            sah: expect.any(Number),
            chargeabilityPct: expect.any(Number),
          }),
        ],
      }),
    ]);
  });

  it("returns a filtered resource computation graph through the assistant", async () => {
    const resourceRecord = {
      id: "resource_augsburg",
      displayName: "Bruce Banner",
      eid: "bruce.banner",
      fte: 1,
      lcrCents: 5_000,
      chargeabilityTarget: 80,
      countryId: "country_de",
      federalState: "BY",
      metroCityId: "city_augsburg",
      availability: {
        monday: 8,
        tuesday: 8,
        wednesday: 8,
        thursday: 8,
        friday: 8,
        saturday: 0,
        sunday: 0,
      },
      country: {
        id: "country_de",
        code: "DE",
        name: "Deutschland",
        dailyWorkingHours: 8,
        scheduleRules: null,
      },
      metroCity: { id: "city_augsburg", name: "Augsburg" },
      managementLevelGroup: {
        id: "mlg_1",
        name: "Senior",
        targetPercentage: 0.8,
      },
    };

    const ctx = createToolContext(
      {
        resource: {
          findUnique: vi.fn().mockResolvedValue(resourceRecord),
          findFirst: vi.fn(),
          findUniqueOrThrow: vi.fn().mockResolvedValue(resourceRecord),
        },
        assignment: {
          findMany: vi.fn().mockResolvedValue([]),
        },
        vacation: {
          findMany: vi.fn().mockResolvedValue([]),
        },
        holidayCalendar: {
          findMany: vi.fn().mockResolvedValue([]),
        },
        calculationRule: {
          findMany: vi.fn().mockResolvedValue([]),
        },
      },
      [PermissionKey.VIEW_COSTS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
    );

    const result = await executeTool(
      "get_resource_computation_graph",
      JSON.stringify({
        resourceId: "resource_augsburg",
        month: "2026-08",
        domain: "SAH",
      }),
      ctx,
    );

    const parsed = JSON.parse(result.content) as {
      resource: { id: string; displayName: string };
      requestedDomain: string;
      totalNodeCount: number;
      selectedNodeCount: number;
      nodes: Array<{ id: string; domain: string }>;
      meta: {
        countryCode: string | null;
        federalState: string | null;
        metroCityName: string | null;
        resolvedHolidays: Array<{ name: string; scope: string }>;
      };
    };

    expect(parsed.resource).toEqual({
      id: "resource_augsburg",
      eid: "bruce.banner",
      displayName: "Bruce Banner",
    });
    expect(parsed.requestedDomain).toBe("SAH");
    expect(parsed.totalNodeCount).toBeGreaterThan(parsed.selectedNodeCount);
    expect(parsed.selectedNodeCount).toBeGreaterThan(0);
    expect(parsed.nodes.every((node) => node.domain === "SAH")).toBe(true);
    expect(parsed.meta).toMatchObject({
      countryCode: "DE",
      federalState: "BY",
      metroCityName: "Augsburg",
    });
    expect(parsed.meta.resolvedHolidays).toEqual(expect.arrayContaining([
      expect.objectContaining({
        name: "Augsburger Friedensfest",
        scope: "CITY",
      }),
    ]));
  });

  it("returns a filtered project computation graph through the assistant", async () => {
    const projectRecord = {
      id: "project_1",
      name: "Gelddruckmaschine",
      shortCode: "GDM",
      budgetCents: 100_000,
      winProbability: 75,
      startDate: new Date("2026-01-05T00:00:00.000Z"),
      endDate: new Date("2026-02-28T00:00:00.000Z"),
      status: "ACTIVE",
      responsiblePerson: "Larissa Joos",
    };

    const ctx = createToolContext(
      {
        project: {
          findUnique: vi.fn().mockResolvedValue(projectRecord),
          findFirst: vi.fn(),
          findUniqueOrThrow: vi.fn().mockResolvedValue(projectRecord),
        },
        estimate: {
          findFirst: vi.fn().mockResolvedValue(null),
        },
        assignment: {
          findMany: vi.fn().mockResolvedValue([
            {
              status: "CONFIRMED",
              dailyCostCents: 4_000,
              startDate: new Date("2026-01-05T00:00:00.000Z"),
              endDate: new Date("2026-01-30T00:00:00.000Z"),
              hoursPerDay: 4,
            },
          ]),
        },
        effortRule: {
          count: vi.fn().mockResolvedValue(0),
        },
        experienceMultiplierRule: {
          count: vi.fn().mockResolvedValue(0),
        },
      },
      [PermissionKey.VIEW_COSTS, PermissionKey.USE_ASSISTANT_ADVANCED_TOOLS],
    );

    const result = await executeTool(
      "get_project_computation_graph",
      JSON.stringify({
        projectId: "project_1",
        domain: "BUDGET",
        includeLinks: true,
      }),
      ctx,
    );

    const parsed = JSON.parse(result.content) as {
      project: { id: string; shortCode: string; name: string };
      requestedDomain: string;
      totalNodeCount: number;
      selectedNodeCount: number;
      selectedLinkCount: number;
      nodes: Array<{ id: string; domain: string }>;
      links: Array<{ source: string; target: string }>;
      meta: { projectName: string; projectCode: string };
    };

    expect(parsed.project).toEqual({
      id: "project_1",
      shortCode: "GDM",
      name: "Gelddruckmaschine",
    });
    expect(parsed.meta).toEqual({
      projectName: "Gelddruckmaschine",
      projectCode: "GDM",
    });
    expect(parsed.requestedDomain).toBe("BUDGET");
    expect(parsed.totalNodeCount).toBeGreaterThan(parsed.selectedNodeCount);
    expect(parsed.selectedNodeCount).toBeGreaterThan(0);
    expect(parsed.selectedLinkCount).toBeGreaterThan(0);
    expect(parsed.nodes.every((node) => node.domain === "BUDGET")).toBe(true);
    expect(parsed.links.length).toBe(parsed.selectedLinkCount);
  });

  it("scopes assistant notification listing to the current user through the router path", async () => {
    const findMany = vi.fn().mockResolvedValue([]);
    const ctx = createToolContext({
      user: {
        findUnique: vi.fn().mockResolvedValue({ id: "user_1" }),
      },
      notification: {
        findMany,
      },
    });

    await executeTool("list_notifications", JSON.stringify({ unreadOnly: true }), ctx);

    expect(findMany).toHaveBeenCalledWith(
      expect.objectContaining({
        where: expect.objectContaining({
          userId: "user_1",
          readAt: null,
        }),
      }),
    );
  });

  it("scopes mark_notification_read mutations to the current user through the router path", async () => {
    const update = vi.fn();
    const ctx = createToolContext({
      user: {
        findUnique: vi.fn().mockResolvedValue({ id: "user_1" }),
      },
      notification: {
        update,
      },
    });

    await executeTool(
      "mark_notification_read",
      JSON.stringify({ notificationId: "notif_1" }),
      ctx,
    );

    expect(update).toHaveBeenCalledWith({
      where: { id: "notif_1", userId: "user_1" },
      data: expect.objectContaining({
        readAt: expect.any(Date),
      }),
    });
  });

  it("requires admin role before listing users through the assistant", async () => {
    const findMany = vi.fn();
    const ctx = createToolContext({
      user: {
        findMany,
      },
    }, [], SystemRole.MANAGER);

    const result = await executeTool("list_users", JSON.stringify({ limit: 10 }), ctx);

    expect(JSON.parse(result.content)).toEqual(
      expect.objectContaining({
        error: expect.stringContaining("Admin role required"),
      }),
    );
    expect(findMany).not.toHaveBeenCalled();
  });
});
