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,
    isChargeabilityActualBooking: actual.isChargeabilityActualBooking,
    listAssignmentBookings: vi.fn(),
  };
});

import { listAssignmentBookings } from "@capakraken/application";
import { chargeabilityReportRouter } from "../router/chargeability-report.js";
import { createCallerFactory } from "../trpc.js";

const createCaller = createCallerFactory(chargeabilityReportRouter);

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

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

  it("excludes proposed bookings by default but includes them when requested", async () => {
    const db = {
      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" } },
          { id: "project_proposed", utilizationCategory: { code: "Chg" } },
        ]),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    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" },
      },
      {
        id: "assignment_proposed",
        projectId: "project_proposed",
        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: "PROPOSED",
        project: {
          id: "project_proposed",
          name: "Proposed Project",
          shortCode: "PP",
          status: "ACTIVE",
          orderType: "CLIENT",
          dynamicFields: null,
        },
        resource: { id: "resource_1", displayName: "Alice", chapter: "CGI" },
      },
    ]);

    const caller = createControllerCaller(db);

    const strict = await caller.getReport({
      startMonth: "2026-03",
      endMonth: "2026-03",
    });
    const withProposed = await caller.getReport({
      startMonth: "2026-03",
      endMonth: "2026-03",
      includeProposed: true,
    });

    const strictMonth = strict.resources[0]?.months[0];
    const proposedMonth = withProposed.resources[0]?.months[0];

    expect(strictMonth).toBeDefined();
    expect(proposedMonth).toBeDefined();
    expect(strict.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(withProposed.explainability.activeFilters).toEqual(["includeProposed"]);
    expect(strictMonth?.chg).toBeGreaterThan(0);
    expect(proposedMonth?.chg).toBeGreaterThan(strictMonth?.chg ?? 0);
    expect(proposedMonth?.chg).toBeCloseTo((strictMonth?.chg ?? 0) * 2, 5);
    expect(withProposed.groupTotals[0]?.chg).toBeGreaterThan(strict.groupTotals[0]?.chg ?? 0);
  });

  it("includes imported TBD draft work only when proposed bookings are enabled", async () => {
    const db = {
      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_tbd", utilizationCategory: { code: "Chg" } },
        ]),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    vi.mocked(listAssignmentBookings).mockResolvedValue([
      {
        id: "assignment_tbd",
        projectId: "project_tbd",
        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: "PROPOSED",
        project: {
          id: "project_tbd",
          name: "TBD Project",
          shortCode: "TBD-P1",
          status: "DRAFT",
          orderType: "CLIENT",
          dynamicFields: { dispoImport: { isTbd: true } },
        },
        resource: { id: "resource_1", displayName: "Alice", chapter: "CGI" },
      },
    ]);

    const caller = createControllerCaller(db);
    const strict = await caller.getReport({
      startMonth: "2026-03",
      endMonth: "2026-03",
    });
    const withProposed = await caller.getReport({
      startMonth: "2026-03",
      endMonth: "2026-03",
      includeProposed: true,
    });

    expect(strict.resources[0]?.months[0]?.chg).toBe(0);
    expect(withProposed.resources[0]?.months[0]?.chg).toBeGreaterThan(0);
    expect(withProposed.groupTotals[0]?.chg).toBeGreaterThan(strict.groupTotals[0]?.chg ?? 0);
  });

  it("reduces SAH for German public holidays based on the calendar", async () => {
    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "resource_de",
            eid: "E-001",
            displayName: "Alice",
            fte: 1,
            availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
            countryId: "country_de",
            federalState: null,
            metroCityId: "city_1",
            chargeabilityTarget: 80,
            country: {
              id: "country_de",
              code: "DE",
              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: "Munich" },
          },
        ]),
      },
      project: {
        findMany: vi.fn().mockResolvedValue([
          { id: "project_full_month", utilizationCategory: { code: "Chg" } },
        ]),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    vi.mocked(listAssignmentBookings).mockResolvedValue([
      {
        id: "assignment_full_month",
        projectId: "project_full_month",
        resourceId: "resource_de",
        startDate: new Date("2026-01-01T00:00:00.000Z"),
        endDate: new Date("2026-01-31T00:00:00.000Z"),
        hoursPerDay: 7,
        dailyCostCents: 0,
        status: "CONFIRMED",
        project: {
          id: "project_full_month",
          name: "Full Month Project",
          shortCode: "FMP",
          status: "ACTIVE",
          orderType: "CLIENT",
          dynamicFields: null,
        },
        resource: { id: "resource_de", displayName: "Alice", chapter: "CGI" },
      },
    ]);

    const caller = createControllerCaller(db);
    const report = await caller.getReport({
      startMonth: "2026-01",
      endMonth: "2026-01",
    });

    const month = report.resources[0]?.months[0];

    expect(month).toBeDefined();
    expect(month?.sah).toBe(168);
    expect(month?.chg).toBeCloseTo(0.875, 5);
  });

  it("applies city-specific public holidays to SAH", async () => {
    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "resource_augsburg",
            eid: "E-001",
            displayName: "Alice",
            fte: 1,
            availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
            countryId: "country_de",
            federalState: "BY",
            metroCityId: "city_1",
            chargeabilityTarget: 80,
            country: {
              id: "country_de",
              code: "DE",
              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: "Augsburg" },
          },
          {
            id: "resource_munich",
            eid: "E-002",
            displayName: "Bob",
            fte: 1,
            availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
            countryId: "country_de",
            federalState: "BY",
            metroCityId: "city_2",
            chargeabilityTarget: 80,
            country: {
              id: "country_de",
              code: "DE",
              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_2", name: "Munich" },
          },
        ]),
      },
      project: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    vi.mocked(listAssignmentBookings).mockResolvedValue([]);

    const caller = createControllerCaller(db);
    const report = await caller.getReport({
      startMonth: "2028-08",
      endMonth: "2028-08",
    });

    const augsburg = report.resources.find((resource) => resource.city === "Augsburg");
    const munich = report.resources.find((resource) => resource.city === "Munich");

    expect(augsburg?.federalState).toBe("BY");
    expect(augsburg?.months[0]?.derivation?.publicHolidayCount).toBe(
      (munich?.months[0]?.derivation?.publicHolidayCount ?? 0) + 1,
    );
    expect(augsburg?.months[0]?.derivation?.publicHolidayHoursDeduction).toBe(
      (munich?.months[0]?.derivation?.publicHolidayHoursDeduction ?? 0) + 8,
    );
    expect(augsburg?.months[0]?.sah).toBe((munich?.months[0]?.sah ?? 0) - 8);
  });

  it("respects individual weekday availability when computing booked hours", async () => {
    const db = {
      resource: {
        findMany: vi.fn().mockResolvedValue([
          {
            id: "resource_pt",
            eid: "E-003",
            displayName: "Carla",
            fte: 1,
            availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 0 },
            countryId: "country_de",
            federalState: null,
            metroCityId: "city_3",
            chargeabilityTarget: 80,
            country: {
              id: "country_de",
              code: "DE",
              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_3", name: "Berlin" },
          },
        ]),
      },
      project: {
        findMany: vi.fn().mockResolvedValue([
          { id: "project_week", utilizationCategory: { code: "Chg" } },
        ]),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
    };

    vi.mocked(listAssignmentBookings).mockResolvedValue([
      {
        id: "assignment_week",
        projectId: "project_week",
        resourceId: "resource_pt",
        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_week",
          name: "Week Project",
          shortCode: "WP",
          status: "ACTIVE",
          orderType: "CLIENT",
          dynamicFields: null,
        },
        resource: { id: "resource_pt", displayName: "Carla", chapter: "CGI" },
      },
    ]);

    const caller = createControllerCaller(db);
    const report = await caller.getReport({
      startMonth: "2026-03",
      endMonth: "2026-03",
    });

    const month = report.resources[0]?.months[0];

    expect(month).toBeDefined();
    expect(month?.chg).toBeCloseTo(16 / 144, 5);
  });

  it("returns a filtered detailed report with rounded percentages", async () => {
    const db = {
      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([]),
      },
    };

    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 caller = createControllerCaller(db);
    const result = await caller.getDetail({
      startMonth: "2026-03",
      endMonth: "2026-03",
      resourceQuery: "ali",
      resourceLimit: 10,
    });

    expect(result.filters).toEqual({
      startMonth: "2026-03",
      endMonth: "2026-03",
      orgUnitId: null,
      managementLevelGroupId: null,
      countryId: null,
      includeProposed: false,
      resourceQuery: "ali",
    });
    expect(result.groupTotals).toEqual([
      expect.objectContaining({
        monthKey: "2026-03",
        totalFte: 1,
        chargeabilityPct: expect.any(Number),
        targetPct: 80,
      }),
    ]);
    expect(result.resourceCount).toBe(1);
    expect(result.returnedResourceCount).toBe(1);
    expect(result.truncated).toBe(false);
    expect(result.explainability).toEqual({
      locationFields: [
        "country",
        "federalState",
        "city",
        "orgUnit",
        "managementLevelGroup",
        "managementLevel",
      ],
      monthDerivationFields: [
        "baseAvailableHours",
        "publicHolidayCount",
        "publicHolidayWorkdayCount",
        "publicHolidayHoursDeduction",
        "absenceDayEquivalent",
        "absenceHoursDeduction",
        "effectiveAvailableHours",
      ],
      activeFilters: ["resourceQuery"],
      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(result.resources).toEqual([
      expect.objectContaining({
        displayName: "Alice",
        targetPct: 80,
        country: "ES",
        federalState: null,
        city: "Barcelona",
        managementLevelGroup: "Senior",
        managementLevel: "L7",
        months: [
          expect.objectContaining({
            monthKey: "2026-03",
            sah: expect.any(Number),
            chargeabilityPct: expect.any(Number),
            gapPct: expect.any(Number),
            derivation: expect.objectContaining({
              baseAvailableHours: expect.any(Number),
              effectiveAvailableHours: expect.any(Number),
            }),
          }),
        ],
      }),
    ]);
  });
});
