import { describe, expect, it } from "vitest";
import type { NutritionDeclaration } from "@app/shared-types";
import {
  bmrMifflinStJeor,
  computeDailyTargets,
  computeItemNutrition,
  computeRecipeNutrition,
  convert,
  scaleNutrition,
  summarizeDay,
  sumNutrition,
  toGrams,
} from "../src/index.js";

const chicken: NutritionDeclaration = {
  basis: "per_100_g",
  values: {
    kcal: 106,
    proteinG: 22,
    carbsG: 0,
    fatG: 2,
    saturatedFatG: 0.6,
    fiberG: 0,
    sugarG: 0,
    saltG: 0.2,
  },
};
const milk: NutritionDeclaration = {
  basis: "per_100_ml",
  values: {
    kcal: 60,
    proteinG: 3.4,
    carbsG: 4.7,
    fatG: 3,
    saturatedFatG: 1.9,
    fiberG: 0,
    sugarG: 4.7,
    saltG: 0.1,
  },
};

describe("enhetskonvertering", () => {
  it("konverterar massa", () => {
    expect(toGrams(1, "KILOGRAM")).toBe(1000);
    expect(toGrams(250, "GRAM")).toBe(250);
  });
  it("konverterar volym via densitet", () => {
    expect(toGrams(1, "LITER", { densityGPerMl: 1.03 })).toBeCloseTo(1030);
    expect(toGrams(1, "DECILITER", { densityGPerMl: 1 })).toBe(100);
    expect(toGrams(1, "TABLESPOON", { densityGPerMl: 0.92 })).toBeCloseTo(13.8);
  });
  it("konverterar styck via styckvikt", () => {
    expect(toGrams(2, "COUNT", { gramsPerPiece: 58 })).toBe(116);
  });
  it("vägrar gissa: null utan densitet/styckvikt (spec §61.4)", () => {
    expect(toGrams(1, "DECILITER")).toBeNull();
    expect(toGrams(1, "COUNT")).toBeNull();
    expect(convert(1, "DECILITER", "GRAM")).toBeNull();
  });
});

describe("näringsberäkning", () => {
  it("räknar per 100 g", () => {
    const result = computeItemNutrition(300, "GRAM", chicken);
    expect(result?.kcal).toBeCloseTo(318);
    expect(result?.proteinG).toBeCloseTo(66);
  });
  it("räknar per 100 ml med volymenhet", () => {
    const result = computeItemNutrition(2, "DECILITER", milk, { densityGPerMl: 1.03 });
    expect(result?.kcal).toBeCloseTo(120);
  });
  it("returnerar null när data saknas – aldrig påhitt", () => {
    expect(computeItemNutrition(1, "COUNT", chicken)).toBeNull();
  });
  it("summerar och skalar", () => {
    const a = computeItemNutrition(100, "GRAM", chicken)!;
    const total = sumNutrition([a, a]);
    expect(total.kcal).toBeCloseTo(212);
    expect(scaleNutrition(total, 0.5).kcal).toBeCloseTo(106);
  });
});

describe("receptnäring", () => {
  it("beräknar per portion och redovisar oberäkneliga", () => {
    const sources = new Map([
      ["chicken", { nutritionPer100: chicken }],
      ["milk", { nutritionPer100: milk, densityGPerMl: 1.03 }],
    ]);
    const result = computeRecipeNutrition(
      [
        { canonicalIngredientId: "chicken", quantity: 400, unit: "GRAM" },
        { canonicalIngredientId: "milk", quantity: 2, unit: "DECILITER" },
        { canonicalIngredientId: "unknown", quantity: 1, unit: "COUNT" },
      ],
      4,
      sources,
    );
    expect(result.uncomputableIngredientIds).toEqual(["unknown"]);
    expect(result.perPortion.kcal).toBeGreaterThan(100);
    expect(result.perPortion.kcal * 4).toBeCloseTo(result.total.kcal, 0);
  });
  it("exkluderar valfria ingredienser", () => {
    const sources = new Map([["chicken", { nutritionPer100: chicken }]]);
    const withOptional = computeRecipeNutrition(
      [
        { canonicalIngredientId: "chicken", quantity: 400, unit: "GRAM" },
        { canonicalIngredientId: "chicken", quantity: 400, unit: "GRAM", optional: true },
      ],
      4,
      sources,
    );
    expect(withOptional.total.kcal).toBeCloseTo(424);
  });
});

