// @WHO:   vps-deploy/api/kds/src/lib/anomaly.ts
// @WHAT:  Anomaly detection for stock depletion — compares actual vs predicted baselines per dayOfWeek
// @PART:  inventory
// @ENTRY: checkAnomalies(), detectAnomalyAfterDeduction()

import { prisma } from './prisma';

export interface AnomalyAlert {
  ingredientId: string;
  ingredientName: string;
  dayOfWeek: number;
  actualDepletion: number;
  meanDepletion: number;
  predictedMax: number;
  criticalMax: number;
  severity: 'WARNING' | 'CRITICAL';
  zScore: number;
}

// @FLOW:     ANOMALY_CHECK
// @CALLS:    DB:outlet_ingredients SELECT, DB:depletion_baselines SELECT,
//            DB:stock_logs SELECT (today's OUT/WASTE)
// @MUTATES:  none
// @BEHAVIOR: Compares today's actual depletion against DepletionBaseline for each ingredient.
//            WARNING if actual > predictedMax, CRITICAL if actual > criticalMax.
//            Returns empty array if no baselines exist (cold start — no false positives).
export async function checkAnomalies(outletId: string): Promise<AnomalyAlert[]> {
  const now = new Date();
  const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  const alerts: AnomalyAlert[] = [];

  // Convert JS day to schema day: 0=Monday, 6=Sunday
  const jsDay = now.getDay();
  const dayOfWeek = (jsDay + 6) % 7;

  // Get all outlet ingredients
  const outletIngs = await prisma.outletIngredient.findMany({
    where: { outletId },
    include: { ingredient: { select: { id: true, name: true } } },
  });

  for (const oi of outletIngs) {
    const ingredientId = oi.ingredientId;

    // Look up baseline for this ingredient + dayOfWeek
    const baseline = await prisma.depletionBaseline.findUnique({
      where: {
        outletId_ingredientId_dayOfWeek: {
          outletId,
          ingredientId,
          dayOfWeek,
        },
      },
    });

    // No baseline → can't detect anomaly (cold start)
    if (!baseline) continue;

    // Skip if stdDev is zero and mean is zero — no historical pattern
    if (baseline.meanDepletion === 0 && baseline.stdDeviation === 0) continue;

    // Calculate today's actual depletion
    const todayLogs = await prisma.stockLog.findMany({
      where: {
        outletId,
        ingredientId,
        type: { in: ['OUT', 'WASTE'] },
        createdAt: { gte: startOfDay },
      },
      select: { quantity: true },
    });

    const actualDepletion = todayLogs.reduce((sum, log) => sum + Math.abs(log.quantity), 0);

    // Check thresholds
    if (actualDepletion > baseline.criticalMax) {
      const zScore = baseline.stdDeviation > 0
        ? (actualDepletion - baseline.meanDepletion) / baseline.stdDeviation
        : actualDepletion > 0 ? Infinity : 0;

      alerts.push({
        ingredientId,
        ingredientName: oi.ingredient.name,
        dayOfWeek,
        actualDepletion,
        meanDepletion: baseline.meanDepletion,
        predictedMax: baseline.predictedMax,
        criticalMax: baseline.criticalMax,
        severity: 'CRITICAL',
        zScore,
      });
    } else if (actualDepletion > baseline.predictedMax) {
      const zScore = baseline.stdDeviation > 0
        ? (actualDepletion - baseline.meanDepletion) / baseline.stdDeviation
        : actualDepletion > 0 ? Infinity : 0;

      alerts.push({
        ingredientId,
        ingredientName: oi.ingredient.name,
        dayOfWeek,
        actualDepletion,
        meanDepletion: baseline.meanDepletion,
        predictedMax: baseline.predictedMax,
        criticalMax: baseline.criticalMax,
        severity: 'WARNING',
        zScore,
      });
    }
  }

  return alerts;
}

// @FLOW:     ANOMALY_CHECK_SINGLE
// @CALLS:    DB:depletion_baselines SELECT, DB:stock_logs SELECT (today's OUT/WASTE)
// @MUTATES:  none
// @BEHAVIOR: Quick check for a single ingredient after a deduction. Adds deductedQuantity to today's
//            existing depletion, then compares against baseline. Returns null if no baseline or within range.
export async function detectAnomalyAfterDeduction(
  outletId: string,
  ingredientId: string,
  deductedQuantity: number,
): Promise<AnomalyAlert | null> {
  const now = new Date();
  const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate());

  // Convert JS day to schema day
  const jsDay = now.getDay();
  const dayOfWeek = (jsDay + 6) % 7;

  const baseline = await prisma.depletionBaseline.findUnique({
    where: {
      outletId_ingredientId_dayOfWeek: {
        outletId,
        ingredientId,
        dayOfWeek,
      },
    },
  });

  // No baseline → can't detect anomaly
  if (!baseline) return null;
  if (baseline.meanDepletion === 0 && baseline.stdDeviation === 0) return null;

  // Get today's existing depletion
  const todayLogs = await prisma.stockLog.findMany({
    where: {
      outletId,
      ingredientId,
      type: { in: ['OUT', 'WASTE'] },
      createdAt: { gte: startOfDay },
    },
    select: { quantity: true },
  });

  const existingDepletion = todayLogs.reduce((sum, log) => sum + Math.abs(log.quantity), 0);
  const actualDepletion = existingDepletion + Math.abs(deductedQuantity);

  // Check thresholds — CRITICAL takes priority
  if (actualDepletion > baseline.criticalMax) {
    const zScore = baseline.stdDeviation > 0
      ? (actualDepletion - baseline.meanDepletion) / baseline.stdDeviation
      : actualDepletion > 0 ? Infinity : 0;

    const ingredient = await prisma.ingredient.findUnique({
      where: { id: ingredientId },
      select: { name: true },
    });

    return {
      ingredientId,
      ingredientName: ingredient?.name ?? 'Unknown',
      dayOfWeek,
      actualDepletion,
      meanDepletion: baseline.meanDepletion,
      predictedMax: baseline.predictedMax,
      criticalMax: baseline.criticalMax,
      severity: 'CRITICAL',
      zScore,
    };
  }

  if (actualDepletion > baseline.predictedMax) {
    const zScore = baseline.stdDeviation > 0
      ? (actualDepletion - baseline.meanDepletion) / baseline.stdDeviation
      : actualDepletion > 0 ? Infinity : 0;

    const ingredient = await prisma.ingredient.findUnique({
      where: { id: ingredientId },
      select: { name: true },
    });

    return {
      ingredientId,
      ingredientName: ingredient?.name ?? 'Unknown',
      dayOfWeek,
      actualDepletion,
      meanDepletion: baseline.meanDepletion,
      predictedMax: baseline.predictedMax,
      criticalMax: baseline.criticalMax,
      severity: 'WARNING',
      zScore,
    };
  }

  return null;
}
