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

vi.mock("../sse/event-bus.js", () => ({
  emitAllocationCreated: vi.fn(),
  emitAllocationDeleted: vi.fn(),
  emitAllocationUpdated: vi.fn(),
}));

vi.mock("../router/timeline-allocation-assignment-procedure-support.js", () => ({
  createTimelineBatchQuickAssignments: vi.fn(),
  createTimelineQuickAssignment: vi.fn(),
}));

vi.mock("../router/timeline-allocation-inline-support.js", () => ({
  applyTimelineInlineAllocationUpdate: vi.fn(),
}));

vi.mock("../router/timeline-allocation-fragment-support.js", () => ({
  carveTimelineAllocationRange: vi.fn(),
}));

vi.mock("../router/timeline-allocation-procedure-support.js", () => ({
  shiftTimelineAllocations: vi.fn(),
}));

import {
  emitAllocationCreated,
  emitAllocationDeleted,
  emitAllocationUpdated,
} from "../sse/event-bus.js";
import { carveTimelineAllocationRange } from "../router/timeline-allocation-fragment-support.js";
import {
  createTimelineBatchQuickAssignments,
  createTimelineQuickAssignment,
} from "../router/timeline-allocation-assignment-procedure-support.js";
import { applyTimelineInlineAllocationUpdate } from "../router/timeline-allocation-inline-support.js";
import { shiftTimelineAllocations } from "../router/timeline-allocation-procedure-support.js";
import {
  applyTimelineAllocationBatchShiftMutation,
  carveTimelineAllocationRangeMutation,
  createTimelineBatchQuickAssignMutation,
  createTimelineQuickAssignMutation,
  updateTimelineAllocationInlineMutation,
} from "../router/timeline-allocation-router-support.js";

