import { describe, it, expect } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import {
  FiscalYearNotFoundError,
  NameRequiredError,
  DatesImmutableError,
  InvalidDateRangeError,
  OverlappingFiscalYearError,
} from "../lib/errors.generated";
import { baseFiscalYear, baseFiscalYear2, baseAccountingPeriod } from "../testing/fixtures";
import { run } from "./updateFiscalYear";

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

    expectErr(
      await run(db, { id: "missing-id", yearName: "FY Updated" }, commandCtx),
      FiscalYearNotFoundError,
    );
  });

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

    expectErr(
      await run(db, { id: baseFiscalYear.id, yearName: "" }, commandCtx),
      NameRequiredError,
    );
  });

  it("returns error when attempting to change dates after accounting periods exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseFiscalYear).mockReturnValueOnce(baseAccountingPeriod);

    expectErr(
      await run(db, { id: baseFiscalYear.id, startDate: new Date("2024-02-01") }, commandCtx),
      DatesImmutableError,
    );
  });

  it("returns error when updated start date is not before end date", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseFiscalYear).mockReturnValueOnce(undefined); // no accounting periods

    expectErr(
      await run(
        db,
        {
          id: baseFiscalYear.id,
          startDate: new Date("2025-01-01"),
          endDate: new Date("2024-06-01"),
        },
        commandCtx,
      ),
      InvalidDateRangeError,
    );
  });

  it("returns error when updated date range overlaps with another fiscal year", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseFiscalYear)
      .mockReturnValueOnce(undefined) // no accounting periods
      .mockReturnValueOnce(baseFiscalYear2); // overlapping fiscal year found

    expectErr(
      await run(
        db,
        {
          id: baseFiscalYear.id,
          startDate: new Date("2024-06-01"),
          endDate: new Date("2025-06-01"),
        },
        commandCtx,
      ),
      OverlappingFiscalYearError,
    );
  });

  it("updates fiscal year name successfully", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updated = { ...baseFiscalYear, name: "FY 2024 Renamed" };
    spies.select.mockReturnValueOnce(baseFiscalYear);
    spies.update.mockReturnValueOnce(updated);

    const result = await run(
      db,
      { id: baseFiscalYear.id, yearName: "FY 2024 Renamed" },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.fiscalYear).toEqual(updated);
  });

  it("updates fiscal year dates when no accounting periods exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const newStart = new Date("2024-02-01");
    const newEnd = new Date("2024-11-30");
    const updated = { ...baseFiscalYear, startDate: newStart, endDate: newEnd };
    spies.select
      .mockReturnValueOnce(baseFiscalYear)
      .mockReturnValueOnce(undefined) // no accounting periods
      .mockReturnValueOnce(undefined); // no overlapping fiscal years
    spies.update.mockReturnValueOnce(updated);

    const result = await run(
      db,
      { id: baseFiscalYear.id, startDate: newStart, endDate: newEnd },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.fiscalYear).toEqual(updated);
  });

  it("emits audit event recording acting user, timestamp, and changed fields", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updated = { ...baseFiscalYear, name: "FY 2024 Audited" };
    spies.select.mockReturnValueOnce(baseFiscalYear);
    spies.update.mockReturnValueOnce(updated);

    const result = await run(
      db,
      { id: baseFiscalYear.id, yearName: "FY 2024 Audited" },
      commandCtx,
    );

    // Verify the command completes successfully (audit event emission is a side effect)
    const value = expectOk(result);
    expect(value.fiscalYear).toEqual(updated);
  });
});
