import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CompanyNotFoundError,
  InvalidNameError,
  InvalidVersionTypeError,
  BaseVersionNotFoundError,
} from "../lib/errors.generated";
import { baseCompany, basePlanVersion } from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./createPlanVersion";

describe("createPlanVersion", () => {
  it("creates a plan version of type BUDGET", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany);
    spies.insert.mockReturnValueOnce(basePlanVersion);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY2024 Budget",
        type: "BUDGET",
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.planVersion).toEqual(basePlanVersion);
  });

  it("creates a plan version of type FORECAST", async () => {
    const forecastVersion = {
      ...basePlanVersion,
      id: "plan-version-forecast",
      type: "FORECAST" as const,
    };
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany);
    spies.insert.mockReturnValueOnce(forecastVersion);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY2024 Forecast",
        type: "FORECAST",
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.planVersion.type).toBe("FORECAST");
  });

  it("creates a plan version of type SIMULATION", async () => {
    const simulationVersion = {
      ...basePlanVersion,
      id: "plan-version-sim",
      type: "SIMULATION" as const,
    };
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany);
    spies.insert.mockReturnValueOnce(simulationVersion);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY2024 Simulation",
        type: "SIMULATION",
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.planVersion.type).toBe("SIMULATION");
  });

  it("creates a plan version with a base version reference", async () => {
    const versionWithBase = {
      ...basePlanVersion,
      id: "plan-version-with-base",
      baseVersionId: basePlanVersion.id,
    };
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany).mockReturnValueOnce(basePlanVersion); // base version lookup
    spies.insert.mockReturnValueOnce(versionWithBase);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY2024 Budget v2",
        type: "BUDGET",
        baseVersionId: basePlanVersion.id,
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.planVersion.baseVersionId).toBe(basePlanVersion.id);
  });

  it("creates a plan version without a base version reference", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany);
    spies.insert.mockReturnValueOnce(basePlanVersion);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY2024 Budget",
        type: "BUDGET",
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.planVersion.baseVersionId).toBeNull();
  });

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

    const result = await run(
      db,
      {
        companyId: "missing-company",
        name: "FY2024 Budget",
        type: "BUDGET",
      },
      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,
      {
        companyId: baseCompany.id,
        name: "FY2024 Budget",
        type: "BUDGET",
      },
      commandCtx,
    );

    expectErr(result, CompanyNotFoundError);
  });

  it("returns error when version type is invalid", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY2024 Budget",
        type: "INVALID_TYPE",
      },
      commandCtx,
    );

    expectErr(result, InvalidVersionTypeError);
  });

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

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "",
        type: "BUDGET",
      },
      commandCtx,
    );

    expectErr(result, InvalidNameError);
  });

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

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY2024 Budget v2",
        type: "BUDGET",
        baseVersionId: "missing-version",
      },
      commandCtx,
    );

    expectErr(result, BaseVersionNotFoundError);
  });

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

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY2024 Budget",
        type: "BUDGET",
      },
      commandCtx,
    );

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