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

vi.mock("@capakraken/application", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@capakraken/application")>();
  return {
    ...actual,
    getDashboardOverview: vi.fn(),
    getDashboardPeakTimes: vi.fn(),
    getDashboardDemand: vi.fn(),
    getDashboardTopValueResources: vi.fn(),
    getDashboardChargeabilityOverview: vi.fn(),
    getDashboardBudgetForecast: vi.fn(),
    getDashboardSkillGapSummary: vi.fn(),
    getDashboardProjectHealth: vi.fn(),
  };
});

vi.mock("../lib/cache.js", () => ({
  cacheGet: vi.fn().mockResolvedValue(null),
  cacheSet: vi.fn().mockResolvedValue(undefined),
}));

vi.mock("../lib/anonymization.js", () => ({
  anonymizeResources: vi.fn((resources: unknown[]) => resources),
  getAnonymizationDirectory: vi.fn().mockResolvedValue(null),
}));

import {
  getDashboardOverview,
  getDashboardPeakTimes,
  getDashboardDemand,
  getDashboardTopValueResources,
  getDashboardChargeabilityOverview,
  getDashboardBudgetForecast,
  getDashboardProjectHealth,
  getDashboardSkillGapSummary,
} from "@capakraken/application";
import { dashboardRouter } from "../router/dashboard.js";
import { createCallerFactory } from "../trpc.js";

const createCaller = createCallerFactory(dashboardRouter);

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

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

function createUnauthenticatedCaller(db: Record<string, unknown>) {
  return createCaller({
    session: null,
    db: db as never,
    dbUser: null,
  });
}

