import type { NutritionInfo, RecipeIngredient } from '@meshitrack/shared';
import { NutritionWarning } from '@meshitrack/shared';

export interface ProductLike {
  servingSize: number;
  servingUnit: string;
  nutrition: {
    calories: number;
    protein: number;
    carbs: number;
    fat: number;
    fiber?: number | null;
    sugar?: number | null;
    sodium?: number | null;
    saturatedFat?: number | null;
    cholesterol?: number | null;
  };
}

export interface RecipeNutritionResult {
  totalNutrition: NutritionInfo;
  perServingNutrition: NutritionInfo;
  ingredientContributions: NutritionInfo[];
}

const WARNING_THRESHOLDS: Array<{
  warning: NutritionWarning;
  check: (n: NutritionInfo) => boolean;
}> = [
  { warning: NutritionWarning.HIGH_CALORIES, check: (n) => n.calories > 800 },
  { warning: NutritionWarning.HIGH_SODIUM, check: (n) => (n.sodium ?? 0) > 1500 },
  { warning: NutritionWarning.HIGH_SUGAR, check: (n) => (n.sugar ?? 0) > 25 },
  { warning: NutritionWarning.HIGH_SATURATED_FAT, check: (n) => (n.saturatedFat ?? 0) > 13 },
  { warning: NutritionWarning.LOW_PROTEIN, check: (n) => n.protein < 10 },
  { warning: NutritionWarning.LOW_FIBER, check: (n) => (n.fiber ?? 999) < 3 },
  { warning: NutritionWarning.HIGH_CHOLESTEROL, check: (n) => (n.cholesterol ?? 0) > 200 },
];

/**
 * Calculates nutrition for a recipe from its persisted (metric) ingredients and the
 * corresponding product documents. All ingredient units must already be RecipeUnit
 * (conversion is handled before calling this service).
 */
export class NutritionCalculatorService {
  /**
   * Calculate total and per-serving nutrition for a recipe.
   *
   * For each ingredient:
   *   ratio = ingredient.quantity / product.servingSize
   *   contribution = product.nutrition * ratio
   * totalNutrition = sum of contributions
   * perServingNutrition = totalNutrition / servings
   */
  public calculateRecipeNutrition(
    ingredients: Array<Pick<RecipeIngredient, 'productId' | 'quantity'>>,
    productMap: Map<string, ProductLike>,
    servings: number,
  ): RecipeNutritionResult {
    const ingredientContributions: NutritionInfo[] = [];

    for (const ingredient of ingredients) {
      const product = productMap.get(ingredient.productId);
      if (!product) {
        ingredientContributions.push(zeroNutrition());
        continue;
      }

      const ratio = product.servingSize > 0 ? ingredient.quantity / product.servingSize : 0;
      ingredientContributions.push(multiplyNutrition(product.nutrition, ratio));
    }

    const totalNutrition = sumNutrition(ingredientContributions);
    const perServingNutrition = multiplyNutrition(totalNutrition, servings > 0 ? 1 / servings : 0);

    return { totalNutrition, perServingNutrition, ingredientContributions };
  }

  public generateWarnings(perServingNutrition: NutritionInfo): NutritionWarning[] {
    return WARNING_THRESHOLDS.filter(({ check }) => check(perServingNutrition)).map(
      ({ warning }) => warning,
    );
  }
}

function zeroNutrition(): NutritionInfo {
  return { calories: 0, protein: 0, carbs: 0, fat: 0 };
}

function multiplyNutrition(n: ProductLike['nutrition'], factor: number): NutritionInfo {
  return {
    calories: round(n.calories * factor),
    protein: round(n.protein * factor),
    carbs: round(n.carbs * factor),
    fat: round(n.fat * factor),
    ...(n.fiber != null ? { fiber: round(n.fiber * factor) } : {}),
    ...(n.sugar != null ? { sugar: round(n.sugar * factor) } : {}),
    ...(n.sodium != null ? { sodium: round(n.sodium * factor) } : {}),
    ...(n.saturatedFat != null ? { saturatedFat: round(n.saturatedFat * factor) } : {}),
    ...(n.cholesterol != null ? { cholesterol: round(n.cholesterol * factor) } : {}),
  };
}

function sumNutrition(items: NutritionInfo[]): NutritionInfo {
  const total = zeroNutrition();
  for (const item of items) {
    total.calories += item.calories;
    total.protein += item.protein;
    total.carbs += item.carbs;
    total.fat += item.fat;
    if (item.fiber != null) total.fiber = round((total.fiber ?? 0) + item.fiber);
    if (item.sugar != null) total.sugar = round((total.sugar ?? 0) + item.sugar);
    if (item.sodium != null) total.sodium = round((total.sodium ?? 0) + item.sodium);
    if (item.saturatedFat != null)
      total.saturatedFat = round((total.saturatedFat ?? 0) + item.saturatedFat);
    if (item.cholesterol != null)
      total.cholesterol = round((total.cholesterol ?? 0) + item.cholesterol);
  }
  total.calories = round(total.calories);
  total.protein = round(total.protein);
  total.carbs = round(total.carbs);
  total.fat = round(total.fat);
  return total;
}

function round(value: number): number {
  return Math.round(value * 1000) / 1000;
}
