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

vi.mock("@capakraken/application", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@capakraken/application")>();
  return {
    ...actual,
    approveEstimateVersion: vi.fn(),
    cloneEstimate: vi.fn(),
    commitDispoImportBatch: vi.fn(),
    countPlanningEntries: vi.fn().mockResolvedValue({ countsByRoleId: new Map() }),
    createEstimateExport: vi.fn(),
    createEstimatePlanningHandoff: vi.fn(),
    createEstimateRevision: vi.fn(),
    assessDispoImportReadiness: vi.fn(),
    loadResourceDailyAvailabilityContexts: vi.fn().mockResolvedValue(new Map()),
    getDashboardDemand: vi.fn().mockResolvedValue([]),
    getDashboardBudgetForecast: vi.fn().mockResolvedValue([]),
    getDashboardOverview: vi.fn(),
    getDashboardSkillGapSummary: vi.fn().mockResolvedValue({
      roleGaps: [],
      totalOpenPositions: 0,
      skillSupplyTop10: [],
      resourcesByRole: [],
    }),
    getDashboardProjectHealth: vi.fn().mockResolvedValue([]),
    getDashboardPeakTimes: vi.fn().mockResolvedValue([]),
    getDashboardTopValueResources: vi.fn().mockResolvedValue([]),
    getEstimateById: vi.fn(),
    listAssignmentBookings: vi.fn().mockResolvedValue([]),
    stageDispoImportBatch: vi.fn(),
    submitEstimateVersion: vi.fn(),
    updateEstimateDraft: vi.fn(),
  };
});

import { executeTool } from "../router/assistant-tools.js";
import { createToolContext } from "./assistant-tools-insights-scenarios-test-helpers.js";

