import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CompanyNotFoundError,
  DuplicateOrderNumberError,
  InvalidOrderTypeError,
  DescriptionRequiredError,
  ResponsiblePersonRequiredError,
  CostCenterNotFoundError,
  InvalidValidityPeriodError,
} from "../lib/errors.generated";
import { baseCompany, baseCostCenter, baseInternalOrder, baseUser } from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./createInternalOrder";

const validInput = {
  companyId: baseCompany.id,
  orderNumber: "IO-001",
  orderType: "OVERHEAD",
  description: "IT Infrastructure Upgrade",
  responsiblePersonId: baseUser.id,
  costCenterId: baseCostCenter.id,
};

describe("createInternalOrder", () => {
  it("creates internal order in CREATED status with required fields", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // company lookup, duplicate check, cost center lookup
    spies.select
      .mockReturnValueOnce(baseCompany)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(baseCostCenter);
    spies.insert.mockReturnValueOnce(baseInternalOrder);

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

    const value = expectOk(result);
    expect(value.internalOrder).toEqual(baseInternalOrder);
  });

  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-company" }, 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 order number already exists within the company", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseCompany).mockReturnValueOnce(baseInternalOrder);

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

    expectErr(result, DuplicateOrderNumberError);
  });

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

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

    expectErr(result, InvalidOrderTypeError);
  });

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

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

    expectErr(result, DescriptionRequiredError);
  });

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

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

    expectErr(result, ResponsiblePersonRequiredError);
  });

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

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

    expectErr(result, CostCenterNotFoundError);
  });

  it("returns error when cost center belongs to a different company", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // company found, no duplicate, cost center query returns undefined (wrong company filter)
    spies.select
      .mockReturnValueOnce(baseCompany)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(undefined);

    const result = await run(db, { ...validInput, costCenterId: "cc-other-company" }, commandCtx);

    expectErr(result, CostCenterNotFoundError);
  });

  it("returns error when validFrom is after validTo", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(baseCostCenter);

    const result = await run(
      db,
      {
        ...validInput,
        validFrom: new Date("2025-12-31"),
        validTo: new Date("2025-01-01"),
      },
      commandCtx,
    );

    expectErr(result, InvalidValidityPeriodError);
  });

  it("creates internal order with optional validity dates", async () => {
    const orderWithDates = {
      ...baseInternalOrder,
      validFrom: new Date("2024-01-01"),
      validTo: new Date("2024-12-31"),
    };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseCompany)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(baseCostCenter);
    spies.insert.mockReturnValueOnce(orderWithDates);

    const result = await run(
      db,
      {
        ...validInput,
        validFrom: new Date("2024-01-01"),
        validTo: new Date("2024-12-31"),
      },
      commandCtx,
    );

    const value = expectOk(result);
    expect(value.internalOrder).toEqual(orderWithDates);
  });

  it("internal orders from different companies are isolated", async () => {
    const companyB = { id: "company-2", status: "ACTIVE" };
    const costCenterB = { ...baseCostCenter, id: "cc-b", companyId: companyB.id };
    const orderB = { ...baseInternalOrder, id: "io-b", companyId: companyB.id };
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(companyB)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(costCenterB);
    spies.insert.mockReturnValueOnce(orderB);

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

    const value = expectOk(result);
    expect(value.internalOrder.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(undefined)
      .mockReturnValueOnce(baseCostCenter);
    spies.insert.mockReturnValueOnce(baseInternalOrder);

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

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