import { Decimal } from "decimal.js";
import type { Transaction } from "../generated/kysely-tailordb";

/**
 * applyValuation
 *
 * Applies valuation logic (FIFO / AVCO / STANDARD_COST) for a stock
 * movement.  When no ItemValuation record exists for the item the
 * function is a silent no-op — this handles items that are not cost-tracked.
 */
export async function applyValuation(
  db: Transaction,
  args: {
    itemId: string;
    inventoryLedgerId: string | null;
    dcIndicator: "DR" | "CR";
    quantity: string | number;
    unitCost?: string | number | null;
  },
): Promise<void> {
  const { itemId, inventoryLedgerId, dcIndicator, quantity, unitCost } = args;

  // 1. Look up ItemValuation to find the associated ValuationPolicy
  const itemValuation = await db
    .selectFrom("ItemValuation")
    .selectAll()
    .where("itemId", "=", itemId)
    .executeTakeFirst();

  if (!itemValuation) {
    // No valuation policy associated — skip (no-op)
    return;
  }

  const policy = await db
    .selectFrom("ValuationPolicy")
    .selectAll()
    .where("id", "=", itemValuation.valuationPolicyId)
    .executeTakeFirst();

  if (!policy) {
    // Policy deleted or missing — skip silently
    return;
  }

  const costingMethod = policy.costingMethod;
  const qty = new Decimal(quantity);

  if (dcIndicator === "DR") {
    // ------ Stock IN ------
    const effectiveUnitCost =
      costingMethod === "STANDARD_COST"
        ? new Decimal(policy.standardCostRate ?? 0)
        : new Decimal(unitCost ?? 0);

    if (costingMethod === "FIFO") {
      // Create a new CostLayer
      await db
        .insertInto("CostLayer")
        .values({
          itemId,
          valuationPolicyId: policy.id,
          inventoryLedgerId: inventoryLedgerId ?? "",
          quantity: qty.toString(),
          unitCost: effectiveUnitCost.toString(),
          remainingQuantity: qty.toString(),
        })
        .returningAll()
        .executeTakeFirst();
    }

    // Update ItemValuation totals (applies to all methods)
    const newTotalQuantity = new Decimal(itemValuation.totalQuantity).plus(qty);
    const newTotalValue = new Decimal(itemValuation.totalValue).plus(qty.mul(effectiveUnitCost));
    const newCostPerUnit = newTotalQuantity.gt(0)
      ? newTotalValue.div(newTotalQuantity)
      : new Decimal(0);

    await db
      .updateTable("ItemValuation")
      .set({
        totalQuantity: newTotalQuantity.toString(),
        totalValue: newTotalValue.toString(),
        costPerUnit: newCostPerUnit.toString(),
      })
      .where("id", "=", itemValuation.id)
      .returningAll()
      .executeTakeFirst();
  } else {
    // ------ Stock OUT (CR) ------
    if (costingMethod === "FIFO") {
      // Consume from oldest CostLayers first
      let remaining = qty;
      const layers = await db
        .selectFrom("CostLayer")
        .selectAll()
        .where("itemId", "=", itemId)
        .where("remainingQuantity", ">", "0")
        .orderBy("createdAt", "asc")
        .execute();

      for (const layer of layers) {
        if (remaining.lte(0)) break;
        const layerRemaining = new Decimal(layer.remainingQuantity);
        const consume = Decimal.min(layerRemaining, remaining);
        await db
          .updateTable("CostLayer")
          .set({
            remainingQuantity: layerRemaining.minus(consume).toString(),
          })
          .where("id", "=", layer.id)
          .returningAll()
          .executeTakeFirst();
        remaining = remaining.minus(consume);
      }
    }

    // Update ItemValuation totals
    const effectiveUnitCost =
      costingMethod === "STANDARD_COST"
        ? new Decimal(policy.standardCostRate ?? 0)
        : new Decimal(itemValuation.costPerUnit);

    const newTotalQuantity = Decimal.max(0, new Decimal(itemValuation.totalQuantity).minus(qty));
    const newTotalValue = Decimal.max(
      0,
      new Decimal(itemValuation.totalValue).minus(qty.mul(effectiveUnitCost)),
    );
    const newCostPerUnit = newTotalQuantity.gt(0)
      ? newTotalValue.div(newTotalQuantity)
      : new Decimal(0);

    await db
      .updateTable("ItemValuation")
      .set({
        totalQuantity: newTotalQuantity.toString(),
        totalValue: newTotalValue.toString(),
        costPerUnit: newCostPerUnit.toString(),
      })
      .where("id", "=", itemValuation.id)
      .returningAll()
      .executeTakeFirst();
  }
}
