import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import type { Selectable, Transaction } from "../generated/kysely-tailordb";
import { ApPurchaseOrderBillingSyncFailedError } from "../lib/errors.generated";
import type { PurchaseCommands } from "../module";

type AccountPayableDocument = Selectable<"AccountPayableDocument">;
type AccountPayableDocumentLine = Selectable<"AccountPayableDocumentLine">;

export async function syncPurchaseOrderBillingStatusFromAccountPayable(
  db: Transaction,
  input: {
    document: AccountPayableDocument;
    lines: AccountPayableDocumentLine[];
  },
  ctx: CommandContext,
  purchaseCommands: Pick<PurchaseCommands, "recalculatePurchaseOrderBillingStatus">,
) {
  const purchaseOrderLineIds = [
    ...new Set(
      input.lines.map((line) => line.purchaseOrderLineId).filter((id): id is string => id !== null),
    ),
  ];
  if (purchaseOrderLineIds.length === 0) {
    return ok(undefined);
  }

  const rows = await db
    .selectFrom("AccountPayableDocumentLine as line")
    .innerJoin("AccountPayableDocument as document", "document.id", "line.accountPayableDocumentId")
    .select([
      "line.purchaseOrderLineId as purchaseOrderLineId",
      "line.quantity as quantity",
      "line.correctionType as correctionType",
      "document.documentType as documentType",
    ])
    .where("line.purchaseOrderLineId", "in", purchaseOrderLineIds)
    .where("document.status", "in", ["REGISTERED", "POSTED"])
    .execute();

  const billedByLineId = new Map<string, Decimal>();
  for (const row of rows) {
    if (row.purchaseOrderLineId === null || row.quantity === null) continue;
    if (row.correctionType === "PRICE" || row.correctionType === "AMOUNT") continue;
    const signedQuantity =
      row.documentType === "CREDIT_MEMO"
        ? new Decimal(row.quantity).neg()
        : new Decimal(row.quantity);
    billedByLineId.set(
      row.purchaseOrderLineId,
      (billedByLineId.get(row.purchaseOrderLineId) ?? new Decimal(0)).plus(signedQuantity),
    );
  }

  const result = await purchaseCommands.recalculatePurchaseOrderBillingStatus(
    db,
    {
      lineBillings: purchaseOrderLineIds.map((purchaseOrderLineId) => ({
        purchaseOrderLineId,
        billedQuantity: (billedByLineId.get(purchaseOrderLineId) ?? new Decimal(0)).toString(),
      })),
    },
    ctx,
  );
  if (!result.ok) {
    return err(new ApPurchaseOrderBillingSyncFailedError(input.document.id));
  }
  return ok(undefined);
}
