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

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-report-test-helpers.js";

describe("assistant chargeability report tools", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("returns the chargeability report readmodel through the assistant", async () => {
    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([]),
        },
      },
      {
        permissions: [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;
      explainability: {
        locationFields: string[];
        monthDerivationFields: string[];
        activeFilters: string[];
        formulas: {
          sah: string;
          chargeabilityPct: string;
          targetHours: string;
          gapHours: string;
        };
        notes: string[];
      };
      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.explainability).toEqual({
      locationFields: [
        "country",
        "federalState",
        "city",
        "orgUnit",
        "managementLevelGroup",
        "managementLevel",
      ],
      monthDerivationFields: [
        "baseAvailableHours",
        "publicHolidayCount",
        "publicHolidayWorkdayCount",
        "publicHolidayHoursDeduction",
        "absenceDayEquivalent",
        "absenceHoursDeduction",
        "effectiveAvailableHours",
      ],
      activeFilters: [],
      formulas: {
        sah: "baseAvailableHours - publicHolidayHoursDeduction - absenceHoursDeduction = effectiveAvailableHours",
        chargeabilityPct: "chargeabilityHours / sahHours",
        targetHours: "sahHours * targetPct",
        gapHours: "chargeabilityHours - targetHours",
      },
      notes: [
        "Location fields explain why two resources can have different SAH in the same month because country, federal state, and city holidays may differ.",
        "Holiday deductions and absence deductions are tracked separately; absence does not deduct days that are already public holidays.",
        "Include proposed work changes chargeability ratios and hours, but it does not change holiday or absence-based SAH derivation.",
      ],
    });
    expect(parsed.resources).toEqual([
      expect.objectContaining({
        displayName: "Alice",
        targetPct: 80,
        months: [
          expect.objectContaining({
            monthKey: "2026-03",
            sah: expect.any(Number),
            chargeabilityPct: expect.any(Number),
          }),
        ],
      }),
    ]);
  });
});
