import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  BudgetNotFoundError,
  InvalidStateError,
  DuplicateActiveBudgetError,
} from "../lib/errors.generated";
import { approvedBudget, baseBudget, activeBudget, basePlanVersion } from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./activateBudget";

const forecastPlanVersion = { ...basePlanVersion, id: "plan-version-forecast", type: "FORECAST" };
const simulationPlanVersion = { ...basePlanVersion, id: "plan-version-sim", type: "SIMULATION" };

describe("activateBudget", () => {
  it("activates an APPROVED budget transitioning it to ACTIVE status", async () => {
    const activatedBudget = { ...approvedBudget, status: "ACTIVE" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(approvedBudget)
      .mockReturnValueOnce(basePlanVersion) // plan version type BUDGET
      .mockReturnValueOnce(undefined); // no existing active
    spies.update.mockReturnValueOnce(activatedBudget);

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

    const value = expectOk(result);
    expect(value.budget.status).toBe("ACTIVE");
  });

  it("enables funds checking upon activation", async () => {
    const activatedBudget = { ...approvedBudget, status: "ACTIVE" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(approvedBudget)
      .mockReturnValueOnce(basePlanVersion)
      .mockReturnValueOnce(undefined);
    spies.update.mockReturnValueOnce(activatedBudget);

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

    const value = expectOk(result);
    expect(value.budget.status).toBe("ACTIVE");
  });

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

    const result = await run(db, { budgetId: "missing" }, commandCtx);

    expectErr(result, BudgetNotFoundError);
  });

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

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

    expectErr(result, InvalidStateError);
  });

  it("returns error when another ACTIVE BUDGET exists for the same cost object and fiscal year", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(approvedBudget)
      .mockReturnValueOnce(basePlanVersion) // type BUDGET
      .mockReturnValueOnce(activeBudget) // existing active
      .mockReturnValueOnce(basePlanVersion); // existing active's plan version is also BUDGET
    // no update mock needed — should return error

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

    expectErr(result, DuplicateActiveBudgetError);
  });

  it("allows multiple ACTIVE FORECAST versions for the same cost object and fiscal year", async () => {
    const forecastApproved = { ...approvedBudget, planVersionId: forecastPlanVersion.id };
    const activatedBudget = { ...forecastApproved, status: "ACTIVE" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(forecastApproved).mockReturnValueOnce(forecastPlanVersion); // type FORECAST — skip uniqueness check
    spies.update.mockReturnValueOnce(activatedBudget);

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

    const value = expectOk(result);
    expect(value.budget.status).toBe("ACTIVE");
  });

  it("allows multiple ACTIVE SIMULATION versions for the same cost object and fiscal year", async () => {
    const simApproved = { ...approvedBudget, planVersionId: simulationPlanVersion.id };
    const activatedBudget = { ...simApproved, status: "ACTIVE" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(simApproved).mockReturnValueOnce(simulationPlanVersion); // type SIMULATION — skip uniqueness check
    spies.update.mockReturnValueOnce(activatedBudget);

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

    const value = expectOk(result);
    expect(value.budget.status).toBe("ACTIVE");
  });

  it("emits audit event recording the status transition and acting user", async () => {
    const activatedBudget = { ...approvedBudget, status: "ACTIVE" };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(approvedBudget)
      .mockReturnValueOnce(basePlanVersion)
      .mockReturnValueOnce(undefined);
    spies.update.mockReturnValueOnce(activatedBudget);

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

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