import { beforeEach, describe, expect, it, vi } from "vitest";

vi.mock("../lib/anonymization.js", () => ({
  getAnonymizationDirectory: vi.fn(),
}));

vi.mock("../router/timeline-project-load-support.js", () => ({
  loadTimelineProjectContext: vi.fn(),
  previewTimelineProjectShift: vi.fn(),
}));

vi.mock("../router/timeline-project-context-support.js", () => ({
  loadTimelineProjectContextDetailArtifacts: vi.fn(),
}));

vi.mock("../router/timeline-project-context-response-support.js", () => ({
  buildTimelineProjectContextResponse: vi.fn(),
  buildTimelineProjectContextDetailResponse: vi.fn(),
}));

vi.mock("@capakraken/application", () => ({
  listAssignmentBookings: vi.fn(),
}));

vi.mock("@capakraken/engine", () => ({
  computeBudgetStatus: vi.fn(),
}));

vi.mock("../router/timeline-project-query-support.js", () => ({
  findTimelineProjectOrThrow: vi.fn(),
  timelineBudgetStatusProjectSelect: { id: true },
  timelineShiftPreviewProjectSelect: { id: true },
}));

vi.mock("../router/timeline-project-read-support.js", () => ({
  buildTimelineBudgetStatusAllocations: vi.fn(),
  buildTimelineBudgetStatusResponse: vi.fn(),
  buildTimelineShiftPreviewDetailResponse: vi.fn(),
}));

import { listAssignmentBookings } from "@capakraken/application";
import { computeBudgetStatus } from "@capakraken/engine";
import { getAnonymizationDirectory } from "../lib/anonymization.js";
import {
  loadTimelineProjectContextDetailArtifacts,
} from "../router/timeline-project-context-support.js";
import {
  buildTimelineProjectContextDetailResponse,
  buildTimelineProjectContextResponse,
} from "../router/timeline-project-context-response-support.js";
import {
  loadTimelineProjectContext,
  previewTimelineProjectShift,
} from "../router/timeline-project-load-support.js";
import {
  buildTimelineBudgetStatusAllocations,
  buildTimelineBudgetStatusResponse,
  buildTimelineShiftPreviewDetailResponse,
} from "../router/timeline-project-read-support.js";
import {
  findTimelineProjectOrThrow,
  timelineBudgetStatusProjectSelect,
  timelineShiftPreviewProjectSelect,
} from "../router/timeline-project-query-support.js";
import {
  readTimelineProjectBudgetStatusResponse,
  readTimelineProjectContextDetailResponse,
  readTimelineProjectContextResponse,
  readTimelineProjectShiftPreviewDetail,
} from "../router/timeline-project-procedure-support.js";

const getAnonymizationDirectoryMock = vi.mocked(getAnonymizationDirectory);
const loadTimelineProjectContextMock = vi.mocked(loadTimelineProjectContext);
const previewTimelineProjectShiftMock = vi.mocked(previewTimelineProjectShift);
const loadTimelineProjectContextDetailArtifactsMock = vi.mocked(loadTimelineProjectContextDetailArtifacts);
const buildTimelineProjectContextResponseMock = vi.mocked(buildTimelineProjectContextResponse);
const buildTimelineProjectContextDetailResponseMock = vi.mocked(buildTimelineProjectContextDetailResponse);
const listAssignmentBookingsMock = vi.mocked(listAssignmentBookings);
const computeBudgetStatusMock = vi.mocked(computeBudgetStatus);
const findTimelineProjectOrThrowMock = vi.mocked(findTimelineProjectOrThrow);
const buildTimelineBudgetStatusAllocationsMock = vi.mocked(buildTimelineBudgetStatusAllocations);
const buildTimelineBudgetStatusResponseMock = vi.mocked(buildTimelineBudgetStatusResponse);
const buildTimelineShiftPreviewDetailResponseMock = vi.mocked(buildTimelineShiftPreviewDetailResponse);

