import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  DuplicateSupplierInvoiceNumberError,
  EmptyBillLinesError,
  InvalidBilledQuantityError,
  PaymentTermSnapshotMissingError,
  PurchaseOrderCancelledError,
  PurchaseOrderLineNotFoundError,
  SupplierNotFoundError,
  SupplierTaxIdentificationMissingError,
} from "../lib/errors.generated";
import {
  basePurchaseBill,
  basePurchaseBillLines,
  basePurchaseOrder,
  baseSupplier,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./createPurchaseBill";

describe("createPurchaseBill", () => {
  it("creates a draft purchase bill with valid line references", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdBill = { ...basePurchaseBill, id: "purchase-bill-new" };
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce([basePurchaseOrder]);
    spies.insert.mockReturnValueOnce(createdBill);

    const result = await run(
      db,
      {
        companyId: basePurchaseBill.companyId,
        supplierId: baseSupplier.id,
        invoiceNumber: "INV-NEW",
        invoiceDate: basePurchaseBill.invoiceDate,
        lines: basePurchaseBillLines,
        supplierTaxIdentification: "TAX-SNAPSHOT",
      },
      commandCtx,
    );

    expect(expectOk(result).purchaseBill.status).toBe("DRAFT");
    expect(spies.values).toHaveBeenCalledWith(
      expect.objectContaining({ invoiceNumber: "INV-NEW", status: "DRAFT" }),
    );
  });

  it("snapshots payment-term data at create time", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce([basePurchaseOrder]);
    spies.insert.mockReturnValueOnce(basePurchaseBill);

    await run(
      db,
      {
        companyId: basePurchaseBill.companyId,
        supplierId: baseSupplier.id,
        invoiceNumber: "INV-SNAPSHOT",
        invoiceDate: basePurchaseBill.invoiceDate,
        lines: basePurchaseBillLines,
        supplierTaxIdentification: "TAX-SNAPSHOT",
      },
      commandCtx,
    );

    expect(spies.values).toHaveBeenCalledWith(
      expect.objectContaining({ paymentTermSnapshot: basePurchaseOrder.paymentTermSnapshot }),
    );
  });

  it("snapshots supplier tax identification at create time", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce([basePurchaseOrder])
      .mockReturnValueOnce({ value: "TAX-DEFAULT" });
    spies.insert.mockReturnValueOnce({
      ...basePurchaseBill,
      supplierTaxIdentification: "TAX-DEFAULT",
    });

    const result = await run(
      db,
      {
        companyId: basePurchaseBill.companyId,
        supplierId: baseSupplier.id,
        invoiceNumber: "INV-TAX",
        invoiceDate: basePurchaseBill.invoiceDate,
        lines: basePurchaseBillLines,
      },
      commandCtx,
    );

    expect(expectOk(result).purchaseBill.supplierTaxIdentification).toBe("TAX-DEFAULT");
    expect(spies.values).toHaveBeenCalledWith(
      expect.objectContaining({ supplierTaxIdentification: "TAX-DEFAULT" }),
    );
  });

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

    const result = await run(
      db,
      {
        companyId: basePurchaseBill.companyId,
        supplierId: "missing-supplier",
        invoiceNumber: "INV-001",
        invoiceDate: basePurchaseBill.invoiceDate,
        lines: basePurchaseBillLines,
      },
      commandCtx,
    );

    expectErr(result, SupplierNotFoundError);
  });

  it("returns error when invoice number is duplicated", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseSupplier).mockReturnValueOnce(basePurchaseBill);

    const result = await run(
      db,
      {
        companyId: basePurchaseBill.companyId,
        supplierId: baseSupplier.id,
        invoiceNumber: basePurchaseBill.invoiceNumber,
        invoiceDate: basePurchaseBill.invoiceDate,
        lines: basePurchaseBillLines,
      },
      commandCtx,
    );

    expectErr(result, DuplicateSupplierInvoiceNumberError);
  });

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

    const result = await run(
      db,
      {
        companyId: basePurchaseBill.companyId,
        supplierId: baseSupplier.id,
        invoiceNumber: "INV-EMPTY",
        invoiceDate: basePurchaseBill.invoiceDate,
        lines: [],
      },
      commandCtx,
    );

    expectErr(result, EmptyBillLinesError);
  });

  it("returns error when a purchase-order line does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce([basePurchaseOrder]);

    const result = await run(
      db,
      {
        companyId: basePurchaseBill.companyId,
        supplierId: baseSupplier.id,
        invoiceNumber: "INV-MISSING-LINE",
        invoiceDate: basePurchaseBill.invoiceDate,
        lines: [{ ...basePurchaseBillLines[0], purchaseOrderLineId: "missing-line" }],
      },
      commandCtx,
    );

    expectErr(result, PurchaseOrderLineNotFoundError);
  });

  it("returns error when billed quantity is zero or negative", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce([basePurchaseOrder]);

    const result = await run(
      db,
      {
        companyId: basePurchaseBill.companyId,
        supplierId: baseSupplier.id,
        invoiceNumber: "INV-BAD-QTY",
        invoiceDate: basePurchaseBill.invoiceDate,
        lines: [{ ...basePurchaseBillLines[0], quantity: 0 }],
      },
      commandCtx,
    );

    expectErr(result, InvalidBilledQuantityError);
  });

  it("returns error when referenced purchase order is cancelled", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const cancelledOrder = { ...basePurchaseOrder, status: "CANCELLED" };
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce([cancelledOrder]);

    const result = await run(
      db,
      {
        companyId: basePurchaseBill.companyId,
        supplierId: baseSupplier.id,
        invoiceNumber: "INV-CANCELLED",
        invoiceDate: basePurchaseBill.invoiceDate,
        lines: basePurchaseBillLines,
      },
      commandCtx,
    );

    expectErr(result, PurchaseOrderCancelledError);
  });

  it("returns error when no payment-term snapshot is available", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce([{ ...basePurchaseOrder, paymentTermSnapshot: null }]);

    const result = await run(
      db,
      {
        companyId: basePurchaseBill.companyId,
        supplierId: baseSupplier.id,
        invoiceNumber: "INV-NO-TERM",
        invoiceDate: basePurchaseBill.invoiceDate,
        lines: basePurchaseBillLines,
        supplierTaxIdentification: "TAX-SNAPSHOT",
      },
      commandCtx,
    );

    expectErr(result, PaymentTermSnapshotMissingError);
  });

  it("returns error when supplier tax identification cannot be snapshotted", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSupplier)
      .mockReturnValueOnce(undefined)
      .mockReturnValueOnce([basePurchaseOrder])
      .mockReturnValueOnce(undefined);

    const result = await run(
      db,
      {
        companyId: basePurchaseBill.companyId,
        supplierId: baseSupplier.id,
        invoiceNumber: "INV-NO-TAX",
        invoiceDate: basePurchaseBill.invoiceDate,
        lines: basePurchaseBillLines,
      },
      commandCtx,
    );

    expectErr(result, SupplierTaxIdentificationMissingError);
  });
});