describe("dashboard router", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  // ─── getOverview ──────────────────────────────────────────────────────────

  describe("getOverview", () => {
    it("returns expected shape with resource and project counts", async () => {
      const overview = {
        totalResources: 42,
        activeResources: 38,
        totalProjects: 15,
        activeProjects: 10,
        draftProjects: 3,
        completedProjects: 2,
        totalBudgetCents: 5_000_000_00,
        avgWinProbability: 78,
      };

      vi.mocked(getDashboardOverview).mockResolvedValue(overview);

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

      expect(result).toMatchObject({
        totalResources: 42,
        activeResources: 38,
        totalProjects: 15,
        activeProjects: 10,
      });
      expect(getDashboardOverview).toHaveBeenCalledTimes(1);
    });

    it("rejects unauthenticated users", async () => {
      const caller = createUnauthenticatedCaller({});
      await expect(caller.getOverview()).rejects.toThrow("Authentication required");
    });
  });

  describe("getStatisticsDetail", () => {
    it("returns assistant-friendly statistics derived from the canonical dashboard overview", async () => {
      vi.mocked(getDashboardOverview).mockResolvedValue({
        totalResources: 12,
        activeResources: 10,
        inactiveResources: 2,
        totalProjects: 7,
        activeProjects: 4,
        inactiveProjects: 3,
        totalAllocations: 21,
        activeAllocations: 18,
        cancelledAllocations: 3,
        approvedVacations: 6,
        totalEstimates: 9,
        budgetSummary: {
          totalBudgetCents: 1_234_56,
          totalCostCents: 654_32,
          avgUtilizationPercent: 53,
        },
        budgetBasis: {
          remainingBudgetCents: 58_024,
          budgetedProjects: 5,
          unbudgetedProjects: 2,
          trackedAssignmentCount: 18,
          windowStart: null,
          windowEnd: null,
        },
        projectsByStatus: [
          { status: "ACTIVE", count: 4 },
          { status: "DRAFT", count: 2 },
          { status: "DONE", count: 1 },
        ],
        chapterUtilization: [
          { chapter: "CGI", resourceCount: 5, avgChargeabilityTarget: 78 },
          { chapter: "Compositing", resourceCount: 3, avgChargeabilityTarget: 74 },
          { chapter: "Unassigned", resourceCount: 2, avgChargeabilityTarget: 0 },
        ],
        recentActivity: [],
      });

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

      expect(getDashboardOverview).toHaveBeenCalledTimes(1);
      expect(result).toEqual({
        activeResources: 10,
        totalProjects: 7,
        activeProjects: 4,
        totalAllocations: 21,
        approvedVacations: 6,
        totalEstimates: 9,
        totalBudget: "1.234,56 EUR",
        projectsByStatus: {
          ACTIVE: 4,
          DRAFT: 2,
          DONE: 1,
        },
        topChapters: [
          { chapter: "CGI", count: 5 },
          { chapter: "Compositing", count: 3 },
          { chapter: "Unassigned", count: 2 },
        ],
      });
    });
  });

  // ─── getPeakTimes ─────────────────────────────────────────────────────────

  describe("getPeakTimes", () => {
    it("returns array of time periods", async () => {
      const peakData = [
        { period: "2026-03", totalHours: 1200, entries: 15 },
        { period: "2026-04", totalHours: 1400, entries: 18 },
      ];

      vi.mocked(getDashboardPeakTimes).mockResolvedValue(peakData);

      const caller = createControllerCaller({});
      const result = await caller.getPeakTimes({
        startDate: "2026-03-01T00:00:00.000Z",
        endDate: "2026-06-30T00:00:00.000Z",
        granularity: "month",
        groupBy: "project",
      });

      expect(result).toHaveLength(2);
      expect(result[0]).toHaveProperty("period", "2026-03");
      expect(getDashboardPeakTimes).toHaveBeenCalledWith(
        expect.anything(),
        expect.objectContaining({
          granularity: "month",
          groupBy: "project",
        }),
      );
    });

    it("passes week granularity to application layer", async () => {
      vi.mocked(getDashboardPeakTimes).mockResolvedValue([]);

      const caller = createControllerCaller({});
      await caller.getPeakTimes({
        startDate: "2026-03-01T00:00:00.000Z",
        endDate: "2026-03-31T00:00:00.000Z",
        granularity: "week",
        groupBy: "chapter",
      });

      expect(getDashboardPeakTimes).toHaveBeenCalledWith(
        expect.anything(),
        expect.objectContaining({
          granularity: "week",
          groupBy: "chapter",
        }),
      );
    });
  });

  // ─── getDemand ────────────────────────────────────────────────────────────

  describe("getDemand", () => {
    it("returns demand entries grouped by project", async () => {
      const demandData = [
        { groupKey: "Project Alpha", totalHours: 500, headcount: 3 },
        { groupKey: "Project Beta", totalHours: 300, headcount: 2 },
      ];

      vi.mocked(getDashboardDemand).mockResolvedValue(demandData);

      const caller = createControllerCaller({});
      const result = await caller.getDemand({
        startDate: "2026-01-01T00:00:00.000Z",
        endDate: "2026-12-31T00:00:00.000Z",
        groupBy: "project",
      });

      expect(result).toHaveLength(2);
      expect(getDashboardDemand).toHaveBeenCalledWith(
        expect.anything(),
        expect.objectContaining({ groupBy: "project" }),
      );
    });

    it("supports grouping by chapter", async () => {
      vi.mocked(getDashboardDemand).mockResolvedValue([]);

      const caller = createControllerCaller({});
      await caller.getDemand({
        startDate: "2026-06-01T00:00:00.000Z",
        endDate: "2026-06-30T00:00:00.000Z",
        groupBy: "chapter",
      });

      expect(getDashboardDemand).toHaveBeenCalledWith(
        expect.anything(),
        expect.objectContaining({ groupBy: "chapter" }),
      );
    });
  });

  describe("getProjectHealthDetail", () => {
    it("returns assistant-friendly health detail derived from the canonical dashboard read model", async () => {
      vi.mocked(getDashboardProjectHealth).mockResolvedValue([
        {
          id: "project_critical",
          projectName: "Critical Project",
          shortCode: "CRIT",
          status: "ACTIVE",
          clientId: "client_1",
          clientName: "Acme",
          budgetHealth: 25,
          staffingHealth: 40,
          timelineHealth: 30,
          compositeScore: 35,
          budgetCents: 100_000,
          spentCents: 82_000,
          remainingBudgetCents: 18_000,
          budgetUtilizationPercent: 82,
          demandHeadcountTotal: 5,
          demandHeadcountFilled: 2,
          demandHeadcountOpen: 3,
          demandRequirementCount: 2,
          plannedEndDate: new Date("2026-06-30T00:00:00.000Z"),
          daysUntilEndDate: 12,
          timelineStatus: "DUE_SOON",
          calendarLocations: [
            {
              countryCode: "DE",
              countryName: "Germany",
              federalState: "BY",
              metroCityName: "Munich",
              assignmentCount: 2,
              spentCents: 52_000,
            },
          ],
          derivation: {
            periodStart: "2026-06-01",
            periodEnd: "2026-06-30",
            calendarContextCount: 1,
            holidayAwareAssignmentCount: 2,
            fallbackAssignmentCount: 0,
            baseSpentCents: 90_000,
            adjustedSpentCents: 82_000,
            publicHolidayDayEquivalent: 1,
            publicHolidayCostDeductionCents: 5_000,
            absenceDayEquivalent: 0.6,
            absenceCostDeductionCents: 3_000,
          },
        },
        {
          id: "project_healthy",
          projectName: "Healthy Project",
          shortCode: "HLTH",
          status: "ACTIVE",
          clientId: "client_1",
          clientName: "Acme",
          budgetHealth: 90,
          staffingHealth: 92,
          timelineHealth: 86,
          compositeScore: 89,
          budgetCents: 200_000,
          spentCents: 20_000,
          remainingBudgetCents: 180_000,
          budgetUtilizationPercent: 10,
          demandHeadcountTotal: 4,
          demandHeadcountFilled: 4,
          demandHeadcountOpen: 0,
          demandRequirementCount: 1,
          plannedEndDate: new Date("2026-09-15T00:00:00.000Z"),
          daysUntilEndDate: 89,
          timelineStatus: "ON_TRACK",
          calendarLocations: [],
        },
      ]);

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

      expect(getDashboardProjectHealth).toHaveBeenCalledTimes(1);
      expect(result).toEqual({
        projects: [
          {
            projectId: "project_critical",
            projectName: "Critical Project",
            shortCode: "CRIT",
            status: "ACTIVE",
            overall: 35,
            budget: 25,
            staffing: 40,
            timeline: 30,
            rating: "critical",
            budgetBasis: {
              budgetCents: 100_000,
              spentCents: 82_000,
              remainingBudgetCents: 18_000,
              budgetUtilizationPercent: 82,
              calendarLocations: [
                {
                  countryCode: "DE",
                  countryName: "Germany",
                  federalState: "BY",
                  metroCityName: "Munich",
                  assignmentCount: 2,
                  spentCents: 52_000,
                },
              ],
              derivation: {
                periodStart: "2026-06-01",
                periodEnd: "2026-06-30",
                calendarContextCount: 1,
                holidayAwareAssignmentCount: 2,
                fallbackAssignmentCount: 0,
                baseSpentCents: 90_000,
                adjustedSpentCents: 82_000,
                publicHolidayDayEquivalent: 1,
                publicHolidayCostDeductionCents: 5_000,
                absenceDayEquivalent: 0.6,
                absenceCostDeductionCents: 3_000,
              },
            },
            staffingBasis: {
              demandHeadcountTotal: 5,
              demandHeadcountFilled: 2,
              demandHeadcountOpen: 3,
              demandRequirementCount: 2,
            },
            timelineBasis: {
              plannedEndDate: "2026-06-30T00:00:00.000Z",
              daysUntilEndDate: 12,
              timelineStatus: "DUE_SOON",
            },
            context: {
              clientId: "client_1",
              clientName: "Acme",
            },
          },
          {
            projectId: "project_healthy",
            projectName: "Healthy Project",
            shortCode: "HLTH",
            status: "ACTIVE",
            overall: 89,
            budget: 90,
            staffing: 92,
            timeline: 86,
            rating: "healthy",
            budgetBasis: {
              budgetCents: 200_000,
              spentCents: 20_000,
              remainingBudgetCents: 180_000,
              budgetUtilizationPercent: 10,
              calendarLocations: [],
              derivation: null,
            },
            staffingBasis: {
              demandHeadcountTotal: 4,
              demandHeadcountFilled: 4,
              demandHeadcountOpen: 0,
              demandRequirementCount: 1,
            },
            timelineBasis: {
              plannedEndDate: "2026-09-15T00:00:00.000Z",
              daysUntilEndDate: 89,
              timelineStatus: "ON_TRACK",
            },
            context: {
              clientId: "client_1",
              clientName: "Acme",
            },
          },
        ],
        summary: {
          healthy: 1,
          atRisk: 0,
          critical: 1,
        },
      });
    });
  });

  // ─── getTopValueResources ─────────────────────────────────────────────────

  describe("getTopValueResources", () => {
    it("returns sorted resources with default limit", async () => {
      const resources = [
        {
          id: "res_1",
          eid: "alice",
          displayName: "Alice",
          chapter: "Delivery",
          valueScore: 95,
          valueScoreBreakdown: {
            skillDepth: 88,
            skillBreadth: 79,
            costEfficiency: 84,
            chargeability: 76,
            experience: 90,
            total: 95,
          },
          valueScoreUpdatedAt: new Date("2026-03-01T00:00:00.000Z"),
          lcrCents: 12_300,
          countryCode: "DE",
          countryName: "Germany",
          federalState: "BY",
          metroCityName: "Munich",
        },
        {
          id: "res_2",
          eid: "bob",
          displayName: "Bob",
          chapter: "Data",
          valueScore: 88,
          valueScoreBreakdown: {
            skillDepth: 80,
            skillBreadth: 77,
            costEfficiency: 91,
            chargeability: 72,
            experience: 81,
            total: 88,
          },
          valueScoreUpdatedAt: new Date("2026-03-02T00:00:00.000Z"),
          lcrCents: 10_800,
          countryCode: "US",
          countryName: "United States",
          federalState: "CA",
          metroCityName: "San Francisco",
        },
      ];

      vi.mocked(getDashboardTopValueResources).mockResolvedValue(resources);

      const caller = createControllerCaller({});
      const result = await caller.getTopValueResources({ limit: 10 });

      expect(result).toEqual(resources);
      expect(getDashboardTopValueResources).toHaveBeenCalledWith(
        expect.anything(),
        expect.objectContaining({ limit: 10 }),
      );
    });

    it("respects custom limit", async () => {
      vi.mocked(getDashboardTopValueResources).mockResolvedValue([]);

      const caller = createControllerCaller({});
      await caller.getTopValueResources({ limit: 5 });

      expect(getDashboardTopValueResources).toHaveBeenCalledWith(
        expect.anything(),
        expect.objectContaining({ limit: 5 }),
      );
    });
  });

  // ─── getChargeabilityOverview ─────────────────────────────────────────────

  describe("getChargeabilityOverview", () => {
    it("returns chargeability data with top and watchlist arrays", async () => {
      const overview = {
        avgChargeability: 72,
        top: [{ id: "res_1", displayName: "Alice", chargeability: 95 }],
        watchlist: [{ id: "res_3", displayName: "Carol", chargeability: 30 }],
      };

      vi.mocked(getDashboardChargeabilityOverview).mockResolvedValue(overview);

      const caller = createControllerCaller({});
      const result = await caller.getChargeabilityOverview({
        includeProposed: false,
        topN: 10,
        watchlistThreshold: 15,
      });

      expect(result).toHaveProperty("top");
      expect(result).toHaveProperty("watchlist");
      expect(result.top).toHaveLength(1);
      expect(result.watchlist).toHaveLength(1);
    });

    it("passes includeProposed flag to application layer", async () => {
      vi.mocked(getDashboardChargeabilityOverview).mockResolvedValue({
        avgChargeability: 60,
        top: [],
        watchlist: [],
      });

      const caller = createControllerCaller({});
      await caller.getChargeabilityOverview({
        includeProposed: true,
        topN: 5,
        watchlistThreshold: 20,
      });

      expect(getDashboardChargeabilityOverview).toHaveBeenCalledWith(
        expect.anything(),
        expect.objectContaining({
          includeProposed: true,
          topN: 5,
          watchlistThreshold: 20,
        }),
      );
    });

    it("requires controller role — blocks USER", async () => {
      const caller = createProtectedCaller({});
      await expect(
        caller.getChargeabilityOverview({
          includeProposed: false,
          topN: 10,
          watchlistThreshold: 15,
        }),
      ).rejects.toThrow(
        expect.objectContaining({ code: "FORBIDDEN" }),
      );
    });
  });

  describe("getBudgetForecast", () => {
    it("returns budget forecast rows with calendar location context", async () => {
      vi.mocked(getDashboardBudgetForecast).mockResolvedValue([
        {
          projectId: "project_1",
          projectName: "Alpha",
          shortCode: "ALPHA",
          clientId: "client_1",
          clientName: "Client One",
          budgetCents: 100_000,
          spentCents: 40_000,
          remainingCents: 60_000,
          burnRate: 10_000,
          estimatedExhaustionDate: "2026-06-30",
          pctUsed: 40,
          activeAssignmentCount: 2,
          derivation: {
            periodStart: "2026-03-01",
            periodEnd: "2026-03-31",
            calendarContextCount: 1,
            holidayAwareAssignmentCount: 2,
            fallbackAssignmentCount: 0,
            baseBurnRateCents: 12_000,
            adjustedBurnRateCents: 10_000,
            publicHolidayDayEquivalent: 1,
            publicHolidayCostDeductionCents: 1_000,
            absenceDayEquivalent: 0.5,
            absenceCostDeductionCents: 1_000,
          },
          calendarLocations: [
            {
              countryCode: "DE",
              countryName: "Germany",
              federalState: "BY",
              metroCityName: "Munich",
              activeAssignmentCount: 2,
              burnRateCents: 10_000,
            },
          ],
        },
      ]);

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

      expect(result).toHaveLength(1);
      expect(result[0]).toMatchObject({
        projectName: "Alpha",
        activeAssignmentCount: 2,
        derivation: {
          holidayAwareAssignmentCount: 2,
          baseBurnRateCents: 12_000,
          adjustedBurnRateCents: 10_000,
        },
        calendarLocations: [
          expect.objectContaining({
            countryCode: "DE",
            federalState: "BY",
            metroCityName: "Munich",
          }),
        ],
      });
      expect(getDashboardBudgetForecast).toHaveBeenCalledTimes(1);
    });

    it("returns assistant-friendly budget forecast detail derived from the canonical dashboard read model", async () => {
      vi.mocked(getDashboardBudgetForecast).mockResolvedValue([
        {
          projectId: "project_1",
          projectName: "Alpha",
          shortCode: "ALPHA",
          clientId: "client_1",
          clientName: "Client One",
          budgetCents: 100_000,
          spentCents: 40_000,
          remainingCents: 60_000,
          burnRate: 10_000,
          estimatedExhaustionDate: "2026-06-30",
          pctUsed: 40,
          activeAssignmentCount: 2,
          derivation: {
            periodStart: "2026-03-01",
            periodEnd: "2026-03-31",
            calendarContextCount: 1,
            holidayAwareAssignmentCount: 2,
            fallbackAssignmentCount: 0,
            baseBurnRateCents: 12_000,
            adjustedBurnRateCents: 10_000,
            publicHolidayDayEquivalent: 1,
            publicHolidayCostDeductionCents: 1_000,
            absenceDayEquivalent: 0.5,
            absenceCostDeductionCents: 1_000,
          },
          calendarLocations: [
            {
              countryCode: "DE",
              countryName: "Germany",
              federalState: "BY",
              metroCityName: "Munich",
              activeAssignmentCount: 2,
              burnRateCents: 10_000,
            },
          ],
        },
      ]);

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

      expect(getDashboardBudgetForecast).toHaveBeenCalledTimes(1);
      expect(result).toEqual({
        forecasts: [
          expect.objectContaining({
            projectId: "project_1",
            projectName: "Alpha",
            shortCode: "ALPHA",
            budgetCents: 100_000,
            spentCents: 40_000,
            remainingCents: 60_000,
            projectedCents: 100_000,
            burnRateCents: 10_000,
            utilization: "40%",
            derivation: expect.objectContaining({
              holidayAwareAssignmentCount: 2,
              baseBurnRateCents: 12_000,
              adjustedBurnRateCents: 10_000,
              publicHolidayCostDeductionCents: 1_000,
              absenceCostDeductionCents: 1_000,
            }),
            burnStatus: "on_track",
            calendarLocations: [
              expect.objectContaining({
                countryCode: "DE",
                federalState: "BY",
                metroCityName: "Munich",
              }),
            ],
          }),
        ],
      });
    });
  });

  describe("getDetail", () => {
    it("returns the canonical assistant dashboard detail payload", async () => {
      vi.mocked(getDashboardOverview).mockResolvedValue({
        budgetBasis: {
          windowStart: "2026-01-01T00:00:00.000Z",
          windowEnd: "2026-06-30T00:00:00.000Z",
        },
        chapterUtilization: [
          {
            chapter: "Delivery",
            resourceCount: 4,
            avgChargeabilityTarget: 78,
          },
        ],
      });
      vi.mocked(getDashboardPeakTimes).mockResolvedValue([
        {
          period: "2026-03",
          totalHours: 320.4,
          capacityHours: 400.2,
          utilizationPct: 80,
          derivation: {
            periodStart: "2026-03-01",
            periodEnd: "2026-03-31",
            calendarContextCount: 1,
            resourceCount: 4,
            groupCount: 1,
            baseAvailableHours: 416.2,
            effectiveAvailableHours: 400.2,
            publicHolidayHoursDeduction: 16,
            absenceDayEquivalent: 1.5,
            absenceHoursDeduction: 12.5,
            calendarLocations: [
              {
                countryCode: "DE",
                countryName: "Germany",
                federalState: "BY",
                metroCityName: "Augsburg",
                resourceCount: 4,
                effectiveAvailableHours: 400.2,
              },
            ],
            bookedHours: 320.4,
            capacityHours: 400.2,
            remainingCapacityHours: 79.8,
            overbookedHours: 0,
            utilizationPct: 80,
          },
        },
      ]);
      vi.mocked(getDashboardSkillGapSummary).mockResolvedValue({
        roleGaps: [
          { role: "Pipeline TD", needed: 4, filled: 1, gap: 3, fillRate: 25 },
          { role: "Lighting TD", needed: 2, filled: 1, gap: 1, fillRate: 50 },
        ],
        totalOpenPositions: 4,
        skillSupplyTop10: [
          { skill: "houdini", resourceCount: 5 },
          { skill: "nuke", resourceCount: 4 },
        ],
        resourcesByRole: [
          { role: "Compositor", count: 6 },
          { role: "Pipeline TD", count: 2 },
        ],
      });
      vi.mocked(getDashboardTopValueResources).mockResolvedValue([
        {
          id: "res_1",
          eid: "pparker",
          displayName: "Peter Parker",
          chapter: "Delivery",
          valueScore: 91,
          valueScoreBreakdown: {
            skillDepth: 85,
            skillBreadth: 74,
            costEfficiency: 93,
            chargeability: 78,
            experience: 88,
            total: 91,
          },
          valueScoreUpdatedAt: new Date("2026-03-03T00:00:00.000Z"),
          lcrCents: 9_500,
          countryCode: "DE",
          countryName: "Germany",
          federalState: "BY",
          metroCityName: "Augsburg",
        },
      ]);
      vi.mocked(getDashboardDemand).mockResolvedValue([
        {
          id: "project_1",
          name: "Gelddruckmaschine",
          shortCode: "GDM",
          allocatedHours: 120,
          requiredFTEs: 4,
          resourceCount: 2,
          derivation: {
            periodStart: "2026-01-01",
            periodEnd: "2026-06-30",
            periodWorkingHoursBase: 1040,
            requiredHours: 2080,
            requiredFTEs: 4,
            fillPct: 50,
            demandSource: "DEMAND_REQUIREMENTS",
            calendarLocations: [
              {
                countryCode: "DE",
                countryName: "Germany",
                federalState: "BY",
                metroCityName: "Augsburg",
                resourceCount: 2,
                allocatedHours: 120,
              },
            ],
          },
        },
      ]);
      vi.mocked(getDashboardChargeabilityOverview).mockResolvedValue({
        rows: [
          {
            id: "res_1",
            eid: "pparker",
            displayName: "Peter Parker",
            chapter: "Delivery",
            chargeabilityTarget: 78,
            actualChargeability: 70,
            expectedChargeability: 76,
            derivation: {
              weeklyAvailabilityHours: 40,
              baseWorkingDays: 22,
              effectiveWorkingDayEquivalent: 21,
              baseAvailableHours: 176,
              effectiveAvailableHours: 168,
              publicHolidayCount: 1,
              publicHolidayWorkdayCount: 1,
              publicHolidayHoursDeduction: 8,
              absenceDayEquivalent: 0,
              absenceHoursDeduction: 0,
              actualBookedHours: 117.6,
              expectedBookedHours: 127.7,
              targetBookedHours: 131,
              unassignedHours: 40.3,
            },
          },
        ],
        top: [],
        watchlist: [],
        month: "2026-03",
      });
      vi.mocked(getDashboardProjectHealth).mockResolvedValue([
        {
          id: "project_1",
          projectName: "Gelddruckmaschine",
          shortCode: "GDM",
          status: "ACTIVE",
          clientId: "client_1",
          clientName: "Acme",
          budgetHealth: 58,
          staffingHealth: 46,
          timelineHealth: 61,
          compositeScore: 55,
          budgetUtilizationPercent: 73,
          remainingBudgetCents: 88_000,
          demandHeadcountTotal: 4,
          demandHeadcountFilled: 2,
          demandHeadcountOpen: 2,
          demandRequirementCount: 2,
          plannedEndDate: new Date("2026-09-30T00:00:00.000Z"),
          daysUntilEndDate: 183,
          timelineStatus: "DUE_SOON",
          derivation: {
            periodStart: "2026-01-01",
            periodEnd: "2026-06-30",
            calendarContextCount: 1,
            holidayAwareAssignmentCount: 2,
            fallbackAssignmentCount: 0,
            baseSpentCents: 140_000,
            adjustedSpentCents: 132_000,
            publicHolidayDayEquivalent: 1,
            publicHolidayCostDeductionCents: 5_000,
            absenceDayEquivalent: 0.5,
            absenceCostDeductionCents: 3_000,
          },
        },
      ]);

      const caller = createControllerCaller({});
      const result = await caller.getDetail({ section: "all" });

      expect(result).toEqual({
        peakTimes: [
          {
            month: "2026-03",
            totalHours: 320.4,
            totalHoursPerDay: 320.4,
            capacityHours: 400.2,
            utilizationPct: 80,
            calendarContextCount: 1,
            calendarLocations: [
              {
                countryCode: "DE",
                countryName: "Germany",
                federalState: "BY",
                metroCityName: "Augsburg",
                resourceCount: 4,
                effectiveAvailableHours: 400.2,
              },
            ],
            explainability: {
              periodStart: "2026-03-01",
              periodEnd: "2026-03-31",
              resourceCount: 4,
              groupCount: 1,
              baseAvailableHours: 416.2,
              effectiveAvailableHours: 400.2,
              publicHolidayHoursDeduction: 16,
              absenceDayEquivalent: 1.5,
              absenceHoursDeduction: 12.5,
              remainingCapacityHours: 79.8,
              overbookedHours: 0,
            },
          },
        ],
        topResources: [
          {
            name: "Peter Parker",
            eid: "pparker",
            chapter: "Delivery",
            lcr: "95,00 EUR",
            valueScore: 91,
            valueScoreBreakdown: {
              skillDepth: 85,
              skillBreadth: 74,
              costEfficiency: 93,
              chargeability: 78,
              experience: 88,
              total: 91,
            },
            valueScoreUpdatedAt: "2026-03-03T00:00:00.000Z",
            countryCode: "DE",
            countryName: "Germany",
            federalState: "BY",
            metroCityName: "Augsburg",
          },
        ],
        demandPipeline: [
          {
            project: "Gelddruckmaschine (GDM)",
            needed: 2,
            requiredFTEs: 4,
            allocatedResources: 2,
            allocatedHours: 120,
            calendarLocations: [
              {
                countryCode: "DE",
                countryName: "Germany",
                federalState: "BY",
                metroCityName: "Augsburg",
                resourceCount: 2,
                allocatedHours: 120,
              },
            ],
            explainability: {
              periodStart: "2026-01-01",
              periodEnd: "2026-06-30",
              periodWorkingHoursBase: 1040,
              requiredHours: 2080,
              fillPct: 50,
              demandSource: "DEMAND_REQUIREMENTS",
              calendarContextCount: 1,
            },
          },
        ],
        chargeabilityByChapter: [
          {
            chapter: "Delivery",
            headcount: 1,
            avgTargetPct: 78,
            avgActualPct: 70,
            avgExpectedPct: 76,
            gapToTargetPct: 8,
            avgTarget: "78%",
            avgActual: "70%",
            avgExpected: "76%",
            explainability: {
              month: "2026-03",
              resourceCount: 1,
              derivedHeadcount: 1,
              baseAvailableHours: 176,
              effectiveAvailableHours: 168,
              actualBookedHours: 117.6,
              expectedBookedHours: 127.7,
              targetBookedHours: 131,
              publicHolidayHoursDeduction: 8,
              absenceDayEquivalent: 0,
              absenceHoursDeduction: 0,
              unassignedHours: 40.3,
            },
          },
        ],
        projectHealth: [
          {
            project: "Gelddruckmaschine (GDM)",
            status: "ACTIVE",
            overall: 55,
            rating: "at_risk",
            budget: 58,
            staffing: 46,
            timeline: 61,
            timelineStatus: "DUE_SOON",
            daysUntilEndDate: 183,
            demandHeadcountOpen: 2,
            explainability: {
              demandHeadcountTotal: 4,
              demandHeadcountFilled: 2,
              demandHeadcountOpen: 2,
              demandRequirementCount: 2,
              plannedEndDate: "2026-09-30T00:00:00.000Z",
              budgetUtilizationPercent: 73,
              remainingBudgetCents: 88_000,
              calendarContextCount: 1,
              holidayAwareAssignmentCount: 2,
              publicHolidayCostDeductionCents: 5_000,
              absenceCostDeductionCents: 3_000,
            },
          },
        ],
        skillGaps: {
          totalOpenPositions: 4,
          roleGaps: [
            { role: "Pipeline TD", gap: 3, needed: 4, filled: 1, fillRate: 25 },
            { role: "Lighting TD", gap: 1, needed: 2, filled: 1, fillRate: 50 },
          ],
          topSkillsInSupply: [
            { skill: "houdini", resourceCount: 5 },
            { skill: "nuke", resourceCount: 4 },
          ],
          resourcesByRole: [
            { role: "Compositor", count: 6 },
            { role: "Pipeline TD", count: 2 },
          ],
        },
      });
      expect(getDashboardPeakTimes).toHaveBeenCalledWith(
        expect.anything(),
        expect.objectContaining({
          startDate: new Date("2026-01-01T00:00:00.000Z"),
          endDate: new Date("2026-06-30T00:00:00.000Z"),
          granularity: "month",
          groupBy: "project",
        }),
      );
      expect(getDashboardChargeabilityOverview).toHaveBeenCalledWith(
        expect.anything(),
        expect.objectContaining({
          includeProposed: false,
          topN: 10,
          watchlistThreshold: 15,
        }),
      );
      expect(getDashboardSkillGapSummary).toHaveBeenCalledWith(expect.anything());
      expect(getDashboardProjectHealth).toHaveBeenCalledTimes(1);
    });
  });
});
