import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  AllocationCycleNotFoundError,
  InvalidStateError,
  NoAllocationRulesError,
  InvalidPercentageError,
  InvalidPercentageSumError,
} from "../lib/errors.generated";
import {
  baseAllocationCycle,
  baseAllocationRule,
  validatedAllocationCycle,
  baseCostCenter,
  baseCostCenter2,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./validateAllocationCycle";

describe("validateAllocationCycle", () => {
  it("validates a DRAFT allocation cycle with valid rules transitioning it to VALIDATED status", async () => {
    const validated = { ...baseAllocationCycle, status: "VALIDATED" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseAllocationCycle) // cycle lookup
      .mockReturnValueOnce([baseAllocationRule]); // rules lookup (execute returns array)
    spies.update.mockReturnValueOnce(validated);

    const result = await run(db, { allocationCycleId: baseAllocationCycle.id }, commandCtx);

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

  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" }, commandCtx);

    expectErr(result, AllocationCycleNotFoundError);
  });

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

    const result = await run(db, { allocationCycleId: validatedAllocationCycle.id }, commandCtx);

    expectErr(result, InvalidStateError);
  });

  it("returns error when cycle has no allocation rules", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseAllocationCycle) // cycle
      .mockReturnValueOnce([]); // no rules

    const result = await run(db, { allocationCycleId: baseAllocationCycle.id }, commandCtx);

    expectErr(result, NoAllocationRulesError);
  });

  it("returns error when percentages do not sum to 100% for a sender", async () => {
    const partialRule = { ...baseAllocationRule, percentage: 50 };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseAllocationCycle) // cycle
      .mockReturnValueOnce([partialRule]); // rules with 50% only

    const result = await run(db, { allocationCycleId: baseAllocationCycle.id }, commandCtx);

    expectErr(result, InvalidPercentageSumError);
  });

  it("returns error when any percentage is zero or negative", async () => {
    const invalidRule = { ...baseAllocationRule, percentage: -50 };
    const offsetRule = {
      ...baseAllocationRule,
      id: "rule-offset",
      receiverId: baseCostCenter2.id,
      percentage: 150,
    };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseAllocationCycle)
      .mockReturnValueOnce([invalidRule, offsetRule]);

    const result = await run(db, { allocationCycleId: baseAllocationCycle.id }, commandCtx);

    expectErr(result, InvalidPercentageError);
  });

  it("validates with multiple senders each summing to 100%", async () => {
    const rule1 = {
      ...baseAllocationRule,
      id: "rule-1",
      senderCostCenterId: baseCostCenter.id,
      receiverId: baseCostCenter2.id,
      percentage: 100,
    };
    const rule2 = {
      ...baseAllocationRule,
      id: "rule-2",
      senderCostCenterId: baseCostCenter2.id,
      receiverId: baseCostCenter.id,
      percentage: 60,
    };
    const rule3 = {
      ...baseAllocationRule,
      id: "rule-3",
      senderCostCenterId: baseCostCenter2.id,
      receiverId: baseCostCenter.id,
      percentage: 40,
    };
    const validated = { ...baseAllocationCycle, status: "VALIDATED" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseAllocationCycle)
      .mockReturnValueOnce([rule1, rule2, rule3]);
    spies.update.mockReturnValueOnce(validated);

    const result = await run(db, { allocationCycleId: baseAllocationCycle.id }, commandCtx);

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

  it("emits audit event recording the status transition and acting user", async () => {
    const validated = { ...baseAllocationCycle, status: "VALIDATED" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseAllocationCycle).mockReturnValueOnce([baseAllocationRule]);
    spies.update.mockReturnValueOnce(validated);

    const result = await run(db, { allocationCycleId: baseAllocationCycle.id }, commandCtx);

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