import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  EmptyPurchaseOrderLinesError,
  ItemNotActiveError,
  ItemNotFoundError,
  InvalidQuantityError,
  InvalidUnitPriceError,
  PartnerNotSupplierError,
  PaymentTermInactiveError,
  ReceivingSiteNotFoundError,
  ReceivingSiteRequiredError,
  SupplierNotActiveError,
  SupplierNotFoundError,
} from "../lib/errors.generated";
import {
  baseItem,
  basePaymentTerm,
  basePurchaseOrder,
  basePurchaseOrderLines,
  baseReceivingSite,
  baseSupplier,
  baseSupplierProfile,
  inactiveSupplier,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./createPurchaseOrder";

describe("createPurchaseOrder", () => {
  it("creates a draft purchase order with valid supplier and lines", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce({ role: "SUPPLIER" })
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseReceivingSite);
    spies.insert.mockReturnValueOnce(basePurchaseOrder);

    const result = await run(
      db,
      {
        companyId: basePurchaseOrder.companyId,
        supplierId: baseSupplier.id,
        lines: basePurchaseOrderLines,
      },
      commandCtx,
    );

    expect(expectOk(result).purchaseOrder.status).toBe("DRAFT");
  });

  it("inherits default payment term from supplier profile when present", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce({ role: "SUPPLIER" })
      .mockReturnValueOnce(baseSupplierProfile)
      .mockReturnValueOnce(basePaymentTerm)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(baseReceivingSite);
    spies.insert.mockReturnValueOnce(basePurchaseOrder);

    await run(
      db,
      {
        companyId: basePurchaseOrder.companyId,
        supplierId: baseSupplier.id,
        lines: basePurchaseOrderLines,
      },
      commandCtx,
    );

    expect(spies.values).toHaveBeenCalledWith(
      expect.objectContaining({ paymentTermId: basePaymentTerm.id }),
    );
  });

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

    const result = await run(
      db,
      {
        companyId: basePurchaseOrder.companyId,
        supplierId: "missing",
        lines: basePurchaseOrderLines,
      },
      commandCtx,
    );

    expectErr(result, SupplierNotFoundError);
  });

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

    const result = await run(
      db,
      {
        companyId: basePurchaseOrder.companyId,
        supplierId: inactiveSupplier.id,
        lines: basePurchaseOrderLines,
      },
      commandCtx,
    );

    expectErr(result, SupplierNotActiveError);
  });

  it("returns error when partner is not a supplier", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseSupplier).mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        companyId: basePurchaseOrder.companyId,
        supplierId: baseSupplier.id,
        lines: basePurchaseOrderLines,
      },
      commandCtx,
    );

    expectErr(result, PartnerNotSupplierError);
  });

  it("returns error when no lines are provided", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseSupplier).mockReturnValueOnce({ role: "SUPPLIER" });

    const result = await run(
      db,
      { companyId: basePurchaseOrder.companyId, supplierId: baseSupplier.id, lines: [] },
      commandCtx,
    );

    expectErr(result, EmptyPurchaseOrderLinesError);
  });

  it("returns error when an item does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce({ role: "SUPPLIER" })
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        companyId: basePurchaseOrder.companyId,
        supplierId: baseSupplier.id,
        lines: basePurchaseOrderLines,
      },
      commandCtx,
    );

    expectErr(result, ItemNotFoundError);
  });

  it("returns error when an item is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce({ role: "SUPPLIER" })
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce({ ...baseItem, status: "INACTIVE" });

    const result = await run(
      db,
      {
        companyId: basePurchaseOrder.companyId,
        supplierId: baseSupplier.id,
        lines: basePurchaseOrderLines,
      },
      commandCtx,
    );

    expectErr(result, ItemNotActiveError);
  });

  it("returns error when quantity is zero or negative", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce({ role: "SUPPLIER" })
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(baseItem);

    const result = await run(
      db,
      {
        companyId: basePurchaseOrder.companyId,
        supplierId: baseSupplier.id,
        lines: [{ ...basePurchaseOrderLines[0], quantity: 0 }],
      },
      commandCtx,
    );

    expectErr(result, InvalidQuantityError);
  });

  it("returns error when unit price is negative", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce({ role: "SUPPLIER" })
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(baseItem);

    const result = await run(
      db,
      {
        companyId: basePurchaseOrder.companyId,
        supplierId: baseSupplier.id,
        lines: [{ ...basePurchaseOrderLines[0], unitPrice: -1 }],
      },
      commandCtx,
    );

    expectErr(result, InvalidUnitPriceError);
  });

  it("returns error when receiving site is missing for a physical line", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce({ role: "SUPPLIER" })
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(baseItem);

    const result = await run(
      db,
      {
        companyId: basePurchaseOrder.companyId,
        supplierId: baseSupplier.id,
        lines: [{ ...basePurchaseOrderLines[0], receivingSiteId: null }],
      },
      commandCtx,
    );

    expectErr(result, ReceivingSiteRequiredError);
  });

  it("returns error when receiving site does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce({ role: "SUPPLIER" })
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce(baseItem)
      .mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        companyId: basePurchaseOrder.companyId,
        supplierId: baseSupplier.id,
        lines: basePurchaseOrderLines,
      },
      commandCtx,
    );

    expectErr(result, ReceivingSiteNotFoundError);
  });

  it("returns error when inherited payment term is inactive", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce({ role: "SUPPLIER" })
      .mockReturnValueOnce(baseSupplierProfile)
      .mockReturnValueOnce({ ...basePaymentTerm, status: "INACTIVE" });

    const result = await run(
      db,
      {
        companyId: basePurchaseOrder.companyId,
        supplierId: baseSupplier.id,
        lines: basePurchaseOrderLines,
      },
      commandCtx,
    );

    expectErr(result, PaymentTermInactiveError);
  });
});
