import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  CompanyScopeMismatchError,
  EmptyReceiptLinesError,
  InvalidReceivedQuantityError,
  NoOpenReceiptQuantityError,
  PurchaseOrderLineNotFoundError,
  PurchaseOrderNotFoundError,
  PurchaseOrderNotReceivableError,
} from "../lib/errors.generated";
import {
  baseCompany,
  baseGoodsReceipt,
  baseGoodsReceiptLines,
  baseItem,
  basePurchaseOrder,
  orderedPurchaseOrder,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./createGoodsReceipt";

describe("createGoodsReceipt", () => {
  it("creates a draft goods receipt against an ordered purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(orderedPurchaseOrder);
    spies.insert.mockReturnValueOnce({
      ...baseGoodsReceipt,
      purchaseOrderId: orderedPurchaseOrder.id,
    });

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        supplierId: orderedPurchaseOrder.supplierId,
        purchaseOrderId: orderedPurchaseOrder.id,
        lines: baseGoodsReceiptLines,
      },
      commandCtx,
    );

    expect(expectOk(result).goodsReceipt.status).toBe("DRAFT");
    expect(spies.values).toHaveBeenCalledWith(
      expect.objectContaining({ purchaseOrderId: orderedPurchaseOrder.id, status: "DRAFT" }),
    );
  });

  it("creates a draft goods receipt against a partially received purchase order", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const partiallyReceivedOrder = {
      ...orderedPurchaseOrder,
      status: "PARTIALLY_RECEIVED",
      lines: JSON.stringify([
        {
          ...baseGoodsReceiptLines[0],
          id: "po-line-1",
          quantity: 10,
          receivedQuantity: 4,
          unitPrice: 100,
          itemId: baseItem.id,
          unitId: "unit-1",
          receivingSiteId: "site-1",
          requiresPhysicalReceipt: true,
          billedQuantity: 0,
        },
      ]),
    };
    spies.select.mockReturnValueOnce(partiallyReceivedOrder);
    spies.insert.mockReturnValueOnce({
      ...baseGoodsReceipt,
      purchaseOrderId: partiallyReceivedOrder.id,
    });

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        supplierId: partiallyReceivedOrder.supplierId,
        purchaseOrderId: partiallyReceivedOrder.id,
        lines: [{ ...baseGoodsReceiptLines[0], quantity: 2 }],
      },
      commandCtx,
    );

    expect(expectOk(result).goodsReceipt.purchaseOrderId).toBe(partiallyReceivedOrder.id);
  });

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

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        supplierId: orderedPurchaseOrder.supplierId,
        purchaseOrderId: "missing-order",
        lines: baseGoodsReceiptLines,
      },
      commandCtx,
    );

    expectErr(result, PurchaseOrderNotFoundError);
  });

  it("returns error when purchase order is cancelled or closed", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce({ ...orderedPurchaseOrder, status: "CANCELLED" });

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        supplierId: orderedPurchaseOrder.supplierId,
        purchaseOrderId: orderedPurchaseOrder.id,
        lines: baseGoodsReceiptLines,
      },
      commandCtx,
    );

    expectErr(result, PurchaseOrderNotReceivableError);
  });

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

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

    expectErr(result, EmptyReceiptLinesError);
  });

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

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        supplierId: orderedPurchaseOrder.supplierId,
        purchaseOrderId: orderedPurchaseOrder.id,
        lines: [{ ...baseGoodsReceiptLines[0], purchaseOrderLineId: "missing-line" }],
      },
      commandCtx,
    );

    expectErr(result, PurchaseOrderLineNotFoundError);
  });

  it("returns error when a receipt line has no open quantity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const exhaustedOrder = {
      ...orderedPurchaseOrder,
      lines: JSON.stringify([
        {
          id: "po-line-1",
          itemId: baseItem.id,
          quantity: 4,
          unitPrice: 100,
          unitId: "unit-1",
          receivingSiteId: "site-1",
          requiresPhysicalReceipt: true,
          matchedPriceRuleId: null,
          priceSnapshot: null,
          receivedQuantity: 4,
          billedQuantity: 0,
        },
      ]),
    };
    spies.select.mockReturnValueOnce(exhaustedOrder);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        supplierId: orderedPurchaseOrder.supplierId,
        purchaseOrderId: orderedPurchaseOrder.id,
        lines: baseGoodsReceiptLines,
      },
      commandCtx,
    );

    expectErr(result, NoOpenReceiptQuantityError);
  });

  it("returns error when received quantity is zero or negative", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(orderedPurchaseOrder);

    const result = await run(
      db,
      {
        companyId: baseCompany.id,
        supplierId: orderedPurchaseOrder.supplierId,
        purchaseOrderId: orderedPurchaseOrder.id,
        lines: [{ ...baseGoodsReceiptLines[0], quantity: 0 }],
      },
      commandCtx,
    );

    expectErr(result, InvalidReceivedQuantityError);
  });

  it("returns error when goods receipt company does not match the purchase order company", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePurchaseOrder);

    const result = await run(
      db,
      {
        companyId: "company-2",
        supplierId: basePurchaseOrder.supplierId,
        purchaseOrderId: basePurchaseOrder.id,
        lines: baseGoodsReceiptLines,
      },
      commandCtx,
    );

    expectErr(result, CompanyScopeMismatchError);
  });
});
