import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CompanyNotFoundError,
  FiscalYearNotFoundError,
  CostObjectNotFoundError,
  PlanVersionNotFoundError,
  InvalidNameError,
  BudgetHolderRequiredError,
} from "../lib/errors.generated";
import {
  baseCompany,
  baseFiscalYear,
  baseCostCenter,
  basePlanVersion,
  baseBudget,
  baseUser,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./createBudget";

const validInput = {
  companyId: baseCompany.id,
  name: "IT Budget 2024",
  description: "Information Technology budget for FY2024",
  budgetHolderId: baseUser.id,
  fiscalYearId: baseFiscalYear.id,
  costObjectType: "CostCenter" as const,
  costObjectId: baseCostCenter.id,
  planVersionId: basePlanVersion.id,
};

describe("createBudget", () => {
  it("creates budget in DRAFT status with required fields", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany)
      .mockReturnValueOnce(baseFiscalYear)
      .mockReturnValueOnce(baseCostCenter)
      .mockReturnValueOnce(basePlanVersion);
    spies.insert.mockReturnValueOnce(baseBudget);

    const result = await run(db, validInput, commandCtx);

    const value = expectOk(result);
    expect(value.budget).toEqual(baseBudget);
  });

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

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

    expectErr(result, CompanyNotFoundError);
  });

  it("returns error when company is not ACTIVE", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce({ ...baseCompany, status: "INACTIVE" });

    const result = await run(db, validInput, commandCtx);

    expectErr(result, CompanyNotFoundError);
  });

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

    const result = await run(db, validInput, commandCtx);

    expectErr(result, FiscalYearNotFoundError);
  });

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

    const result = await run(db, validInput, commandCtx);

    expectErr(result, CostObjectNotFoundError);
  });

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

    const result = await run(db, validInput, commandCtx);

    expectErr(result, PlanVersionNotFoundError);
  });

  it("returns error when name is missing", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany)
      .mockReturnValueOnce(baseFiscalYear)
      .mockReturnValueOnce(baseCostCenter)
      .mockReturnValueOnce(basePlanVersion);

    const result = await run(db, { ...validInput, name: "" }, commandCtx);

    expectErr(result, InvalidNameError);
  });

  it("returns error when budget holder is missing", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany)
      .mockReturnValueOnce(baseFiscalYear)
      .mockReturnValueOnce(baseCostCenter)
      .mockReturnValueOnce(basePlanVersion);

    const result = await run(db, { ...validInput, budgetHolderId: null }, commandCtx);

    expectErr(result, BudgetHolderRequiredError);
  });

  it("budgets from different companies are isolated", async () => {
    const companyB = { id: "company-2", status: "ACTIVE" };
    const budgetCompanyB = { ...baseBudget, id: "budget-company-b", companyId: companyB.id };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(companyB)
      .mockReturnValueOnce(baseFiscalYear)
      .mockReturnValueOnce(baseCostCenter)
      .mockReturnValueOnce(basePlanVersion);
    spies.insert.mockReturnValueOnce(budgetCompanyB);

    const result = await run(db, { ...validInput, companyId: companyB.id }, commandCtx);

    const value = expectOk(result);
    expect(value.budget.companyId).toBe(companyB.id);
  });

  it("emits audit event recording acting user, timestamp, and initial field values", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany)
      .mockReturnValueOnce(baseFiscalYear)
      .mockReturnValueOnce(baseCostCenter)
      .mockReturnValueOnce(basePlanVersion);
    spies.insert.mockReturnValueOnce(baseBudget);

    const result = await run(db, validInput, commandCtx);

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