describe("timeline project procedure support", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("builds project context responses from loaded context and anonymization directory", async () => {
    loadTimelineProjectContextMock.mockResolvedValueOnce({
      project: { id: "project_1" },
      allocations: [{ id: "allocation_1" }],
      demands: [{ id: "demand_1" }],
      assignments: [{ id: "assignment_1" }],
      allResourceAllocations: [{ id: "booking_1" }],
      resourceIds: ["resource_1"],
    } as never);
    getAnonymizationDirectoryMock.mockResolvedValueOnce({ enabled: true } as never);
    buildTimelineProjectContextResponseMock.mockReturnValueOnce({ ok: true } as never);

    await expect(readTimelineProjectContextResponse({} as never, "project_1")).resolves.toEqual({
      ok: true,
    });

    expect(loadTimelineProjectContextMock).toHaveBeenCalledWith({}, "project_1");
    expect(getAnonymizationDirectoryMock).toHaveBeenCalledWith({});
    expect(buildTimelineProjectContextResponseMock).toHaveBeenCalledWith({
      project: { id: "project_1" },
      allocations: [{ id: "allocation_1" }],
      demands: [{ id: "demand_1" }],
      assignments: [{ id: "assignment_1" }],
      allResourceAllocations: [{ id: "booking_1" }],
      resourceIds: ["resource_1"],
      directory: { enabled: true },
    });
  });

  it("builds project context detail responses from loaded artifacts", async () => {
    loadTimelineProjectContextMock.mockResolvedValueOnce({
      project: {
        id: "project_1",
        startDate: new Date("2026-04-01T00:00:00.000Z"),
        endDate: new Date("2026-04-10T00:00:00.000Z"),
      },
      allocations: [{ id: "allocation_1" }],
      demands: [{ id: "demand_1", startDate: new Date("2026-04-01T00:00:00.000Z") }],
      assignments: [{ id: "assignment_1", startDate: new Date("2026-04-02T00:00:00.000Z") }],
      allResourceAllocations: [{ id: "booking_1" }],
      resourceIds: ["resource_1"],
    } as never);
    getAnonymizationDirectoryMock.mockResolvedValueOnce({ enabled: true } as never);
    loadTimelineProjectContextDetailArtifactsMock.mockResolvedValueOnce({
      period: {
        startDate: new Date("2026-04-01T00:00:00.000Z"),
        endDate: new Date("2026-04-10T00:00:00.000Z"),
      },
      holidayOverlays: [{ id: "overlay_1" }],
      assignmentConflicts: [{ assignmentId: "assignment_1" }],
    } as never);
    buildTimelineProjectContextDetailResponseMock.mockReturnValueOnce({ detail: true } as never);

    await expect(
      readTimelineProjectContextDetailResponse({} as never, {
        projectId: "project_1",
        startDate: "2026-04-03",
        endDate: "2026-04-12",
        durationDays: 10,
      }),
    ).resolves.toEqual({ detail: true });

    expect(loadTimelineProjectContextDetailArtifactsMock).toHaveBeenCalledWith({}, {
      projectId: "project_1",
      requestedStartDate: "2026-04-03",
      requestedEndDate: "2026-04-12",
      durationDays: 10,
      projectStartDate: new Date("2026-04-01T00:00:00.000Z"),
      projectEndDate: new Date("2026-04-10T00:00:00.000Z"),
      firstAssignmentStartDate: new Date("2026-04-02T00:00:00.000Z"),
      firstDemandStartDate: new Date("2026-04-01T00:00:00.000Z"),
      assignments: [{ id: "assignment_1", startDate: new Date("2026-04-02T00:00:00.000Z") }],
      allResourceAllocations: [{ id: "booking_1" }],
      resourceIds: ["resource_1"],
    });
    expect(buildTimelineProjectContextDetailResponseMock).toHaveBeenCalledWith({
      project: {
        id: "project_1",
        startDate: new Date("2026-04-01T00:00:00.000Z"),
        endDate: new Date("2026-04-10T00:00:00.000Z"),
      },
      period: {
        startDate: new Date("2026-04-01T00:00:00.000Z"),
        endDate: new Date("2026-04-10T00:00:00.000Z"),
      },
      allocations: [{ id: "allocation_1" }],
      demands: [{ id: "demand_1", startDate: new Date("2026-04-01T00:00:00.000Z") }],
      assignments: [{ id: "assignment_1", startDate: new Date("2026-04-02T00:00:00.000Z") }],
      allResourceAllocations: [{ id: "booking_1" }],
      resourceIds: ["resource_1"],
      assignmentConflicts: [{ assignmentId: "assignment_1" }],
      holidayOverlays: [{ id: "overlay_1" }],
      directory: { enabled: true },
    });
  });

  it("builds shift preview detail responses", async () => {
    findTimelineProjectOrThrowMock.mockResolvedValueOnce({ id: "project_1" } as never);
    previewTimelineProjectShiftMock.mockResolvedValueOnce({ valid: true } as never);
    buildTimelineShiftPreviewDetailResponseMock.mockReturnValueOnce({ preview: true } as never);

    await expect(
      readTimelineProjectShiftPreviewDetail({} as never, {
        projectId: "project_1",
        newStartDate: new Date("2026-04-03T00:00:00.000Z"),
        newEndDate: new Date("2026-04-12T00:00:00.000Z"),
      }),
    ).resolves.toEqual({ preview: true });

    expect(findTimelineProjectOrThrowMock).toHaveBeenCalledWith({}, {
      projectId: "project_1",
      select: timelineShiftPreviewProjectSelect,
    });
    expect(previewTimelineProjectShiftMock).toHaveBeenCalledWith({}, {
      projectId: "project_1",
      newStartDate: new Date("2026-04-03T00:00:00.000Z"),
      newEndDate: new Date("2026-04-12T00:00:00.000Z"),
    });
    expect(buildTimelineShiftPreviewDetailResponseMock).toHaveBeenCalledWith({
      project: { id: "project_1" },
      requestedShift: {
        newStartDate: new Date("2026-04-03T00:00:00.000Z"),
        newEndDate: new Date("2026-04-12T00:00:00.000Z"),
      },
      preview: { valid: true },
    });
  });

  it("builds budget status responses from project bookings", async () => {
    findTimelineProjectOrThrowMock.mockResolvedValueOnce({
      id: "project_1",
      budgetCents: 100_000,
      winProbability: 80,
      startDate: new Date("2026-04-01T00:00:00.000Z"),
      endDate: new Date("2026-04-10T00:00:00.000Z"),
    } as never);
    listAssignmentBookingsMock.mockResolvedValueOnce([{ id: "booking_1" }] as never);
    buildTimelineBudgetStatusAllocationsMock.mockReturnValueOnce([{ id: "allocation_1" }] as never);
    computeBudgetStatusMock.mockReturnValueOnce({ withinBudget: true } as never);
    buildTimelineBudgetStatusResponseMock.mockReturnValueOnce({ budget: true } as never);

    await expect(readTimelineProjectBudgetStatusResponse({} as never, "project_1")).resolves.toEqual({
      budget: true,
    });

    expect(findTimelineProjectOrThrowMock).toHaveBeenCalledWith({}, {
      projectId: "project_1",
      select: timelineBudgetStatusProjectSelect,
    });
    expect(listAssignmentBookingsMock).toHaveBeenCalledWith({}, {
      projectIds: ["project_1"],
    });
    expect(buildTimelineBudgetStatusAllocationsMock).toHaveBeenCalledWith([{ id: "booking_1" }]);
    expect(computeBudgetStatusMock).toHaveBeenCalledWith(
      100_000,
      80,
      [{ id: "allocation_1" }],
      new Date("2026-04-01T00:00:00.000Z"),
      new Date("2026-04-10T00:00:00.000Z"),
    );
    expect(buildTimelineBudgetStatusResponseMock).toHaveBeenCalledWith({
      project: {
        id: "project_1",
        budgetCents: 100_000,
        winProbability: 80,
        startDate: new Date("2026-04-01T00:00:00.000Z"),
        endDate: new Date("2026-04-10T00:00:00.000Z"),
      },
      budgetStatus: { withinBudget: true },
      totalAllocations: 1,
    });
  });
});
