import { describe, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  BillingPolicyViolationError,
  DueScheduleNotComputableError,
} from "../lib/errors.generated";
import {
  basePaymentTerm,
  baseSalesInvoice,
  baseSalesInvoiceLine,
  baseSalesOrderLine,
  baseShipmentLine,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./issueSalesInvoice";

describe("issueSalesInvoice", () => {
  it("issues an order-based invoice when quantities and prices are valid", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const invoice = { ...baseSalesInvoice, billingPolicy: "ORDER" as const };
    spies.select
      .mockReturnValueOnce(invoice)
      .mockReturnValueOnce([baseSalesInvoiceLine])
      .mockReturnValueOnce(baseSalesOrderLine)
      .mockReturnValueOnce(basePaymentTerm)
      .mockReturnValueOnce(baseSalesOrderLine);
    spies.update
      .mockReturnValueOnce({ ...baseSalesOrderLine, invoicedQuantity: 5 })
      .mockReturnValueOnce({
        ...invoice,
        status: "ISSUED" as const,
      });

    const result = await run(db, { salesInvoiceId: invoice.id }, commandCtx);

    expectOk(result);
  });

  it("issues a shipment-gated invoice when posted shipment evidence exists", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const shipmentInvoiceLine = {
      ...baseSalesInvoiceLine,
      shipmentLineId: baseShipmentLine.id,
      billingPolicy: "SHIPMENT" as const,
      quantity: baseShipmentLine.quantity,
    };
    const invoice = { ...baseSalesInvoice, billingPolicy: "SHIPMENT" as const };
    spies.select
      .mockReturnValueOnce(invoice)
      .mockReturnValueOnce([shipmentInvoiceLine])
      .mockReturnValueOnce(baseSalesOrderLine)
      .mockReturnValueOnce(baseShipmentLine)
      .mockReturnValueOnce(basePaymentTerm)
      .mockReturnValueOnce(baseSalesOrderLine);
    spies.update
      .mockReturnValueOnce({ ...baseSalesOrderLine, invoicedQuantity: 4 })
      .mockReturnValueOnce({
        ...invoice,
        status: "ISSUED" as const,
      });

    const result = await run(db, { salesInvoiceId: invoice.id }, commandCtx);

    expectOk(result);
  });

  it("throws when draft invoice exceeds allowed billed quantity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseSalesInvoice)
      .mockReturnValueOnce([{ ...baseSalesInvoiceLine, quantity: 11 }])
      .mockReturnValueOnce(baseSalesOrderLine);

    const result = await run(db, { salesInvoiceId: baseSalesInvoice.id }, commandCtx);

    expectErr(result, BillingPolicyViolationError);
  });

  it("throws when due schedule cannot be computed", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce({ ...baseSalesInvoice, paymentTermId: null });

    const result = await run(db, { salesInvoiceId: baseSalesInvoice.id }, commandCtx);

    expectErr(result, DueScheduleNotComputableError);
  });
});
