import { describe, expect, it } from "vitest";
import {
  applySubstitution,
  checkRecipeSafety,
  computeCoverage,
  deriveRecipeAllergens,
  estimateCost,
  isRecipeSafe,
  scaleIngredients,
  type IngredientSafetyInfo,
} from "../src/index.js";

const INFO = new Map<string, IngredientSafetyInfo>([
  [
    "chicken",
    {
      id: "chicken",
      allergens: [],
      isVegan: false,
      isVegetarian: false,
      containsGluten: false,
      containsLactose: false,
      isPork: false,
      isBeef: false,
      isAlcohol: false,
    },
  ],
  [
    "milk",
    {
      id: "milk",
      allergens: ["milk"],
      isVegan: false,
      isVegetarian: true,
      containsGluten: false,
      containsLactose: true,
      isPork: false,
      isBeef: false,
      isAlcohol: false,
    },
  ],
  [
    "pasta",
    {
      id: "pasta",
      allergens: ["gluten"],
      mayContainAllergens: ["eggs"],
      isVegan: true,
      isVegetarian: true,
      containsGluten: true,
      containsLactose: false,
      isPork: false,
      isBeef: false,
      isAlcohol: false,
    },
  ],
  [
    "bacon",
    {
      id: "bacon",
      allergens: [],
      isVegan: false,
      isVegetarian: false,
      containsGluten: false,
      containsLactose: false,
      isPork: true,
      isBeef: false,
      isAlcohol: false,
    },
  ],
  [
    "tofu",
    {
      id: "tofu",
      allergens: ["soy"],
      isVegan: true,
      isVegetarian: true,
      containsGluten: false,
      containsLactose: false,
      isPork: false,
      isBeef: false,
      isAlcohol: false,
    },
  ],
]);

describe("KRITISKT: deterministisk allergisäkerhet (spec §61.2)", () => {
  it("blockerar recept med användarens allergen", () => {
    const violations = checkRecipeSafety(
      { ingredients: [{ canonicalIngredientId: "milk", optional: false }], spiceLevel: 0 },
      { allergens: ["milk"] },
      INFO,
    );
    expect(isRecipeSafe(violations)).toBe(false);
    expect(violations[0]?.code).toBe("allergen");
    expect(violations[0]?.severity).toBe("blocker");
  });
  it("'kan innehålla spår' ger varning när användaren har allergi", () => {
    const violations = checkRecipeSafety(
      { ingredients: [{ canonicalIngredientId: "pasta", optional: false }], spiceLevel: 0 },
      { allergens: ["eggs"] },
      INFO,
    );
    expect(violations.some((v) => v.code === "allergen_may_contain")).toBe(true);
  });
  it("okänd ingrediens ger varning – aldrig tyst OK (försiktighetsprincipen)", () => {
    const violations = checkRecipeSafety(
      { ingredients: [{ canonicalIngredientId: "mystery", optional: false }], spiceLevel: 0 },
      { allergens: [] },
      INFO,
    );
    expect(violations.some((v) => v.code === "unverified_data")).toBe(true);
  });
  it("valfri ingrediens med allergen → varning, inte blocker", () => {
    const violations = checkRecipeSafety(
      { ingredients: [{ canonicalIngredientId: "milk", optional: true }], spiceLevel: 0 },
      { allergens: ["milk"] },
      INFO,
    );
    expect(isRecipeSafe(violations)).toBe(true);
    expect(violations[0]?.severity).toBe("warning");
  });
  it("vegan blockerar kött och mjölk", () => {
    const violations = checkRecipeSafety(
      {
        ingredients: [
          { canonicalIngredientId: "chicken", optional: false },
          { canonicalIngredientId: "milk", optional: false },
        ],
        spiceLevel: 0,
      },
      { allergens: [], dietPattern: "vegan" },
      INFO,
    );
    expect(violations.filter((v) => v.code === "diet_pattern")).toHaveLength(2);
  });
  it("halal blockerar fläsk", () => {
    const violations = checkRecipeSafety(
      { ingredients: [{ canonicalIngredientId: "bacon", optional: false }], spiceLevel: 0 },
      { allergens: [], religiousRule: "halal" },
      INFO,
    );
    expect(isRecipeSafe(violations)).toBe(false);
  });
  it("härleder receptallergener ur ingredienserna", () => {
    expect(deriveRecipeAllergens(["pasta", "milk", "chicken"], INFO)).toEqual(["gluten", "milk"]);
  });
});

