import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import { purchaseBillLifecycle } from "../db/purchaseBill.lifecycle.generated";
import type { Transaction } from "../generated/kysely-tailordb";
import { calculateDueSchedule } from "../lib/domain";
import {
  MatchModeRequiredError,
  PaymentScheduleCalculationFailedError,
  PurchaseBillNotDraftError,
  PurchaseBillNotFoundError,
  PurchaseOrderCancelledError,
} from "../lib/errors.generated";
import {
  planThreeWayMatch,
  createThreeWayMatchEvents,
  updatePOBillingStatus,
} from "../lib/matching";

function priceVarianceExceeded(
  billedUnitPrice: string | number,
  orderedUnitPrice: string | number,
) {
  const billed = new Decimal(billedUnitPrice);
  const ordered = new Decimal(orderedUnitPrice);
  if (ordered.lte(0)) return billed.gt(0);
  return billed.gt(ordered);
}

export interface MatchPurchaseBillInput {
  id: string;
  matchingMode: "TWO_WAY" | "THREE_WAY";
}

export async function run(db: Transaction, input: MatchPurchaseBillInput, ctx: CommandContext) {
  void ctx;
  const purchaseBill = await db
    .selectFrom("PurchaseBill")
    .selectAll()
    .where("id", "=", input.id)
    .forUpdate()
    .executeTakeFirst();
  if (!purchaseBill) {
    return err(new PurchaseBillNotFoundError(input.id));
  }
  if (!purchaseBillLifecycle.tryTransition(purchaseBill.status, "match")) {
    return err(new PurchaseBillNotDraftError(input.id));
  }
  if (!input.matchingMode) {
    return err(new MatchModeRequiredError(input.id));
  }

  const billLines = await db
    .selectFrom("PurchaseBillLine")
    .selectAll()
    .where("purchaseBillId", "=", input.id)
    .execute();

  const postedReceipts = await db
    .selectFrom("GoodsReceipt")
    .selectAll()
    .where("status", "=", "POSTED")
    .execute();
  const postedReceiptIdSet = new Set(postedReceipts.map((r) => r.id));

  const affectedOrderIds = new Set<string>();
  // Map from bill line ID to the resolved PO line
  const resolvedOrderLines = new Map<
    string,
    NonNullable<Awaited<ReturnType<typeof getOrderLine>>>
  >();
  let varianceReason: string | null = null;
  let billAmount = new Decimal(0);

  // Helper: fetch PO line directly by stable ID (no lineage resolution needed)
  async function getOrderLine(lineId: string) {
    return db
      .selectFrom("PurchaseOrderLine")
      .selectAll()
      .where("id", "=", lineId)
      .executeTakeFirst();
  }

  for (const billLine of billLines) {
    if (billLine.purchaseOrderLineId === null) {
      billAmount = billAmount.plus(
        new Decimal(billLine.quantity).mul(billLine.billedUnitPrice ?? 0),
      );
      continue;
    }

    const orderLine = await getOrderLine(billLine.purchaseOrderLineId);
    if (!orderLine) continue;

    const purchaseOrder = await db
      .selectFrom("PurchaseOrder")
      .selectAll()
      .where("id", "=", orderLine.purchaseOrderId)
      .executeTakeFirst();
    if (!purchaseOrder) continue;

    if (purchaseOrder.orderStatus === "CANCELLED") {
      return err(new PurchaseOrderCancelledError(purchaseOrder.id));
    }

    affectedOrderIds.add(purchaseOrder.id);
    resolvedOrderLines.set(billLine.id, orderLine);

    if (
      billLine.billedUnitPrice !== null &&
      billLine.billedUnitPrice !== undefined &&
      priceVarianceExceeded(billLine.billedUnitPrice, orderLine.unitPrice)
    ) {
      varianceReason ??= "PRICE_VARIANCE_EXCEEDED";
    }

    billAmount = billAmount.plus(new Decimal(billLine.quantity).mul(billLine.billedUnitPrice ?? 0));
  }

  if (input.matchingMode === "THREE_WAY") {
    const plan = await planThreeWayMatch(db, billLines, resolvedOrderLines, postedReceiptIdSet);
    if (plan.quantityVarianceDetected) {
      varianceReason ??= "RECEIPT_QUANTITY_EXCEEDED";
    }

    if (!varianceReason && plan.allocations.length > 0) {
      await createThreeWayMatchEvents(db, plan.allocations);
    }
  }

  const dueSchedule = calculateDueSchedule(
    purchaseBill.paymentTermSnapshotLines ?? [],
    new Date(purchaseBill.invoiceDate),
    billAmount.toString(),
  );
  if ("error" in dueSchedule) {
    return err(new PaymentScheduleCalculationFailedError(input.id));
  }

  const nextStatus = purchaseBillLifecycle.tryTransition(
    purchaseBill.status,
    varianceReason ? "block" : "match",
  );
  if (!nextStatus) {
    return err(new PurchaseBillNotDraftError(input.id));
  }

  const matched = await db
    .updateTable("PurchaseBill")
    .set({
      status: nextStatus,
      matchingMode: input.matchingMode,
      varianceReason,
      dueSchedule: varianceReason ? null : dueSchedule.schedule,
      updatedAt: new Date(),
    })
    .where("id", "=", input.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  if (!varianceReason) {
    await updatePOBillingStatus(db, affectedOrderIds);
  }

  return ok({ purchaseBill: matched });
}
