import { err, InsufficientPermissionError } from "@tailor-platform/erp-kit/core";
import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  ApDocumentNotFoundError,
  ApInvalidDocumentStatusError,
  ApPurchaseOrderBillingSyncFailedError,
} from "../lib/errors.generated";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import {
  baseAccountPayableDocumentLine,
  baseAccountPayableDocument,
  postedAccountPayableDocument,
  registeredAccountPayableDocument,
} from "../testing/fixtures";
import { mockPurchaseCommands } from "../testing/moduleMocks";
import { run } from "./cancelAccountPayableDocument";

function runCancel(
  mock: ReturnType<typeof createKyselyMock<Namespace["main-db"]>>,
  input: Parameters<typeof run>[1],
  purchaseCommands = mockPurchaseCommands(),
) {
  return mock.withTx((trx) => run(trx, input, commandCtx, purchaseCommands));
}

describe("cancelAccountPayableDocument", () => {
  it("cancels DRAFT AP document", async () => {
    const cancelled = {
      ...baseAccountPayableDocument,
      status: "CANCELLED",
      cancelledAt: new Date(),
    };
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAccountPayableDocument], [cancelled]);

    const purchaseCommands = mockPurchaseCommands();
    const result = await runCancel(mock, { id: baseAccountPayableDocument.id }, purchaseCommands);

    expect(expectOk(result).accountPayableDocument.status).toBe("CANCELLED");
    expect(purchaseCommands.recalculatePurchaseOrderBillingStatus).not.toHaveBeenCalled();
  });

  it("cancels REGISTERED AP document", async () => {
    const cancelled = {
      ...registeredAccountPayableDocument,
      status: "CANCELLED",
      cancelledAt: new Date(),
    };
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [registeredAccountPayableDocument],
      [cancelled],
      [baseAccountPayableDocumentLine],
      [],
    );

    const purchaseCommands = mockPurchaseCommands();
    const result = await runCancel(
      mock,
      { id: registeredAccountPayableDocument.id },
      purchaseCommands,
    );

    expect(expectOk(result).accountPayableDocument.status).toBe("CANCELLED");
    expect(purchaseCommands.recalculatePurchaseOrderBillingStatus.mock.lastCall?.[1]).toEqual({
      lineBillings: [{ purchaseOrderLineId: "purchase-order-line-1", billedQuantity: "0" }],
    });
  });

  it("syncs purchase-order billing status when cancelling REGISTERED AP invoice", async () => {
    const cancelled = {
      ...registeredAccountPayableDocument,
      status: "CANCELLED",
      cancelledAt: new Date(),
    };
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [registeredAccountPayableDocument],
      [cancelled],
      [baseAccountPayableDocumentLine],
      [{ ...baseAccountPayableDocumentLine, quantity: "3" }],
    );

    const purchaseCommands = mockPurchaseCommands();
    const result = await runCancel(
      mock,
      { id: registeredAccountPayableDocument.id },
      purchaseCommands,
    );

    expect(expectOk(result).accountPayableDocument.status).toBe("CANCELLED");
    expect(purchaseCommands.recalculatePurchaseOrderBillingStatus.mock.lastCall?.[1]).toEqual({
      lineBillings: [{ purchaseOrderLineId: "purchase-order-line-1", billedQuantity: "3" }],
    });
  });

  it("returns error when AP document does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await runCancel(mock, { id: "missing" });

    expectErr(result, ApDocumentNotFoundError);
  });

  it("returns error when AP document is POSTED", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([postedAccountPayableDocument]);

    const result = await runCancel(mock, { id: postedAccountPayableDocument.id });

    expectErr(result, ApInvalidDocumentStatusError);
  });

  it("returns error when purchase-order billing sync fails", async () => {
    const cancelled = {
      ...registeredAccountPayableDocument,
      status: "CANCELLED",
      cancelledAt: new Date(),
    };
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [registeredAccountPayableDocument],
      [cancelled],
      [baseAccountPayableDocumentLine],
      [],
    );

    const purchaseCommands = mockPurchaseCommands();
    purchaseCommands.recalculatePurchaseOrderBillingStatus.mockResolvedValueOnce(
      err(
        new InsufficientPermissionError(
          commandCtx.actorId,
          "purchase:purchaseOrder:recalculatePurchaseOrderBillingStatus",
        ),
      ),
    );

    const result = await runCancel(
      mock,
      { id: registeredAccountPayableDocument.id },
      purchaseCommands,
    );

    expectErr(result, ApPurchaseOrderBillingSyncFailedError);
  });
});
