import { TRPCError } from "@trpc/server";
import { describe, expect, it, vi } from "vitest";

const {
  findAllocationEntryMock,
  updateAllocationEntryMock,
} = vi.hoisted(() => ({
  findAllocationEntryMock: vi.fn(),
  updateAllocationEntryMock: vi.fn(),
}));

vi.mock("@capakraken/application", async (importOriginal) => {
  const actual = await importOriginal<typeof import("@capakraken/application")>();
  return {
    ...actual,
    findAllocationEntry: findAllocationEntryMock,
    updateAllocationEntry: updateAllocationEntryMock,
  };
});

import { applyTimelineBatchAllocationShift } from "../router/timeline-allocation-shift-support.js";

describe("timeline allocation shift support", () => {
  it("returns an empty result when the shift delta is zero", async () => {
    const db = {
      $transaction: vi.fn(),
    };

    await expect(applyTimelineBatchAllocationShift({
      db: db as never,
      allocationIds: ["allocation_1"],
      daysDelta: 0,
      mode: "move",
    })).resolves.toEqual([]);

    expect(findAllocationEntryMock).not.toHaveBeenCalled();
    expect(db.$transaction).not.toHaveBeenCalled();
  });

  it("throws when no allocations can be resolved", async () => {
    findAllocationEntryMock.mockResolvedValue(null);

    await expect(applyTimelineBatchAllocationShift({
      db: {
        $transaction: vi.fn(),
      } as never,
      allocationIds: ["missing_1", "missing_2"],
      daysDelta: 2,
      mode: "move",
    })).rejects.toThrowError(new TRPCError({
      code: "NOT_FOUND",
      message: "No allocations found",
    }));
  });

  it("updates resolved allocations and writes a batch audit record", async () => {
    findAllocationEntryMock
      .mockResolvedValueOnce({
        entry: {
          id: "allocation_1",
          startDate: new Date("2026-04-01T00:00:00.000Z"),
          endDate: new Date("2026-04-05T00:00:00.000Z"),
        },
      })
      .mockResolvedValueOnce(null)
      .mockResolvedValueOnce({
        entry: {
          id: "allocation_3",
          startDate: new Date("2026-04-10T00:00:00.000Z"),
          endDate: new Date("2026-04-12T00:00:00.000Z"),
        },
      });

    updateAllocationEntryMock
      .mockResolvedValueOnce({
        allocation: { id: "allocation_1", projectId: "project_1", resourceId: "resource_1" },
      })
      .mockResolvedValueOnce({
        allocation: { id: "allocation_3", projectId: "project_2", resourceId: "resource_2" },
      });

    const tx = {
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
    };
    const db = {
      $transaction: vi.fn(async (callback: (client: typeof tx) => unknown) => callback(tx)),
    };

    const result = await applyTimelineBatchAllocationShift({
      db: db as never,
      allocationIds: ["allocation_1", "missing_2", "allocation_3"],
      daysDelta: 2,
      mode: "move",
    });

    expect(result).toEqual([
      { id: "allocation_1", projectId: "project_1", resourceId: "resource_1" },
      { id: "allocation_3", projectId: "project_2", resourceId: "resource_2" },
    ]);
    expect(updateAllocationEntryMock).toHaveBeenNthCalledWith(
      1,
      tx,
      {
        id: "allocation_1",
        demandRequirementUpdate: {
          startDate: new Date("2026-04-03T00:00:00.000Z"),
          endDate: new Date("2026-04-07T00:00:00.000Z"),
        },
        assignmentUpdate: {
          startDate: new Date("2026-04-03T00:00:00.000Z"),
          endDate: new Date("2026-04-07T00:00:00.000Z"),
        },
      },
    );
    expect(updateAllocationEntryMock).toHaveBeenNthCalledWith(
      2,
      tx,
      {
        id: "allocation_3",
        demandRequirementUpdate: {
          startDate: new Date("2026-04-12T00:00:00.000Z"),
          endDate: new Date("2026-04-14T00:00:00.000Z"),
        },
        assignmentUpdate: {
          startDate: new Date("2026-04-12T00:00:00.000Z"),
          endDate: new Date("2026-04-14T00:00:00.000Z"),
        },
      },
    );
    expect(tx.auditLog.create).toHaveBeenCalledWith({
      data: {
        entityType: "Allocation",
        entityId: "allocation_1,missing_2,allocation_3",
        action: "UPDATE",
        changes: {
          operation: "batchShift",
          mode: "move",
          daysDelta: 2,
          count: 2,
        },
      },
    });
  });
});
