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

const {
  calculateAllocationMock,
  updateAssignmentMock,
  updateDemandRequirementMock,
  validateShiftMock,
} = vi.hoisted(() => ({
  validateShiftMock: vi.fn(),
  calculateAllocationMock: vi.fn(),
  updateAssignmentMock: vi.fn(),
  updateDemandRequirementMock: vi.fn(),
}));

vi.mock("@capakraken/engine", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@capakraken/engine")>();
  return {
    ...actual,
    validateShift: validateShiftMock,
    calculateAllocation: calculateAllocationMock,
  };
});

vi.mock("@capakraken/application", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@capakraken/application")>();
  return {
    ...actual,
    updateAssignment: updateAssignmentMock,
    updateDemandRequirement: updateDemandRequirementMock,
  };
});

import { buildTimelineProjectShiftEventPayload } from "../router/timeline-shift-support.js";
import { applyTimelineProjectShift } from "../router/timeline-shift-procedure-support.js";

function createValidValidationResult(
  overrides: Partial<ShiftValidationResult> = {},
): ShiftValidationResult {
  return {
    valid: true,
    errors: [],
    warnings: [],
    conflictDetails: [],
    costImpact: {
      currentTotalCents: 100_000,
      newTotalCents: 112_000,
      deltaCents: 12_000,
      budgetCents: 200_000,
      budgetUtilizationBefore: 50,
      budgetUtilizationAfter: 56,
      wouldExceedBudget: false,
    },
    ...overrides,
  };
}

describe("timeline shift procedure support", () => {
  it("applies project shift updates and recalculates only staffed assignments", async () => {
    const validation = createValidValidationResult();
    validateShiftMock.mockReturnValue(validation);
    calculateAllocationMock.mockReturnValue({ dailyCostCents: 54_321 });
    updateDemandRequirementMock.mockResolvedValue({});
    updateAssignmentMock.mockResolvedValue({});

    const updatedProject = {
      id: "project_1",
      startDate: new Date("2026-04-03"),
      endDate: new Date("2026-04-12"),
    };
    const db = {
      project: {
        update: vi.fn().mockResolvedValue(updatedProject),
      },
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
      vacation: {
        findMany: vi.fn().mockResolvedValue([]),
      },
      calculationRule: {
        findMany: vi.fn().mockResolvedValue([{ id: "rule_1", priority: 1 }]),
      },
      $transaction: vi.fn(async (callback: (tx: typeof db) => unknown) => callback(db)),
    };

    const context = {
      project: {
        id: "project_1",
        budgetCents: 200_000,
        winProbability: 80,
        startDate: new Date("2026-04-01"),
        endDate: new Date("2026-04-10"),
      },
      demandRequirements: [
        {
          id: "demand_1",
        },
      ],
      assignments: [
        {
          id: "assignment_staffed",
          resourceId: "resource_1",
          hoursPerDay: 8,
          metadata: { includeSaturday: true },
          resource: {
            lcrCents: 5_000,
            availability: {
              monday: 8,
              tuesday: 8,
              wednesday: 8,
              thursday: 8,
              friday: 8,
            },
          },
        },
        {
          id: "assignment_placeholder",
          resourceId: null,
          hoursPerDay: 4,
          metadata: {},
          resource: null,
        },
      ],
      shiftPlan: {
        validationAllocations: [
          {
            id: "assignment_1",
            resourceId: "resource_1",
            startDate: new Date("2026-04-01"),
            endDate: new Date("2026-04-05"),
            hoursPerDay: 8,
            percentage: 100,
            role: "Compositing",
            dailyCostCents: 40_000,
            status: AllocationStatus.ACTIVE,
            resource: {
              id: "resource_1",
              displayName: "Alice",
              lcrCents: 5_000,
              availability: {
                monday: 8,
                tuesday: 8,
                wednesday: 8,
                thursday: 8,
                friday: 8,
              },
            },
            allAllocationsForResource: [],
          },
        ],
      },
    };

    const result = await applyTimelineProjectShift({
      db: db as never,
      projectId: "project_1",
      newStartDate: new Date("2026-04-03"),
      newEndDate: new Date("2026-04-12"),
      context: context as never,
    });

    expect(db.project.update).toHaveBeenCalledWith({
      where: { id: "project_1" },
      data: {
        startDate: new Date("2026-04-03"),
        endDate: new Date("2026-04-12"),
      },
    });
    expect(updateDemandRequirementMock).toHaveBeenCalledWith(
      db,
      "demand_1",
      {
        startDate: new Date("2026-04-03"),
        endDate: new Date("2026-04-12"),
      },
    );
    expect(db.vacation.findMany).toHaveBeenCalledTimes(1);
    expect(db.calculationRule.findMany).toHaveBeenCalledTimes(1);
    expect(calculateAllocationMock).toHaveBeenCalledTimes(1);
    expect(updateAssignmentMock).toHaveBeenNthCalledWith(
      1,
      db,
      "assignment_staffed",
      {
        startDate: new Date("2026-04-03"),
        endDate: new Date("2026-04-12"),
        dailyCostCents: 54_321,
      },
    );
    expect(updateAssignmentMock).toHaveBeenNthCalledWith(
      2,
      db,
      "assignment_placeholder",
      {
        startDate: new Date("2026-04-03"),
        endDate: new Date("2026-04-12"),
      },
    );
    expect(db.auditLog.create).toHaveBeenCalledWith({
      data: {
        entityType: "Project",
        entityId: "project_1",
        action: "SHIFT",
        changes: expect.objectContaining({
          before: {
            startDate: new Date("2026-04-01"),
            endDate: new Date("2026-04-10"),
          },
          after: {
            startDate: new Date("2026-04-03"),
            endDate: new Date("2026-04-12"),
          },
          costImpact: validation.costImpact,
        }),
      },
    });
    expect(result).toEqual({
      project: updatedProject,
      validation,
      event: buildTimelineProjectShiftEventPayload({
        projectId: "project_1",
        newStartDate: new Date("2026-04-03"),
        newEndDate: new Date("2026-04-12"),
        validation,
        assignments: context.assignments as never,
      }),
    });
  });
});
