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

describe("createFiscalYear", () => {
  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: "FY 2024",
        startDate: new Date("2024-01-01"),
        endDate: new Date("2024-12-31"),
      },
      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: "FY 2024",
        startDate: new Date("2024-01-01"),
        endDate: new Date("2024-12-31"),
      },
      commandCtx,
    );

    expectErr(result, CompanyNotFoundError);
  });

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

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "",
        startDate: new Date("2024-01-01"),
        endDate: new Date("2024-12-31"),
      },
      commandCtx,
    );

    expectErr(result, NameRequiredError);
  });

  it("returns error when start date is not before end date", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY 2024",
        startDate: new Date("2024-12-31"),
        endDate: new Date("2024-01-01"),
      },
      commandCtx,
    );

    expectErr(result, InvalidDateRangeError);
  });

  it("returns error when fiscal year overlaps with an existing fiscal year for the same company", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany).mockReturnValueOnce(baseFiscalYear);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY 2024 Overlap",
        startDate: new Date("2024-06-01"),
        endDate: new Date("2025-05-31"),
      },
      commandCtx,
    );

    expectErr(result, OverlappingFiscalYearError);
  });

  it("creates fiscal year with valid name, start date, and end date", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany).mockReturnValueOnce(undefined);
    spies.insert.mockReturnValueOnce(baseFiscalYear);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY 2024",
        startDate: new Date("2024-01-01"),
        endDate: new Date("2024-12-31"),
      },
      commandCtx,
    );

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

  it("creates non-calendar fiscal year (e.g., April-to-March) with correct date boundaries", async () => {
    const nonCalendarFiscalYear = {
      ...baseFiscalYear,
      id: "fiscal-year-non-calendar",
      name: "FY 2024-2025",
      startDate: new Date("2024-04-01"),
      endDate: new Date("2025-03-31"),
    };
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany).mockReturnValueOnce(undefined);
    spies.insert.mockReturnValueOnce(nonCalendarFiscalYear);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY 2024-2025",
        startDate: new Date("2024-04-01"),
        endDate: new Date("2025-03-31"),
      },
      commandCtx,
    );

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

  it("fiscal years from different companies are isolated", async () => {
    const companyB = { id: "company-2", status: "ACTIVE" };
    const fiscalYearCompanyB = {
      ...baseFiscalYear,
      id: "fiscal-year-company-b",
      companyId: companyB.id,
    };
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(companyB).mockReturnValueOnce(undefined);
    spies.insert.mockReturnValueOnce(fiscalYearCompanyB);

    const result = await run(
      db,
      {
        companyId: companyB.id,
        name: "FY 2024",
        startDate: new Date("2024-01-01"),
        endDate: new Date("2024-12-31"),
      },
      commandCtx,
    );

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

  it("emits audit event recording acting user, timestamp, company reference, and fiscal year details", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany).mockReturnValueOnce(undefined);
    spies.insert.mockReturnValueOnce(baseFiscalYear);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        name: "FY 2024",
        startDate: new Date("2024-01-01"),
        endDate: new Date("2024-12-31"),
      },
      commandCtx,
    );

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