describe("assistant tools workflow scenarios", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(listAssignmentBookings).mockResolvedValue([]);
  });

  it("capacity search then resource check: finds capacity and then checks a specific resource from the result", async () => {
    vi.mocked(listAssignmentBookings).mockResolvedValue([]);

    const resourceRecord = {
      id: "res_1",
      displayName: "Bruce Banner",
      eid: "EMP-001",
      fte: 1,
      availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, sunday: 0 },
      countryId: "country_de",
      federalState: null,
      metroCityId: null,
      country: { code: "DE", dailyWorkingHours: 8 },
      metroCity: null,
      areaRole: { name: "Pipeline TD" },
      chapter: "Delivery",
    };

    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue([resourceRecord]),
        findUnique: vi.fn().mockResolvedValue(resourceRecord),
        findFirst: vi.fn().mockResolvedValue(null),
      },
      assignment: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
    };

    const ctx = createToolContext(db, {
      userRole: SystemRole.CONTROLLER,
      permissions: [PermissionKey.VIEW_PLANNING],
    });

    // Step 1: find capacity for the period
    const capacityResult = await executeTool(
      "find_capacity",
      JSON.stringify({
        startDate: "2026-05-01",
        endDate: "2026-05-30",
        minHoursPerDay: 4,
        chapter: "Delivery",
      }),
      ctx,
    );

    const capacityParsed = JSON.parse(capacityResult.content) as {
      results: Array<{ id: string; name: string; eid: string }>;
      totalFound: number;
    };

    expect(db.resource.findMany).toHaveBeenCalled();
    expect(capacityParsed.results).toHaveLength(1);
    expect(capacityParsed.results[0].name).toBe("Bruce Banner");

    // Step 2: use resource ID from result to check availability in a specific date window
    const foundResourceId = capacityParsed.results[0].id;
    expect(foundResourceId).toBe("res_1");

    const availabilityResult = await executeTool(
      "check_resource_availability",
      JSON.stringify({
        resourceId: foundResourceId,
        startDate: "2026-05-05",
        endDate: "2026-05-09",
      }),
      ctx,
    );

    const availabilityParsed = JSON.parse(availabilityResult.content) as {
      workingDays: number;
      periodAvailableHours: number;
      periodBookedHours: number;
      periodRemainingHours: number;
      isFullyAvailable: boolean;
    };

    // 2026-05-05 (Tue) to 2026-05-09 (Sat) = 4 working days (Tue-Fri)
    expect(availabilityParsed.workingDays).toBe(4);
    expect(availabilityParsed.periodAvailableHours).toBe(32);
    expect(availabilityParsed.periodBookedHours).toBe(0);
    expect(availabilityParsed.periodRemainingHours).toBe(32);
    expect(availabilityParsed.isFullyAvailable).toBe(true);
  });

  it("vacation balance then upcoming vacations: retrieves balance and then lists upcoming vacations for the same resource", async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date("2026-05-01T00:00:00.000Z"));

    try {
      const db = {
        resource: {
          findUnique: vi.fn().mockResolvedValue({
            id: "res_2",
            eid: "EMP-002",
            displayName: "Tony Stark",
            userId: "user_2",
            chapter: "Engineering",
            federalState: null,
            countryId: "country_de",
            metroCityId: null,
            country: { code: "DE", name: "Germany" },
            metroCity: null,
          }),
        },
        holidayCalendar: {
          findMany: vi.fn().mockResolvedValue([]),
        },
        systemSettings: {
          findUnique: vi.fn().mockResolvedValue({ vacationDefaultDays: 28 }),
        },
        vacationEntitlement: {
          findUnique: vi.fn().mockResolvedValue(null),
          create: vi.fn().mockResolvedValue({
            id: "ent_2026_res2",
            resourceId: "res_2",
            year: 2026,
            entitledDays: 28,
            carryoverDays: 0,
            usedDays: 0,
            pendingDays: 0,
          }),
          update: vi.fn().mockResolvedValue({
            id: "ent_2026_res2",
            resourceId: "res_2",
            year: 2026,
            entitledDays: 28,
            carryoverDays: 0,
            usedDays: 0,
            pendingDays: 0,
          }),
        },
        vacation: {
          findMany: vi.fn().mockImplementation(async (args: { where?: { type?: unknown; status?: unknown; startDate?: unknown } } = {}) => {
            if (args?.where?.status === "APPROVED" && args?.where?.startDate) {
              // list_vacations_upcoming query
              return [
                {
                  id: "vac_1",
                  resourceId: "res_2",
                  status: "APPROVED",
                  type: "ANNUAL",
                  startDate: new Date("2026-05-10T00:00:00.000Z"),
                  endDate: new Date("2026-05-14T00:00:00.000Z"),
                  isHalfDay: false,
                  halfDayPart: null,
                  resource: {
                    id: "res_2",
                    displayName: "Tony Stark",
                    eid: "EMP-002",
                    lcrCents: 9_000,
                    chapter: "Engineering",
                  },
                  requestedBy: null,
                  approvedBy: null,
                },
              ];
            }
            // vacation balance queries (by type)
            return [];
          }),
        },
      };

      const ctx = createToolContext(db, { userRole: SystemRole.ADMIN });

      // Step 1: get vacation balance for the resource
      const balanceResult = await executeTool(
        "get_vacation_balance",
        JSON.stringify({ resourceId: "res_2", year: 2026 }),
        ctx,
      );

      const balanceParsed = JSON.parse(balanceResult.content) as {
        resource: string;
        eid: string;
        year: number;
        entitlement: number;
        remaining: number;
      };

      expect(balanceParsed.resource).toBe("Tony Stark");
      expect(balanceParsed.eid).toBe("EMP-002");
      expect(balanceParsed.year).toBe(2026);
      expect(balanceParsed.entitlement).toBe(28);
      expect(balanceParsed.remaining).toBe(28);

      // Step 2: list upcoming vacations for the same resource (filter by name)
      const upcomingResult = await executeTool(
        "list_vacations_upcoming",
        JSON.stringify({ resourceName: "Tony", daysAhead: 30, limit: 10 }),
        ctx,
      );

      const upcomingParsed = JSON.parse(upcomingResult.content) as Array<{
        resource: string;
        eid: string;
        start: string;
        end: string;
      }>;

      expect(upcomingParsed).toHaveLength(1);
      expect(upcomingParsed[0].resource).toBe("Tony Stark");
      expect(upcomingParsed[0].eid).toBe("EMP-002");
      expect(upcomingParsed[0].start).toBe("2026-05-10");
      expect(upcomingParsed[0].end).toBe("2026-05-14");
    } finally {
      vi.useRealTimers();
    }
  });

  it("demand listing then staffing suggestions: lists open demands and then retrieves suggestions for the project", async () => {
    vi.mocked(listAssignmentBookings).mockResolvedValue([]);

    const projectRecord = {
      id: "project_1",
      shortCode: "GDM",
      name: "Gelddruckmaschine",
      status: "ACTIVE",
      responsiblePerson: null,
      startDate: new Date("2026-06-01T00:00:00.000Z"),
      endDate: new Date("2026-06-30T00:00:00.000Z"),
    };

    const db = {
      demandRequirement: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "demand_1",
            projectId: "project_1",
            startDate: new Date("2026-06-01T00:00:00.000Z"),
            endDate: new Date("2026-06-30T00:00:00.000Z"),
            hoursPerDay: 8,
            percentage: 100,
            role: "Pipeline TD",
            roleId: null,
            headcount: 2,
            status: "OPEN",
            metadata: null,
            createdAt: new Date("2026-05-01T00:00:00.000Z"),
            updatedAt: new Date("2026-05-01T00:00:00.000Z"),
            project: {
              id: "project_1",
              name: "Gelddruckmaschine",
              shortCode: "GDM",
              status: "ACTIVE",
              endDate: new Date("2026-06-30T00:00:00.000Z"),
            },
            roleEntity: null,
            assignments: [],
          },
        ]),
      },
      project: {
        findUnique: vi.fn().mockResolvedValue(projectRecord),
        findFirst: vi.fn().mockResolvedValue(null),
      },
      resource: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "res_3",
            displayName: "Natasha Romanoff",
            eid: "EMP-003",
            fte: 1,
            chapter: "VFX",
            skills: [{ skill: "Houdini", category: "FX", proficiency: 4, isMainSkill: true }],
            lcrCents: 7_500,
            chargeabilityTarget: 80,
            availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8, saturday: 0, sunday: 0 },
            valueScore: 85,
            countryId: null,
            federalState: null,
            metroCityId: null,
            country: null,
            metroCity: null,
            areaRole: { name: "Pipeline TD" },
          },
        ]),
      },
      systemSettings: {
        findUnique: vi.fn().mockResolvedValue(null),
      },
    };

    const ctx = createToolContext(db, {
      userRole: SystemRole.CONTROLLER,
      permissions: [PermissionKey.VIEW_PLANNING, PermissionKey.VIEW_COSTS],
    });

    // Step 1: list demands (no status filter — AllocationStatus enum doesn't include OPEN)
    const demandsResult = await executeTool(
      "list_demands",
      JSON.stringify({}),
      ctx,
    );

    const demandsParsed = JSON.parse(demandsResult.content) as Array<{
      id: string;
      project: string;
      role: string;
      status: string;
      headcount: number;
      remaining: number;
    }>;

    expect(demandsParsed).toHaveLength(1);
    expect(demandsParsed[0].id).toBe("demand_1");
    expect(demandsParsed[0].project).toBe("Gelddruckmaschine");
    expect(demandsParsed[0].status).toBe("OPEN");
    expect(demandsParsed[0].headcount).toBe(2);
    expect(demandsParsed[0].remaining).toBe(2);

    // Step 2: get staffing suggestions for the project from the demand
    const projectId = demandsParsed[0].id.startsWith("demand_") ? "project_1" : demandsParsed[0].id;

    const suggestionsResult = await executeTool(
      "get_staffing_suggestions",
      JSON.stringify({
        projectId,
        startDate: "2026-06-01",
        endDate: "2026-06-30",
        limit: 5,
      }),
      ctx,
    );

    const suggestionsParsed = JSON.parse(suggestionsResult.content) as {
      project: string;
      suggestions: Array<{ id: string; name: string }>;
    };

    expect(suggestionsParsed.project).toBe("Gelddruckmaschine (GDM)");
    expect(Array.isArray(suggestionsParsed.suggestions)).toBe(true);
  });

  it("budget status check: retrieves budget utilization for a project", async () => {
    vi.mocked(listAssignmentBookings).mockResolvedValue([
      {
        projectId: "project_2",
        status: "CONFIRMED",
        dailyCostCents: 12_000,
        hoursPerDay: 8,
        startDate: new Date("2026-07-01T00:00:00.000Z"),
        endDate: new Date("2026-07-02T00:00:00.000Z"),
        project: {
          id: "project_2",
          status: "ACTIVE",
        },
      },
      {
        projectId: "project_2",
        status: "CONFIRMED",
        dailyCostCents: 8_000,
        hoursPerDay: 8,
        startDate: new Date("2026-07-03T00:00:00.000Z"),
        endDate: new Date("2026-07-03T00:00:00.000Z"),
        project: {
          id: "project_2",
          status: "ACTIVE",
        },
      },
    ] as Awaited<ReturnType<typeof listAssignmentBookings>>);

    const db = {
      project: {
        findUnique: vi
          .fn()
          .mockResolvedValueOnce({
            id: "project_2",
            name: "Raketenbauprojekt",
            shortCode: "RBP",
            status: "ACTIVE",
            responsiblePerson: "Elon",
          })
          .mockResolvedValueOnce({
            id: "project_2",
            name: "Raketenbauprojekt",
            shortCode: "RBP",
            budgetCents: 500_000,
            winProbability: 100,
            startDate: new Date("2026-07-01T00:00:00.000Z"),
            endDate: new Date("2026-07-31T00:00:00.000Z"),
          }),
        findFirst: vi.fn(),
      },
      assignment: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    const ctx = createToolContext(db, {
      userRole: SystemRole.CONTROLLER,
      permissions: [PermissionKey.VIEW_COSTS],
    });

    const result = await executeTool(
      "get_budget_status",
      JSON.stringify({ projectId: "project_2" }),
      ctx,
    );

    const parsed = JSON.parse(result.content) as {
      project: string;
      code: string;
      budget: string;
      confirmed: string;
      proposed: string;
      allocated: string;
      remaining: string;
      utilization: string;
    };

    expect(vi.mocked(listAssignmentBookings)).toHaveBeenCalledWith(db, {
      projectIds: ["project_2"],
    });
    expect(parsed.project).toBe("Raketenbauprojekt");
    expect(parsed.code).toBe("RBP");
    expect(parsed.budget).toBe("5.000,00 EUR");
    // Booking 1: July 1-2 (2 days) × 12000 cents = 24000 cents = 240 EUR
    // Booking 2: July 3 (1 day) × 8000 cents = 8000 cents = 80 EUR
    // Total confirmed: 320 EUR
    expect(parsed.confirmed).toBe("320,00 EUR");
    expect(parsed.proposed).toBe("0,00 EUR");
    expect(parsed.allocated).toBe("320,00 EUR");
    expect(parsed.remaining).toBe("4.680,00 EUR");
    expect(parsed.utilization).toBe("6.4%");
  });

  it("search resources with availability filter: finds resources in a chapter", async () => {
    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "res_4",
            eid: "EMP-004",
            displayName: "Wanda Maximoff",
            chapter: "FX",
            fte: 1,
            lcrCents: 8_000,
            chargeabilityTarget: 80,
            isActive: true,
            areaRole: { name: "FX Artist" },
            country: { code: "DE", name: "Germany" },
            metroCity: null,
            orgUnit: null,
          },
          {
            id: "res_5",
            eid: "EMP-005",
            displayName: "Pietro Maximoff",
            chapter: "FX",
            fte: 0.8,
            lcrCents: 7_200,
            chargeabilityTarget: 75,
            isActive: true,
            areaRole: { name: "Motion Designer" },
            country: { code: "DE", name: "Germany" },
            metroCity: null,
            orgUnit: null,
          },
        ]),
      },
    };

    const ctx = createToolContext(db, {
      userRole: SystemRole.CONTROLLER,
      permissions: [PermissionKey.VIEW_ALL_RESOURCES],
    });

    const result = await executeTool(
      "search_resources",
      JSON.stringify({ orgUnit: undefined, country: "DE", limit: 10 }),
      ctx,
    );

    const parsed = JSON.parse(result.content) as Array<{
      id: string;
      name: string;
      eid: string;
      chapter: string;
      role: string;
    }>;

    expect(db.resource.findMany).toHaveBeenCalled();
    expect(parsed).toHaveLength(2);

    const wanda = parsed.find((r) => r.id === "res_4");
    expect(wanda).toBeDefined();
    expect(wanda?.name).toBe("Wanda Maximoff");
    expect(wanda?.eid).toBe("EMP-004");
    expect(wanda?.chapter).toBe("FX");
    expect(wanda?.role).toBe("FX Artist");

    const pietro = parsed.find((r) => r.id === "res_5");
    expect(pietro).toBeDefined();
    expect(pietro?.name).toBe("Pietro Maximoff");
    expect(pietro?.eid).toBe("EMP-005");
  });
});
