import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import type { Transaction } from "../generated/kysely-tailordb";
import { deriveBillingStatus, type LineProgress } from "../lib/domain";
import {
  NegativeBilledQuantityError,
  PurchaseOrderLineNotFoundError,
} from "../lib/errors.generated";

export interface PurchaseOrderLineBillingInput {
  purchaseOrderLineId: string;
  billedQuantity: string;
}

export interface RecalculatePurchaseOrderBillingStatusInput {
  lineBillings: PurchaseOrderLineBillingInput[];
}

export async function run(
  db: Transaction,
  input: RecalculatePurchaseOrderBillingStatusInput,
  _ctx: CommandContext,
) {
  if (input.lineBillings.length === 0) {
    return ok({ purchaseOrders: [] });
  }

  const billedByLineId = new Map<string, Decimal>();
  for (const lineBilling of input.lineBillings) {
    const billedQuantity = new Decimal(lineBilling.billedQuantity);
    if (billedQuantity.lt(0)) {
      return err(new NegativeBilledQuantityError(lineBilling.purchaseOrderLineId));
    }
    billedByLineId.set(lineBilling.purchaseOrderLineId, billedQuantity);
  }

  const purchaseOrderLineIds = [...billedByLineId.keys()];
  const referencedLines = await db
    .selectFrom("PurchaseOrderLine")
    .selectAll()
    .where("id", "in", purchaseOrderLineIds)
    .forUpdate()
    .execute();

  const foundLineIds = new Set(referencedLines.map((line) => line.id));
  const missingLineId = purchaseOrderLineIds.find((lineId) => !foundLineIds.has(lineId));
  if (missingLineId) {
    return err(new PurchaseOrderLineNotFoundError(missingLineId));
  }

  for (const line of referencedLines) {
    await db
      .updateTable("PurchaseOrderLine")
      .set({
        billedQuantity: (billedByLineId.get(line.id) ?? new Decimal(0)).toString(),
        updatedAt: new Date(),
      })
      .where("id", "=", line.id)
      .execute();
  }

  const affectedOrderIds = [...new Set(referencedLines.map((line) => line.purchaseOrderId))];
  const purchaseOrders = [];
  for (const purchaseOrderId of affectedOrderIds) {
    await db
      .selectFrom("PurchaseOrder")
      .selectAll()
      .where("id", "=", purchaseOrderId)
      .forUpdate()
      .executeTakeFirstOrThrow();

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

    const lineProgress: LineProgress[] = orderLines.map((line) => ({
      quantity: line.quantity,
      billedQuantity: line.billedQuantity ?? "0",
    }));

    const purchaseOrder = await db
      .updateTable("PurchaseOrder")
      .set({
        billingStatus: deriveBillingStatus(lineProgress),
        updatedAt: new Date(),
      })
      .where("id", "=", purchaseOrderId)
      .returningAll()
      .executeTakeFirstOrThrow();

    purchaseOrders.push(purchaseOrder);
  }

  return ok({ purchaseOrders });
}