describe("energi & dagsmål", () => {
  it("Mifflin–St Jeor referensvärde (man 80 kg, 180 cm, 30 år)", () => {
    const bmr = bmrMifflinStJeor({ sex: "male", weightKg: 80, heightCm: 180, age: 30 });
    expect(bmr).toBeCloseTo(10 * 80 + 6.25 * 180 - 5 * 30 + 5); // 1780
  });
  it("viktnedgång ger underskott men aldrig under golvet", () => {
    const normal = computeDailyTargets({
      sex: "female",
      age: 35,
      heightCm: 165,
      weightKg: 60,
      activityLevel: "moderate",
      primaryGoal: "lose_weight",
    });
    expect(normal.targets.kcal).toBeLessThan(normal.basis.tdeeKcal);
    const extreme = computeDailyTargets({
      sex: "female",
      age: 80,
      heightCm: 145,
      weightKg: 40,
      activityLevel: "sedentary",
      primaryGoal: "lose_weight",
    });
    expect(extreme.targets.kcal).toBeGreaterThanOrEqual(1200);
  });
  it("proteinmål skalar med kroppsvikt och mål", () => {
    const base = computeDailyTargets({
      sex: "male",
      age: 30,
      heightCm: 180,
      weightKg: 80,
      activityLevel: "moderate",
    });
    const muscle = computeDailyTargets({
      sex: "male",
      age: 30,
      heightCm: 180,
      weightKg: 80,
      activityLevel: "moderate",
      primaryGoal: "build_muscle",
    });
    expect(muscle.targets.proteinG).toBeGreaterThan(base.targets.proteinG);
    expect(muscle.targets.proteinG).toBe(Math.round(80 * 1.8));
  });
});

describe("dagsöversikt", () => {
  it("summerar måltider och flaggar salt", () => {
    const meal = {
      kcal: 700,
      proteinG: 40,
      carbsG: 60,
      fatG: 25,
      saturatedFatG: 8,
      fiberG: 6,
      sugarG: 5,
      saltG: 4,
    };
    const summary = summarizeDay([meal, meal], {
      kcal: 2000,
      proteinG: 100,
      carbsG: 220,
      fatG: 65,
      fiberG: 25,
      saltMaxG: 6,
    });
    expect(summary.consumed.kcal).toBe(1400);
    expect(summary.remaining.kcal).toBe(600);
    expect(summary.saltWarning).toBe(true);
    expect(summary.progress.proteinG).toBeCloseTo(0.8);
  });
});

describe("i18n-enheter (i18n-spec §9–§11)", () => {
  it("amerikanska volymer konverterar enligt dokumenterad standard", () => {
    expect(toGrams(1, "CUP_US", { densityGPerMl: 1 })).toBeCloseTo(236.59);
    expect(toGrams(2, "FLUID_OUNCE_US", { densityGPerMl: 1 })).toBeCloseTo(59.14);
    expect(toGrams(1, "TEASPOON", { densityGPerMl: 1 })).toBe(5);
    expect(toGrams(1, "TABLESPOON", { densityGPerMl: 1 })).toBe(15);
    expect(toGrams(2, "PINCH", { densityGPerMl: 1 })).toBe(1);
  });
  it("amerikanska massor är massa – ingen densitet behövs", () => {
    expect(toGrams(1, "OUNCE")).toBeCloseTo(28.35);
    expect(toGrams(1, "POUND")).toBeCloseTo(453.59);
    expect(convert(1, "POUND", "OUNCE")).toBeCloseTo(16, 1);
  });
  it("DECILITER och cup↔dl-konvertering utan förlust", () => {
    expect(convert(1, "CUP_US", "DECILITER")).toBeCloseTo(2.3659);
    expect(convert(2.3659, "DECILITER", "CUP_US")).toBeCloseTo(1, 4);
  });
  it("volym↔massa vägrar fortfarande utan ingrediensdensitet (i18n-spec §10)", () => {
    expect(toGrams(1, "CUP_US")).toBeNull();
    expect(convert(100, "GRAM", "CUP_US")).toBeNull();
  });
  it("count-enheter (CLOVE, CAN …) kräver styckvikt", () => {
    expect(toGrams(2, "CLOVE", { gramsPerPiece: 5 })).toBe(10);
    expect(toGrams(1, "CAN")).toBeNull();
  });
});