describe("lagermatchning (spec §17)", () => {
  it("beräknar täckning, saknat och utgående", () => {
    const result = computeCoverage(
      [
        {
          canonicalIngredientId: "chicken",
          displayNameSv: "Kyckling",
          quantity: 500,
          unit: "GRAM",
          optional: false,
        },
        {
          canonicalIngredientId: "pasta",
          displayNameSv: "Pasta",
          quantity: 300,
          unit: "GRAM",
          optional: false,
        },
      ],
      [
        {
          id: "1",
          canonicalIngredientId: "chicken",
          quantity: 600,
          unit: "GRAM",
          bestBeforeDate: "2026-08-03",
        },
      ],
      new Map(),
      new Date("2026-08-02T00:00:00Z"), // UTC – testet ska vara sant i alla tidszoner
    );
    expect(result.coverage).toBe(0.5);
    expect(result.missing.map((m) => m.canonicalIngredientId)).toEqual(["pasta"]);
    expect(result.expiringUsed[0]?.canonicalIngredientId).toBe("chicken");
  });
  it("mjölkprincipen: passerat bäst före räknas som tillgängligt, inte som borta", () => {
    // Mjölken som gick ut igår är inte automatiskt dålig – recepten ska
    // fortsätta föreslå den (användaren luktar/smakar) i stället för att
    // tyst behandla varan som obefintlig och driva nyinköp/svinn.
    const result = computeCoverage(
      [
        {
          canonicalIngredientId: "milk",
          displayNameSv: "Mjölk",
          quantity: 500,
          unit: "MILLILITER",
          optional: false,
        },
      ],
      [
        {
          id: "1",
          canonicalIngredientId: "milk",
          quantity: 1000,
          unit: "MILLILITER",
          bestBeforeDate: "2026-08-01", // gick ut igår
        },
      ],
      new Map(),
      new Date("2026-08-02T00:00:00Z"), // UTC – testet ska vara sant i alla tidszoner
    );
    expect(result.coverage).toBe(1);
    expect(result.missing).toHaveLength(0);
    expect(result.expiringUsed[0]?.canonicalIngredientId).toBe("milk"); // prioriteras – "räddar mjölken"
  });
});

describe("skalning", () => {
  it("skalar mängder och avrundar köksvänligt", () => {
    const scaled = scaleIngredients(
      [
        {
          canonicalIngredientId: "chicken",
          displayNameSv: "Kyckling",
          quantity: 500,
          unit: "GRAM",
          optional: false,
        },
        {
          canonicalIngredientId: "salt",
          displayNameSv: "Salt",
          quantity: 1,
          unit: "TEASPOON",
          optional: false,
        },
      ],
      4,
      6,
    );
    expect(scaled[0]?.quantity).toBe(750);
    expect(scaled[1]?.quantity).toBe(1.5);
  });
  it("kastar på ogiltiga portioner", () => {
    expect(() => scaleIngredients([], 0, 4)).toThrow();
  });
});

describe("substitution (spec §20)", () => {
  it("byter ingrediens med mängdfaktor och varnar för fel kontext", () => {
    const result = applySubstitution(
      [
        {
          canonicalIngredientId: "milk",
          displayNameSv: "Mjölk",
          quantity: 200,
          unit: "MILLILITER",
          optional: false,
        },
      ],
      {
        fromIngredientId: "milk",
        toIngredientId: "tofu",
        ratio: 0.8,
        instructionsSv: "Tillsätt sist.",
        notRecommendedFor: ["whipping"],
      },
      "Tofu",
      "whipping",
    );
    expect(result.ingredients[0]?.canonicalIngredientId).toBe("tofu");
    expect(result.ingredients[0]?.quantity).toBe(160);
    expect(result.applied.warningSv).toContain("rekommenderas inte");
  });
  it("kastar när ingrediensen inte finns i receptet", () => {
    expect(() =>
      applySubstitution(
        [],
        { fromIngredientId: "x", toIngredientId: "y", ratio: 1, notRecommendedFor: [] },
        "Y",
      ),
    ).toThrow();
  });
});

describe("kostnadsuppskattning (spec §26)", () => {
  it("beräknar pris per portion med täckningsgrad", () => {
    const result = estimateCost(
      [
        { canonicalIngredientId: "chicken", quantity: 500, unit: "GRAM", optional: false },
        { canonicalIngredientId: "unknown", quantity: 100, unit: "GRAM", optional: false },
      ],
      4,
      // 130 kr/kg = 13000 minor/kg; 500 g -> 6500 minor totalt, 1625 minor/portion.
      new Map([["chicken", { pricePerKgMinor: 13_000, source: "default" as const }]]),
    );
    expect(result.totalMinor).toBe(6500);
    expect(result.perPortionMinor).toBe(1625);
    expect(result.priceCoverage).toBe(0.5);
  });
});
