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

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

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

vi.mock("../router/timeline-cost-support.js", () => ({
  calculateTimelineAllocationDailyCost: calculateTimelineAllocationDailyCostMock,
}));

import { applyTimelineInlineAllocationUpdate } from "../router/timeline-allocation-inline-support.js";

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

  it("updates staffed allocations with recalculated daily cost and audit changes", async () => {
    loadAllocationEntryMock.mockResolvedValue({
      entry: {
        id: "allocation_1",
        hoursPerDay: 4,
        startDate: new Date("2026-04-01T00:00:00.000Z"),
        endDate: new Date("2026-04-05T00:00:00.000Z"),
        metadata: {},
      },
      resourceId: "resource_1",
    });
    calculateTimelineAllocationDailyCostMock.mockResolvedValue(54000);
    updateAllocationEntryMock.mockResolvedValue({
      allocation: {
        id: "allocation_1",
        projectId: "project_1",
        resourceId: "resource_1",
      },
    });

    const tx = {
      auditLog: {
        create: vi.fn().mockResolvedValue({}),
      },
    };
    const db = {
      resource: {
        findUnique: vi.fn().mockResolvedValue({
          id: "resource_1",
          lcrCents: 5000,
          availability: { monday: 8, tuesday: 8, wednesday: 8, thursday: 8, friday: 8 },
        }),
      },
      $transaction: vi.fn(async (callback: (client: typeof tx) => unknown) => callback(tx)),
    };

    const result = await applyTimelineInlineAllocationUpdate({
      db: db as never,
      allocationId: "allocation_1",
      hoursPerDay: 6,
      endDate: new Date("2026-04-06T00:00:00.000Z"),
      includeSaturday: true,
      role: "Architect",
    });

    expect(result).toEqual({
      id: "allocation_1",
      projectId: "project_1",
      resourceId: "resource_1",
    });
    expect(calculateTimelineAllocationDailyCostMock).toHaveBeenCalledWith(
      expect.objectContaining({
        db,
        resourceId: "resource_1",
        lcrCents: 5000,
        hoursPerDay: 6,
        includeSaturday: true,
      }),
    );
    expect(updateAllocationEntryMock).toHaveBeenCalledWith(
      tx,
      {
        id: "allocation_1",
        demandRequirementUpdate: {
          hoursPerDay: 6,
          startDate: new Date("2026-04-01T00:00:00.000Z"),
          endDate: new Date("2026-04-06T00:00:00.000Z"),
          metadata: { includeSaturday: true },
          role: "Architect",
        },
        assignmentUpdate: {
          hoursPerDay: 6,
          startDate: new Date("2026-04-01T00:00:00.000Z"),
          endDate: new Date("2026-04-06T00:00:00.000Z"),
          dailyCostCents: 54000,
          metadata: { includeSaturday: true },
          role: "Architect",
        },
      },
    );
    expect(tx.auditLog.create).toHaveBeenCalledWith({
      data: expect.objectContaining({
        entityType: "Allocation",
        entityId: "allocation_1",
        action: "UPDATE",
      }),
    });
  });

  it("updates placeholder allocations without recalculating cost", async () => {
    loadAllocationEntryMock.mockResolvedValue({
      entry: {
        id: "demand_1",
        hoursPerDay: 4,
        startDate: new Date("2026-04-01T00:00:00.000Z"),
        endDate: new Date("2026-04-05T00:00:00.000Z"),
        metadata: {},
      },
      resourceId: null,
    });
    updateAllocationEntryMock.mockResolvedValue({
      allocation: {
        id: "demand_1",
        projectId: "project_1",
        resourceId: null,
      },
    });

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

    await expect(applyTimelineInlineAllocationUpdate({
      db: db as never,
      allocationId: "demand_1",
      hoursPerDay: 5,
    })).resolves.toEqual({
      id: "demand_1",
      projectId: "project_1",
      resourceId: null,
    });

    expect(db.resource.findUnique).not.toHaveBeenCalled();
    expect(calculateTimelineAllocationDailyCostMock).not.toHaveBeenCalled();
  });

  it("throws when a staffed allocation references a missing resource", async () => {
    loadAllocationEntryMock.mockResolvedValue({
      entry: {
        id: "allocation_1",
        hoursPerDay: 4,
        startDate: new Date("2026-04-01T00:00:00.000Z"),
        endDate: new Date("2026-04-05T00:00:00.000Z"),
        metadata: {},
      },
      resourceId: "resource_1",
    });

    await expect(applyTimelineInlineAllocationUpdate({
      db: {
        resource: {
          findUnique: vi.fn().mockResolvedValue(null),
        },
        $transaction: vi.fn(),
      } as never,
      allocationId: "allocation_1",
    })).rejects.toThrowError(new TRPCError({
      code: "NOT_FOUND",
      message: "Resource not found",
    }));
  });
});
