// @WHO:   vps-deploy/api/kds/src/lib/stock-deduction.ts
// @WHAT:  Hybrid stock deduction logic — Recipe-first, Regression-fallback, Skip on cold start
// @PART:  inventory
// @ENTRY: deductStockForOrder()

import { prisma } from './prisma';

export interface DeductionItem {
  ingredientId: string;
  ingredientName: string;
  quantity: number;
  source: 'RECIPE' | 'REGRESSION' | 'SKIPPED';
  confidence: number;
}

export interface DeductionResult {
  orderId: string;
  deducted: boolean;
  items: DeductionItem[];
  skippedIngredients: string[];
  errors: string[];
}

// @FLOW:     STOCK_DEDUCT_ORDER
// @CALLS:    DB:orders SELECT, DB:order_items SELECT, DB:recipes SELECT,
//            DB:ingredient_usage_estimates SELECT, DB:outlet_ingredients UPDATE,
//            DB:stock_deduction_logs INSERT, DB:stock_logs INSERT, DB:orders UPDATE
// @MUTATES:  DB:outlet_ingredients (UPDATE stock), DB:stock_deduction_logs (INSERT),
//            DB:stock_logs (INSERT), DB:orders (UPDATE stockDeducted=true)
// @BEHAVIOR: Idempotent — if order.stockDeducted is already true, returns immediately.
//            Hybrid deduction: tries Recipe first (confidence=1.0), falls back to Regression estimate
//            (confidence=estimate.confidenceScore), skips if no data (cold start).
//            Never lets stock go below 0. Each deduction is logged with source attribution.
export async function deductStockForOrder(orderId: string): Promise<DeductionResult> {
  const result: DeductionResult = {
    orderId,
    deducted: false,
    items: [],
    skippedIngredients: [],
    errors: [],
  };

  // 1. Load order with items
  const order = await prisma.order.findUnique({
    where: { id: orderId },
    include: {
      items: {
        include: {
          menu: {
            select: { outletId: true },
          },
        },
      },
    },
  });

  if (!order) {
    result.errors.push(`Order ${orderId} not found`);
    return result;
  }

  // 2. Idempotency check
  if (order.stockDeducted) {
    result.deducted = true;
    return result;
  }

  const outletId = order.outletId;

  // Collect all deductions to apply (ingredientId → { totalQty, source, confidence, menuBreakdown })
  const deductions = new Map<string, {
    totalQty: number;
    source: 'RECIPE' | 'REGRESSION';
    confidence: number;
    menuBreakdown: Record<string, number>;
  }>();

  const skippedIngredientsSet = new Set<string>();

  // 3. For each order item, determine deduction per ingredient
  for (const orderItem of order.items) {
    const menuId = orderItem.menuId;
    const orderQty = orderItem.quantity;

    // 3a. Look up Recipe[] for this menuId
    const recipes = await prisma.recipe.findMany({
      where: { menuId },
      include: { ingredient: { select: { id: true, name: true } } },
    });

    if (recipes.length > 0) {
      // 3b. Recipe-based deduction — confidence = 1.0
      for (const recipe of recipes) {
        const deductQty = recipe.quantity * orderQty;
        const ingId = recipe.ingredientId;

        const existing = deductions.get(ingId);
        if (existing) {
          existing.totalQty += deductQty;
          existing.menuBreakdown[menuId] = (existing.menuBreakdown[menuId] || 0) + deductQty;
          // Keep RECIPE source (highest confidence)
        } else {
          deductions.set(ingId, {
            totalQty: deductQty,
            source: 'RECIPE',
            confidence: 1.0,
            menuBreakdown: { [menuId]: deductQty },
          });
        }
      }
    } else {
      // 3c. No recipes — try regression estimates
      const estimates = await prisma.ingredientUsageEstimate.findMany({
        where: {
          outletId,
          menuId,
          confidenceScore: { gt: 0 },
        },
      });

      if (estimates.length > 0) {
        for (const estimate of estimates) {
          const deductQty = estimate.estimatedUsage * orderQty;
          const ingId = estimate.ingredientId;

          const existing = deductions.get(ingId);
          if (existing) {
            existing.totalQty += deductQty;
            existing.menuBreakdown[menuId] = (existing.menuBreakdown[menuId] || 0) + deductQty;
            // If already RECIPE, don't downgrade to REGRESSION
            if (existing.source !== 'RECIPE') {
              existing.confidence = Math.min(existing.confidence, estimate.confidenceScore);
            }
          } else {
            deductions.set(ingId, {
              totalQty: deductQty,
              source: 'REGRESSION',
              confidence: estimate.confidenceScore,
              menuBreakdown: { [menuId]: deductQty },
            });
          }
        }
      }
      // If NO estimates either: cold start — skip deduction for this menu's ingredients
      // (ingredients will be skipped individually below if no deduction was collected)
    }
  }

  // 3d. Apply deductions
  for (const [ingredientId, ded] of deductions) {
    const ingredient = await prisma.ingredient.findUnique({
      where: { id: ingredientId },
      select: { name: true },
    });

    const outletIng = await prisma.outletIngredient.findUnique({
      where: {
        outletId_ingredientId: { outletId, ingredientId },
      },
    });

    // If no OutletIngredient record or autoDeduct is off and source is not RECIPE, skip
    if (!outletIng) {
      skippedIngredientsSet.add(ingredientId);
      result.items.push({
        ingredientId,
        ingredientName: ingredient?.name ?? 'Unknown',
        quantity: 0,
        source: ded.source,
        confidence: ded.confidence,
      });
      continue;
    }

    // Only deduct if autoDeduct is true OR source is RECIPE
    if (!outletIng.autoDeduct && ded.source !== 'RECIPE') {
      skippedIngredientsSet.add(ingredientId);
      result.items.push({
        ingredientId,
        ingredientName: ingredient?.name ?? 'Unknown',
        quantity: 0,
        source: 'SKIPPED',
        confidence: ded.confidence,
      });
      continue;
    }

    // Cap deduction at current stock (never go below 0)
    let actualQty = ded.totalQty;
    let capped = false;
    if (outletIng.stock - actualQty < 0) {
      actualQty = outletIng.stock;
      capped = true;
    }

    if (actualQty <= 0) {
      // No stock to deduct — log but don't error
      result.items.push({
        ingredientId,
        ingredientName: ingredient?.name ?? 'Unknown',
        quantity: 0,
        source: ded.source,
        confidence: ded.confidence,
      });
      if (capped) {
        result.errors.push(
          `${ingredient?.name ?? ingredientId}: stock already at 0, cannot deduct ${ded.totalQty.toFixed(2)}`,
        );
      }
      continue;
    }

    // Update OutletIngredient stock
    await prisma.outletIngredient.update({
      where: {
        outletId_ingredientId: { outletId, ingredientId },
      },
      data: {
        stock: outletIng.stock - actualQty,
      },
    });

    // Create StockLog
    await prisma.stockLog.create({
      data: {
        outletId,
        ingredientId,
        type: 'OUT',
        quantity: -actualQty,
        source: ded.source === 'RECIPE' ? 'RECIPE_DEDUCT' : 'REGRESSION_DEDUCT',
        note: `Order ${order.orderNumber}`,
        costImpact: -(actualQty * outletIng.lastCostPerUnit),
      },
    });

    // Create StockDeductionLog
    await prisma.stockDeductionLog.create({
      data: {
        outletId,
        ingredientId,
        orderId,
        quantity: actualQty,
        source: ded.source,
        confidence: ded.confidence,
        menuBreakdown: ded.menuBreakdown,
      },
    });

    result.items.push({
      ingredientId,
      ingredientName: ingredient?.name ?? 'Unknown',
      quantity: actualQty,
      source: ded.source,
      confidence: ded.confidence,
    });

    if (capped) {
      result.errors.push(
        `${ingredient?.name ?? ingredientId}: stock capped at ${outletIng.stock}, wanted to deduct ${ded.totalQty.toFixed(2)}`,
      );
    }
  }

  // Mark ingredients that appeared in order items but had no deduction as SKIPPED
  for (const orderItem of order.items) {
    const recipes = await prisma.recipe.findMany({ where: { menuId: orderItem.menuId } });
    const estimates = await prisma.ingredientUsageEstimate.findMany({
      where: { outletId, menuId: orderItem.menuId },
    });

    // Collect ingredient IDs from this menu
    const menuIngredientIds = new Set([
      ...recipes.map((r) => r.ingredientId),
      ...estimates.map((e) => e.ingredientId),
    ]);

    for (const ingId of menuIngredientIds) {
      if (!deductions.has(ingId) && !skippedIngredientsSet.has(ingId)) {
        // This ingredient was in estimates but had 0 confidence (filtered out earlier)
        skippedIngredientsSet.add(ingId);
        result.items.push({
          ingredientId: ingId,
          ingredientName: (await prisma.ingredient.findUnique({ where: { id: ingId }, select: { name: true } }))?.name ?? 'Unknown',
          quantity: 0,
          source: 'SKIPPED',
          confidence: 0,
        });
      }
    }
  }

  result.skippedIngredients = [...skippedIngredientsSet];

  // 4. Mark order as stock deducted
  await prisma.order.update({
    where: { id: orderId },
    data: { stockDeducted: true },
  });

  result.deducted = true;
  return result;
}
