import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import { purchaseOrderLifecycle } from "../db/purchaseOrder.lifecycle.generated";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  PurchaseOrderAlreadyFulfilledError,
  PurchaseOrderNotCancellableError,
  PurchaseOrderNotFoundError,
} from "../lib/errors.generated";
import type { InventoryCommands, InventoryQueries } from "../module";

export interface CancelPurchaseOrderInput {
  id: string;
}

export async function run(
  db: Transaction,
  input: CancelPurchaseOrderInput,
  ctx: CommandContext,
  inventoryCommands: Pick<InventoryCommands, "closeInventorySupplyPlan">,
  inventoryQueries: Pick<InventoryQueries, "listPurchaseReceiptEvidenceForMatching">,
) {
  const purchaseOrder = await db
    .selectFrom("PurchaseOrder")
    .selectAll()
    .where("id", "=", input.id)
    .forUpdate()
    .executeTakeFirst();
  if (!purchaseOrder) {
    return err(new PurchaseOrderNotFoundError(input.id));
  }
  const nextOrderStatus = purchaseOrderLifecycle.tryTransition(purchaseOrder.orderStatus, "cancel");
  if (!nextOrderStatus) {
    return err(new PurchaseOrderNotCancellableError(input.id));
  }

  const purchaseOrderLines = await db
    .selectFrom("PurchaseOrderLine")
    .selectAll()
    .where("purchaseOrderId", "=", input.id)
    .execute();
  const purchaseOrderLineIds = new Set(purchaseOrderLines.map((line) => line.id));

  if (purchaseOrderLineIds.size > 0) {
    const receiptEvidenceResult = await inventoryQueries.listPurchaseReceiptEvidenceForMatching(
      db,
      { purchaseOrderId: input.id, purchaseOrderLineIds: [...purchaseOrderLineIds] },
      ctx,
    );
    if (receiptEvidenceResult.value.receiptEvidence.length > 0) {
      return err(new PurchaseOrderAlreadyFulfilledError(input.id));
    }

    const billedLine = purchaseOrderLines.find((line) =>
      new Decimal(line.billedQuantity ?? "0").gt(0),
    );
    if (billedLine) {
      return err(new PurchaseOrderAlreadyFulfilledError(input.id));
    }
  }

  const cancelled = await db
    .updateTable("PurchaseOrder")
    .set({ orderStatus: nextOrderStatus })
    .where("id", "=", input.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  const closeResult = await inventoryCommands.closeInventorySupplyPlan(
    db,
    { target: "SOURCE_DOCUMENT", sourceType: "PURCHASE_ORDER", sourceId: input.id },
    ctx,
  );
  if (!closeResult.ok) return closeResult;

  return ok({ purchaseOrder: cancelled });
}