const emitAllocationCreatedMock = vi.mocked(emitAllocationCreated);
const emitAllocationDeletedMock = vi.mocked(emitAllocationDeleted);
const emitAllocationUpdatedMock = vi.mocked(emitAllocationUpdated);
const carveTimelineAllocationRangeMock = vi.mocked(carveTimelineAllocationRange);
const createTimelineBatchQuickAssignmentsMock = vi.mocked(createTimelineBatchQuickAssignments);
const createTimelineQuickAssignmentMock = vi.mocked(createTimelineQuickAssignment);
const applyTimelineInlineAllocationUpdateMock = vi.mocked(applyTimelineInlineAllocationUpdate);
const shiftTimelineAllocationsMock = vi.mocked(shiftTimelineAllocations);

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

  it("updates an inline allocation and emits an update event", async () => {
    const db = {} as never;
    applyTimelineInlineAllocationUpdateMock.mockResolvedValueOnce({
      id: "allocation_1",
      projectId: "project_1",
      resourceId: "resource_1",
    } as never);

    await expect(
      updateTimelineAllocationInlineMutation({
        db,
        allocationId: "allocation_1",
        hoursPerDay: 8,
        startDate: new Date("2026-04-01T00:00:00.000Z"),
        endDate: new Date("2026-04-05T00:00:00.000Z"),
        includeSaturday: true,
        role: "Lead",
      }),
    ).resolves.toEqual({
      id: "allocation_1",
      projectId: "project_1",
      resourceId: "resource_1",
    });

    expect(applyTimelineInlineAllocationUpdateMock).toHaveBeenCalledWith({
      db,
      allocationId: "allocation_1",
      hoursPerDay: 8,
      startDate: new Date("2026-04-01T00:00:00.000Z"),
      endDate: new Date("2026-04-05T00:00:00.000Z"),
      includeSaturday: true,
      role: "Lead",
    });
    expect(emitAllocationUpdatedMock).toHaveBeenCalledWith({
      id: "allocation_1",
      projectId: "project_1",
      resourceId: "resource_1",
    });
  });

  it("creates a quick assignment with the router source metadata", async () => {
    const db = {} as never;
    createTimelineQuickAssignmentMock.mockResolvedValueOnce({ id: "allocation_1" } as never);

    await expect(
      createTimelineQuickAssignMutation(db, {
        resourceId: "resource_1",
        projectId: "project_1",
        startDate: new Date("2026-04-01T00:00:00.000Z"),
        endDate: new Date("2026-04-05T00:00:00.000Z"),
        hoursPerDay: 8,
        role: "Lead",
        status: "PROPOSED",
      }),
    ).resolves.toEqual({ id: "allocation_1" });

    expect(createTimelineQuickAssignmentMock).toHaveBeenCalledWith(db, {
      resourceId: "resource_1",
      projectId: "project_1",
      startDate: new Date("2026-04-01T00:00:00.000Z"),
      endDate: new Date("2026-04-05T00:00:00.000Z"),
      hoursPerDay: 8,
      role: "Lead",
      status: "PROPOSED",
      source: "quickAssign",
    });
  });

  it("maps batch quick assignments to the batch router source metadata", async () => {
    const db = {} as never;
    createTimelineBatchQuickAssignmentsMock.mockResolvedValueOnce({ count: 2 } as never);

    await expect(
      createTimelineBatchQuickAssignMutation(db, {
        assignments: [
          {
            resourceId: "resource_1",
            projectId: "project_1",
            startDate: new Date("2026-04-01T00:00:00.000Z"),
            endDate: new Date("2026-04-05T00:00:00.000Z"),
            hoursPerDay: 8,
            role: "Lead",
            status: "PROPOSED",
          },
          {
            resourceId: "resource_2",
            projectId: "project_2",
            startDate: new Date("2026-04-06T00:00:00.000Z"),
            endDate: new Date("2026-04-10T00:00:00.000Z"),
            hoursPerDay: 6,
            role: "Artist",
            status: "PROPOSED",
          },
        ],
      }),
    ).resolves.toEqual({ count: 2 });

    expect(createTimelineBatchQuickAssignmentsMock).toHaveBeenCalledWith(db, {
      assignments: [
        {
          resourceId: "resource_1",
          projectId: "project_1",
          startDate: new Date("2026-04-01T00:00:00.000Z"),
          endDate: new Date("2026-04-05T00:00:00.000Z"),
          hoursPerDay: 8,
          role: "Lead",
          status: "PROPOSED",
          source: "batchQuickAssign",
        },
        {
          resourceId: "resource_2",
          projectId: "project_2",
          startDate: new Date("2026-04-06T00:00:00.000Z"),
          endDate: new Date("2026-04-10T00:00:00.000Z"),
          hoursPerDay: 6,
          role: "Artist",
          status: "PROPOSED",
          source: "batchQuickAssign",
        },
      ],
    });
  });

  it("delegates allocation batch shifts unchanged", async () => {
    const db = {} as never;
    shiftTimelineAllocationsMock.mockResolvedValueOnce({ count: 3 } as never);

    await expect(
      applyTimelineAllocationBatchShiftMutation(db, {
        allocationIds: ["allocation_1", "allocation_2"],
        daysDelta: 5,
        mode: "preserve-duration",
      }),
    ).resolves.toEqual({ count: 3 });

    expect(shiftTimelineAllocationsMock).toHaveBeenCalledWith(db, {
      allocationIds: ["allocation_1", "allocation_2"],
      daysDelta: 5,
      mode: "preserve-duration",
    });
  });

  it("carves an allocation range and emits update/create/delete events for every affected fragment", async () => {
    const db = {} as never;
    const startDate = new Date("2026-04-09T00:00:00.000Z");
    const endDate = new Date("2026-04-10T00:00:00.000Z");

    carveTimelineAllocationRangeMock.mockResolvedValueOnce({
      action: "split",
      allocationGroupId: "group_1",
      updatedAllocationIds: ["allocation_left"],
      createdAllocationIds: ["allocation_right"],
      deletedAllocationIds: ["allocation_removed"],
      projectId: "project_1",
      resourceId: "resource_1",
    });

    await expect(
      carveTimelineAllocationRangeMutation({
        db,
        allocationId: "allocation_1",
        startDate,
        endDate,
      }),
    ).resolves.toEqual({
      action: "split",
      allocationGroupId: "group_1",
      updatedAllocationIds: ["allocation_left"],
      createdAllocationIds: ["allocation_right"],
      deletedAllocationIds: ["allocation_removed"],
      projectId: "project_1",
      resourceId: "resource_1",
    });

    expect(carveTimelineAllocationRangeMock).toHaveBeenCalledWith({
      db,
      allocationId: "allocation_1",
      startDate,
      endDate,
    });
    expect(emitAllocationUpdatedMock).toHaveBeenCalledWith({
      id: "allocation_left",
      projectId: "project_1",
      resourceId: "resource_1",
    });
    expect(emitAllocationCreatedMock).toHaveBeenCalledWith({
      id: "allocation_right",
      projectId: "project_1",
      resourceId: "resource_1",
    });
    expect(emitAllocationDeletedMock).toHaveBeenCalledWith(
      "allocation_removed",
      "project_1",
      "resource_1",
    );
  });
});
