import { Decimal } from "decimal.js";
import type { DB } from "../generated/kysely-tailordb";
import { ok, type ReadonlyDB } from "@tailor-platform/erp-kit/core";

export type GetPurchaseOrderInput =
  | { id: string }
  | { companyId: string; externalSupplierOrderReference: string };

export async function run(db: ReadonlyDB<DB>, input: GetPurchaseOrderInput) {
  let id: string;
  if ("id" in input) {
    id = input.id;
  } else {
    const purchaseOrder = await db
      .selectFrom("PurchaseOrder")
      .selectAll()
      .where("companyId", "=", input.companyId)
      .where("externalSupplierOrderReference", "=", input.externalSupplierOrderReference)
      .executeTakeFirst();
    if (!purchaseOrder) return ok({ purchaseOrder: null });
    id = purchaseOrder.id;
  }

  const purchaseOrder = await db
    .selectFrom("PurchaseOrder")
    .selectAll()
    .where("id", "=", id)
    .executeTakeFirst();
  if (!purchaseOrder) return ok({ purchaseOrder: null });

  const lines = await db
    .selectFrom("PurchaseOrderLine")
    .selectAll()
    .where("purchaseOrderId", "=", id)
    .execute();

  const lineIds = lines.map((l) => l.id);

  // Fetch fulfillment data only when there are lines
  const [receiptLines, billLines, matchedBills, postedReceipts] =
    lineIds.length > 0
      ? await Promise.all([
          db
            .selectFrom("GoodsReceiptLine")
            .selectAll()
            .where("purchaseOrderLineId", "in", lineIds)
            .execute(),
          db
            .selectFrom("PurchaseBillLine")
            .selectAll()
            .where("purchaseOrderLineId", "in", lineIds)
            .execute(),
          db.selectFrom("PurchaseBill").selectAll().where("status", "=", "MATCHED").execute(),
          db.selectFrom("GoodsReceipt").selectAll().where("status", "=", "POSTED").execute(),
        ])
      : [[], [], [], []];

  const postedReceiptIdSet = new Set(postedReceipts.map((r) => r.id));
  const matchedBillIdSet = new Set(matchedBills.map((b) => b.id));

  // Raw received quantity per individual PO line ID (from POSTED receipts only)
  const rawReceivedByLineId = new Map<string, Decimal>();
  for (const rl of receiptLines) {
    if (rl.purchaseOrderLineId && postedReceiptIdSet.has(rl.goodsReceiptId)) {
      rawReceivedByLineId.set(
        rl.purchaseOrderLineId,
        (rawReceivedByLineId.get(rl.purchaseOrderLineId) ?? new Decimal(0)).plus(rl.quantity),
      );
    }
  }

  // Raw billed quantity per individual PO line ID (from MATCHED bills only)
  const rawBilledByLineId = new Map<string, Decimal>();
  for (const bl of billLines) {
    if (bl.purchaseOrderLineId && matchedBillIdSet.has(bl.purchaseBillId)) {
      rawBilledByLineId.set(
        bl.purchaseOrderLineId,
        (rawBilledByLineId.get(bl.purchaseOrderLineId) ?? new Decimal(0)).plus(bl.quantity),
      );
    }
  }

  // Helper: get all line IDs sharing the same lineage root
  function getLineageIds(line: (typeof lines)[number]): string[] {
    const rootId = line.rootLineId ?? line.id;
    return lines.filter((l) => l.id === rootId || l.rootLineId === rootId).map((l) => l.id);
  }

  // Compute lineage-aware quantities per line
  const enrichedLines = lines.map((line) => {
    const lineageIds = getLineageIds(line);
    const receivedQuantity = lineageIds.reduce(
      (sum, lid) => sum.plus(rawReceivedByLineId.get(lid) ?? 0),
      new Decimal(0),
    );
    const billedQuantity = lineageIds.reduce(
      (sum, lid) => sum.plus(rawBilledByLineId.get(lid) ?? 0),
      new Decimal(0),
    );
    const qty = new Decimal(line.quantity);
    return {
      ...line,
      receivedQuantity: receivedQuantity.toString(),
      billedQuantity: billedQuantity.toString(),
      remainingQuantityToReceive: Decimal.max(qty.minus(receivedQuantity), 0).toString(),
      remainingQuantityToBill: Decimal.max(qty.minus(billedQuantity), 0).toString(),
    };
  });

  return ok({
    purchaseOrder: {
      ...purchaseOrder,
      lines: enrichedLines,
    },
  });
}
