import { describe, it, expect } from 'vitest';
import { NutritionCalculatorService } from '../../../src/modules/recipes/nutrition-calculator.service.js';
import { NutritionWarning } from '@meshitrack/shared';

const service = new NutritionCalculatorService();

function makeProduct(
  overrides: Partial<{
    servingSize: number;
    servingUnit: string;
    nutrition: Record<string, number>;
  }> = {},
) {
  return {
    servingSize: overrides.servingSize ?? 100,
    servingUnit: overrides.servingUnit ?? 'g',
    nutrition: {
      calories: 200,
      protein: 20,
      carbs: 10,
      fat: 8,
      ...(overrides.nutrition ?? {}),
    },
  };
}

describe(NutritionCalculatorService.name, () => {
  describe('calculateRecipeNutrition', () => {
    it('calculates total and per-serving nutrition from one ingredient', () => {
      const product = makeProduct(); // 200 kcal per 100g
      const productMap = new Map([['p1', product]]);

      const result = service.calculateRecipeNutrition(
        [{ productId: 'p1', quantity: 200 }], // 200g = 2 servings worth
        productMap,
        2, // 2 servings
      );

      expect(result.totalNutrition.calories).toBe(400);
      expect(result.perServingNutrition.calories).toBe(200);
      expect(result.totalNutrition.protein).toBe(40);
      expect(result.perServingNutrition.protein).toBe(20);
    });

    it('sums contributions from multiple ingredients', () => {
      const p1 = makeProduct({ nutrition: { calories: 100, protein: 10, carbs: 5, fat: 4 } });
      const p2 = makeProduct({ nutrition: { calories: 200, protein: 20, carbs: 10, fat: 8 } });
      const productMap = new Map([
        ['p1', p1],
        ['p2', p2],
      ]);

      const result = service.calculateRecipeNutrition(
        [
          { productId: 'p1', quantity: 100 }, // 1× serving
          { productId: 'p2', quantity: 100 }, // 1× serving
        ],
        productMap,
        1,
      );

      expect(result.totalNutrition.calories).toBe(300);
      expect(result.perServingNutrition.calories).toBe(300);
    });

    it('uses zero nutrition for unknown product', () => {
      const productMap = new Map<string, ReturnType<typeof makeProduct>>();

      const result = service.calculateRecipeNutrition(
        [{ productId: 'missing', quantity: 100 }],
        productMap,
        1,
      );

      expect(result.totalNutrition.calories).toBe(0);
    });

    it('propagates optional nutrients (sodium, fiber, sugar)', () => {
      const product = makeProduct({
        nutrition: {
          calories: 100,
          protein: 5,
          carbs: 10,
          fat: 2,
          sodium: 800,
          fiber: 4,
          sugar: 12,
        },
      });
      const productMap = new Map([['p1', product]]);

      const result = service.calculateRecipeNutrition(
        [{ productId: 'p1', quantity: 100 }],
        productMap,
        1,
      );

      expect(result.totalNutrition.sodium).toBe(800);
      expect(result.totalNutrition.fiber).toBe(4);
      expect(result.totalNutrition.sugar).toBe(12);
    });

    it('propagates saturatedFat and cholesterol across multiple ingredients', () => {
      const p1 = makeProduct({
        nutrition: {
          calories: 100,
          protein: 5,
          carbs: 10,
          fat: 3,
          saturatedFat: 1.5,
          cholesterol: 30,
        },
      });
      const p2 = makeProduct({
        nutrition: {
          calories: 150,
          protein: 8,
          carbs: 12,
          fat: 5,
          saturatedFat: 2.5,
          cholesterol: 50,
        },
      });
      const productMap = new Map([
        ['p1', p1],
        ['p2', p2],
      ]);

      const result = service.calculateRecipeNutrition(
        [
          { productId: 'p1', quantity: 100 },
          { productId: 'p2', quantity: 100 },
        ],
        productMap,
        1,
      );

      expect(result.totalNutrition.saturatedFat).toBe(4);
      expect(result.totalNutrition.cholesterol).toBe(80);
    });

    it('handles product with zero servingSize', () => {
      const product = makeProduct({ servingSize: 0 });
      const productMap = new Map([['p1', product]]);

      const result = service.calculateRecipeNutrition(
        [{ productId: 'p1', quantity: 100 }],
        productMap,
        1,
      );

      // ratio = 0 when servingSize = 0
      expect(result.totalNutrition.calories).toBe(0);
    });

    it('handles zero servings', () => {
      const product = makeProduct();
      const productMap = new Map([['p1', product]]);

      const result = service.calculateRecipeNutrition(
        [{ productId: 'p1', quantity: 100 }],
        productMap,
        0,
      );

      expect(result.perServingNutrition.calories).toBe(0);
    });

    it('multiplies saturatedFat and cholesterol by ratio', () => {
      const product = makeProduct({
        nutrition: {
          calories: 100,
          protein: 5,
          carbs: 10,
          fat: 3,
          saturatedFat: 2,
          cholesterol: 40,
        },
      });
      const productMap = new Map([['p1', product]]);

      const result = service.calculateRecipeNutrition(
        [{ productId: 'p1', quantity: 200 }], // 2x serving
        productMap,
        2,
      );

      // 2x ratio, then divide by 2 servings = same as per serving
      expect(result.totalNutrition.saturatedFat).toBe(4);
      expect(result.totalNutrition.cholesterol).toBe(80);
      expect(result.perServingNutrition.saturatedFat).toBe(2);
      expect(result.perServingNutrition.cholesterol).toBe(40);
    });
  });

  describe('generateWarnings', () => {
    it('flags HIGH_CALORIES when > 800 kcal/serving', () => {
      const warnings = service.generateWarnings({
        calories: 900,
        protein: 20,
        carbs: 50,
        fat: 30,
      });
      expect(warnings).toContain(NutritionWarning.HIGH_CALORIES);
    });

    it('flags HIGH_SODIUM when > 1500 mg/serving', () => {
      const warnings = service.generateWarnings({
        calories: 400,
        protein: 15,
        carbs: 30,
        fat: 10,
        sodium: 1600,
      });
      expect(warnings).toContain(NutritionWarning.HIGH_SODIUM);
    });

    it('flags LOW_PROTEIN when < 10 g/serving', () => {
      const warnings = service.generateWarnings({
        calories: 300,
        protein: 5,
        carbs: 40,
        fat: 10,
      });
      expect(warnings).toContain(NutritionWarning.LOW_PROTEIN);
    });

    it('flags LOW_FIBER when fiber is present and < 3 g/serving', () => {
      const warnings = service.generateWarnings({
        calories: 300,
        protein: 15,
        carbs: 40,
        fat: 10,
        fiber: 1,
      });
      expect(warnings).toContain(NutritionWarning.LOW_FIBER);
    });

    it('does not flag LOW_FIBER when fiber is absent', () => {
      const warnings = service.generateWarnings({
        calories: 300,
        protein: 15,
        carbs: 40,
        fat: 10,
      });
      expect(warnings).not.toContain(NutritionWarning.LOW_FIBER);
    });

    it('returns no warnings for a healthy meal', () => {
      const warnings = service.generateWarnings({
        calories: 450,
        protein: 30,
        carbs: 40,
        fat: 12,
        sodium: 600,
        fiber: 8,
        sugar: 10,
        saturatedFat: 4,
        cholesterol: 80,
      });
      expect(warnings).toHaveLength(0);
    });

    it('can return multiple warnings', () => {
      const warnings = service.generateWarnings({
        calories: 900,
        protein: 5,
        carbs: 80,
        fat: 40,
        sodium: 2000,
        sugar: 30,
        saturatedFat: 20,
        cholesterol: 250,
        fiber: 1,
      });
      expect(warnings.length).toBeGreaterThan(1);
    });
  });
});
