import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import { AllocationCycleNotFoundError, InvalidStateError } from "../lib/errors.generated";
import {
  validatedAllocationCycle,
  baseAllocationCycle,
  baseAllocationRule,
  baseAllocationResult,
  activeCostElement,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./executeAllocationCycle";

describe("executeAllocationCycle", () => {
  it("executes a VALIDATED allocation cycle in ACTUAL mode transitioning it to EXECUTED status", async () => {
    const executed = { ...validatedAllocationCycle, status: "EXECUTED" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(validatedAllocationCycle) // cycle lookup
      .mockReturnValueOnce([baseAllocationRule]); // rules lookup
    spies.insert.mockReturnValueOnce(baseAllocationResult);
    spies.update.mockReturnValueOnce(executed);

    const result = await run(
      db,
      {
        allocationCycleId: validatedAllocationCycle.id,
        costElementId: activeCostElement.id,
        mode: "ACTUAL",
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.allocationCycle.status).toBe("EXECUTED");
  });

  it("creates AllocationResult records in ACTUAL mode", async () => {
    const executed = { ...validatedAllocationCycle, status: "EXECUTED" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(validatedAllocationCycle)
      .mockReturnValueOnce([baseAllocationRule]);
    spies.insert.mockReturnValueOnce(baseAllocationResult);
    spies.update.mockReturnValueOnce(executed);

    const result = await run(
      db,
      {
        allocationCycleId: validatedAllocationCycle.id,
        costElementId: activeCostElement.id,
        mode: "ACTUAL",
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.allocationResults).toHaveLength(1);
    expect(spies.insert).toHaveBeenCalled();
  });

  it("executes in TEST_RUN mode returning preview results without status change", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(validatedAllocationCycle)
      .mockReturnValueOnce([baseAllocationRule]);

    const result = await run(
      db,
      {
        allocationCycleId: validatedAllocationCycle.id,
        costElementId: activeCostElement.id,
        mode: "TEST_RUN",
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.allocationCycle.status).toBe("VALIDATED");
    expect(value.allocationResults).toHaveLength(1);
    expect(value.allocationResults[0].costElementId).toBe(activeCostElement.id);
    expect(spies.update).not.toHaveBeenCalled();
    expect(spies.insert).not.toHaveBeenCalled();
  });

  it("returns error when allocation cycle does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        allocationCycleId: "missing-id",
        costElementId: activeCostElement.id,
        mode: "ACTUAL",
      },
      commandCtx,
    );

    expectErr(result, AllocationCycleNotFoundError);
  });

  it("returns error when allocation cycle is not in VALIDATED status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseAllocationCycle);

    const result = await run(
      db,
      {
        allocationCycleId: baseAllocationCycle.id,
        costElementId: activeCostElement.id,
        mode: "ACTUAL",
      },
      commandCtx,
    );

    expectErr(result, InvalidStateError);
  });

  it("each AllocationResult references cycle, sender, receiver, amount, and cost element", async () => {
    const executed = { ...validatedAllocationCycle, status: "EXECUTED" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(validatedAllocationCycle)
      .mockReturnValueOnce([baseAllocationRule]);
    spies.insert.mockReturnValueOnce(baseAllocationResult);
    spies.update.mockReturnValueOnce(executed);

    const result = await run(
      db,
      {
        allocationCycleId: validatedAllocationCycle.id,
        costElementId: activeCostElement.id,
        mode: "ACTUAL",
      },
      commandCtx,
    );

    const value = expectOk(result);
    const ar = value.allocationResults[0];
    expect(ar).toBeDefined();
  });

  it("emits audit event recording the execution and acting user", async () => {
    const executed = { ...validatedAllocationCycle, status: "EXECUTED" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(validatedAllocationCycle)
      .mockReturnValueOnce([baseAllocationRule]);
    spies.insert.mockReturnValueOnce(baseAllocationResult);
    spies.update.mockReturnValueOnce(executed);

    const result = await run(
      db,
      {
        allocationCycleId: validatedAllocationCycle.id,
        costElementId: activeCostElement.id,
        mode: "ACTUAL",
      },
      commandCtx,
    );

    expectOk(result);
    expect(spies.update).toHaveBeenCalled();
  });
});
