import { describe, it, expect } from "vitest";
import {
  verifyCandidate,
  type CanonicalIngredientLookup,
  type SimilarityLookup,
} from "../src/verification.js";
import type { RecipeCandidate } from "../src/types.js";

const mockIngredients: CanonicalIngredientLookup = {
  getById(id: string) {
    const db: Record<string, ReturnType<CanonicalIngredientLookup["getById"]>> = {
      kycklingfile: {
        id: "kycklingfile",
        nutritionPer100: {
          basis: "per_100_g",
          values: {
            kcal: 165,
            proteinG: 31,
            carbsG: 0,
            fatG: 3.6,
            saturatedFatG: 1,
            fiberG: 0,
            sugarG: 0,
            saltG: 0.1,
          },
        },
        defaultUnit: "GRAM",
        densityGPerMl: null,
        gramsPerPiece: null,
        allergens: [],
        isVegan: false,
        isVegetarian: false,
        containsGluten: false,
        containsLactose: false,
        isPork: false,
        isBeef: false,
        isAlcohol: false,
      },
      ris: {
        id: "ris",
        nutritionPer100: {
          basis: "per_100_g",
          values: {
            kcal: 130,
            proteinG: 2.7,
            carbsG: 28,
            fatG: 0.3,
            saturatedFatG: 0.1,
            fiberG: 0.4,
            sugarG: 0.1,
            saltG: 0,
          },
        },
        defaultUnit: "GRAM",
        densityGPerMl: null,
        gramsPerPiece: null,
        allergens: [],
        isVegan: true,
        isVegetarian: true,
        containsGluten: false,
        containsLactose: false,
        isPork: false,
        isBeef: false,
        isAlcohol: false,
      },
      krossade_tomater: {
        id: "krossade_tomater",
        nutritionPer100: {
          basis: "per_100_g",
          values: {
            kcal: 32,
            proteinG: 1.6,
            carbsG: 5.8,
            fatG: 0.3,
            saturatedFatG: 0,
            fiberG: 1.2,
            sugarG: 4,
            saltG: 0.1,
          },
        },
        defaultUnit: "GRAM",
        densityGPerMl: null,
        gramsPerPiece: null,
        allergens: [],
        isVegan: true,
        isVegetarian: true,
        containsGluten: false,
        containsLactose: false,
        isPork: false,
        isBeef: false,
        isAlcohol: false,
      },
      chicken_breast: {
        id: "chicken_breast",
        nutritionPer100: {
          basis: "per_100_g",
          values: {
            kcal: 165,
            proteinG: 31,
            carbsG: 0,
            fatG: 3.6,
            saturatedFatG: 1,
            fiberG: 0,
            sugarG: 0,
            saltG: 0.1,
          },
        },
        defaultUnit: "GRAM",
        densityGPerMl: null,
        gramsPerPiece: null,
        allergens: [],
        isVegan: false,
        isVegetarian: false,
        containsGluten: false,
        containsLactose: false,
        isPork: false,
        isBeef: false,
        isAlcohol: false,
      },
      sesame_seeds: {
        id: "sesame_seeds",
        nutritionPer100: {
          basis: "per_100_g",
          values: {
            kcal: 600,
            proteinG: 18,
            carbsG: 12,
            fatG: 50,
            saturatedFatG: 7,
            fiberG: 12,
            sugarG: 0,
            saltG: 0,
          },
        },
        defaultUnit: "GRAM",
        densityGPerMl: 0.6,
        gramsPerPiece: null,
        allergens: ["sesame"],
        isVegan: true,
        isVegetarian: true,
        containsGluten: false,
        containsLactose: false,
        isPork: false,
        isBeef: false,
        isAlcohol: false,
      },
      milk_3: {
        id: "milk_3",
        nutritionPer100: {
          basis: "per_100_g",
          values: {
            kcal: 60,
            proteinG: 3.4,
            carbsG: 4.7,
            fatG: 3,
            saturatedFatG: 1.9,
            fiberG: 0,
            sugarG: 4.7,
            saltG: 0.1,
          },
        },
        defaultUnit: "DECILITER",
        densityGPerMl: 1.03,
        gramsPerPiece: null,
        allergens: ["milk"],
        isVegan: false,
        isVegetarian: true,
        containsGluten: false,
        containsLactose: true,
        isPork: false,
        isBeef: false,
        isAlcohol: false,
      },
    };
    return db[id] ?? undefined;
  },
};

const noSimilarity: SimilarityLookup = {
  async hasSimilarity() {
    return false;
  },
};

function makeCandidate(overrides?: Partial<RecipeCandidate>): RecipeCandidate {
  return {
    titleSv: "Testrecept",
    descriptionSv: "Ett enkelt testrecept.",
    cuisine: "swedish",
    mealTypes: ["dinner"],
    prepTimeMinutes: 10,
    cookTimeMinutes: 20,
    totalTimeMinutes: 30,
    portions: 4,
    spiceLevel: 1,
    ingredients: [
      {
        canonicalIngredientId: "kycklingfile",
        displayNameSv: "kycklingfilé",
        quantity: 500,
        unit: "GRAM",
        optional: false,
        note: null,
      },
      {
        canonicalIngredientId: "ris",
        displayNameSv: "ris",
        quantity: 300,
        unit: "GRAM",
        optional: false,
        note: null,
      },
      {
        canonicalIngredientId: "krossade_tomater",
        displayNameSv: "krossade tomater",
        quantity: 400,
        unit: "GRAM",
        optional: false,
        note: null,
      },
    ],
    steps: [
      {
        stepNumber: 1,
        instructionSv: "Stek kycklingen i en panna.",
        timerSeconds: null,
        temperatureC: null,
        tip: null,
      },
      {
        stepNumber: 2,
        instructionSv: "Tillsätt tomater och ris, låt koka.",
        timerSeconds: 900,
        temperatureC: null,
        tip: null,
      },
    ],
    storageGuidanceSv: "Förvara i kylskåp upp till 3 dagar.",
    mealPrepFriendly: false,
    freezerFriendly: false,
    sourceType: "ai_generated",
    confidence: 0.9,
    ...overrides,
  };
}

describe("verifyCandidate", () => {
  it("passerar en giltig kandidat som verified", async () => {
    const result = await verifyCandidate(makeCandidate(), mockIngredients, noSimilarity);
    expect(result.status).toBe("verified");
    expect(result.reasons).toHaveLength(0);
    expect(result.allergens).toHaveLength(0);
    expect(result.nutritionPerPortion).not.toBeNull();
    expect(result.nutritionPerPortion!.kcal).toBeGreaterThan(0);
  });

  it("avvisar kandidat med okänd ingrediens", async () => {
    const c = makeCandidate({
      ingredients: [
        {
          canonicalIngredientId: "fantasi_gronsak",
          displayNameSv: "fantasigrönsak",
          quantity: 100,
          unit: "GRAM",
          optional: false,
          note: null,
        },
      ],
    });
    const result = await verifyCandidate(c, mockIngredients, noSimilarity);
    expect(result.status).toBe("rejected");
    expect(result.reasons.some((r) => r.includes("fantasi_gronsak"))).toBe(true);
  });

  it("markerar unverified vid orimlig tid", async () => {
    const c = makeCandidate({ prepTimeMinutes: 200, cookTimeMinutes: 10, totalTimeMinutes: 210 });
    const result = await verifyCandidate(c, mockIngredients, noSimilarity, {
      maxPrepTimeMinutes: 60,
    });
    expect(result.status).toBe("unverified");
    expect(result.reasons.some((r) => r.includes("Förberedelsetid"))).toBe(true);
  });

  it("markerar unverified vid för få steg", async () => {
    const c = makeCandidate({
      steps: [
        {
          stepNumber: 1,
          instructionSv: "Gör allt.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
      ],
    });
    const result = await verifyCandidate(c, mockIngredients, noSimilarity, { minSteps: 2 });
    expect(result.status).toBe("unverified");
    expect(result.reasons.some((r) => r.includes("steg"))).toBe(true);
  });

  it("härleder allergener korrekt", async () => {
    const c = makeCandidate({
      ingredients: [
        {
          canonicalIngredientId: "kycklingfile",
          displayNameSv: "kycklingfilé",
          quantity: 500,
          unit: "GRAM",
          optional: false,
          note: null,
        },
      ],
    });
    const result = await verifyCandidate(c, mockIngredients, noSimilarity);
    expect(result.allergens).toEqual([]);
  });

  it("överkör AI:n när AI påstår inga allergener men ingrediens har mjölk", async () => {
    const milkIngredient: ReturnType<CanonicalIngredientLookup["getById"]> = {
      id: "milk_3",
      nutritionPer100: {
        basis: "per_100_g",
        values: {
          kcal: 60,
          proteinG: 3.4,
          carbsG: 4.7,
          fatG: 3,
          saturatedFatG: 1.9,
          fiberG: 0,
          sugarG: 4.7,
          saltG: 0.1,
        },
      },
      defaultUnit: "DECILITER",
      densityGPerMl: 1.03,
      gramsPerPiece: null,
      allergens: ["milk"],
      isVegan: false,
      isVegetarian: true,
      containsGluten: false,
      containsLactose: true,
      isPork: false,
      isBeef: false,
      isAlcohol: false,
    };
    const lookup: CanonicalIngredientLookup = {
      getById(id: string) {
        return id === "milk_3" ? milkIngredient : undefined;
      },
    };
    const c = makeCandidate({
      aiClaimedAllergens: [],
      ingredients: [
        {
          canonicalIngredientId: "milk_3",
          displayNameSv: "mjölk",
          quantity: 5,
          unit: "DECILITER",
          optional: false,
          note: null,
        },
      ],
    });
    const result = await verifyCandidate(c, lookup, noSimilarity);
    expect(result.allergens).toContain("milk");
  });

  it("slänger AI:n:s falska gluten-allergen när ingen ingrediens innehåller gluten", async () => {
    const c = makeCandidate({
      aiClaimedAllergens: ["gluten"],
      ingredients: [
        {
          canonicalIngredientId: "kycklingfile",
          displayNameSv: "kycklingfilé",
          quantity: 500,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "ris",
          displayNameSv: "ris",
          quantity: 300,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "krossade_tomater",
          displayNameSv: "krossade tomater",
          quantity: 400,
          unit: "GRAM",
          optional: false,
          note: null,
        },
      ],
    });
    const result = await verifyCandidate(c, mockIngredients, noSimilarity);
    expect(result.allergens).not.toContain("gluten");
    expect(result.status).toBe("verified");
  });

  it("detekterar dubblett", async () => {
    const dupSimilarity: SimilarityLookup = {
      async hasSimilarity() {
        return true;
      },
    };
    const result = await verifyCandidate(makeCandidate(), mockIngredients, dupSimilarity);
    expect(result.status).toBe("unverified");
    expect(result.reasons.some((r) => r.includes("dubblett"))).toBe(true);
  });

  it("kräver genomstekningssteg för rå fågel (positivt)", async () => {
    const c = makeCandidate({
      ingredients: [
        {
          canonicalIngredientId: "chicken_breast",
          displayNameSv: "kycklingfilé",
          quantity: 500,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "ris",
          displayNameSv: "ris",
          quantity: 300,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "krossade_tomater",
          displayNameSv: "krossade tomater",
          quantity: 400,
          unit: "GRAM",
          optional: false,
          note: null,
        },
      ],
      steps: [
        {
          stepNumber: 1,
          instructionSv: "Stek kycklingen tills den är genomstekt och innertemperaturen är 72°C.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
        {
          stepNumber: 2,
          instructionSv: "Tillsätt ris och tomater, koka klart.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
      ],
    });
    const result = await verifyCandidate(c, mockIngredients, noSimilarity);
    expect(result.status).toBe("verified");
    expect(result.reasons).not.toContain(
      "Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.",
    );
  });

  it("markerar unverified när rå fågel bara beskrivs som gyllenbrun (utseende räcker inte)", async () => {
    const c = makeCandidate({
      ingredients: [
        {
          canonicalIngredientId: "chicken_breast",
          displayNameSv: "kycklingfilé",
          quantity: 500,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "ris",
          displayNameSv: "ris",
          quantity: 300,
          unit: "GRAM",
          optional: false,
          note: null,
        },
      ],
      steps: [
        {
          stepNumber: 1,
          instructionSv: "Stek kycklingen tills den är gyllenbrun.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
        {
          stepNumber: 2,
          instructionSv: "Tillsätt ris och koka klart.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
      ],
    });
    const result = await verifyCandidate(c, mockIngredients, noSimilarity);
    expect(result.status).toBe("unverified");
    expect(result.reasons).toContain(
      "Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.",
    );
  });

  it("markerar unverified när rå fågel saknar genomstekningssteg (negativt)", async () => {
    const c = makeCandidate({
      ingredients: [
        {
          canonicalIngredientId: "chicken_breast",
          displayNameSv: "kycklingfilé",
          quantity: 500,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "ris",
          displayNameSv: "ris",
          quantity: 300,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "krossade_tomater",
          displayNameSv: "krossade tomater",
          quantity: 400,
          unit: "GRAM",
          optional: false,
          note: null,
        },
      ],
      steps: [
        {
          stepNumber: 1,
          instructionSv: "Stek kycklingen i pannan.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
        {
          stepNumber: 2,
          instructionSv: "Tillsätt ris och tomater, koka klart.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
      ],
    });
    const result = await verifyCandidate(c, mockIngredients, noSimilarity);
    expect(result.status).toBe("unverified");
    expect(result.reasons).toContain(
      "Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.",
    );
  });

  it("härleder allergen även från valfri ingrediens (optional sesame)", async () => {
    const c = makeCandidate({
      ingredients: [
        {
          canonicalIngredientId: "kycklingfile",
          displayNameSv: "kycklingfilé",
          quantity: 300,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "ris",
          displayNameSv: "ris",
          quantity: 300,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "sesame_seeds",
          displayNameSv: "sesamfrön till garnering",
          quantity: 10,
          unit: "GRAM",
          optional: true,
          note: null,
        },
      ],
      steps: [
        {
          stepNumber: 1,
          instructionSv: "Stek kycklingen genomstekt.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
        {
          stepNumber: 2,
          instructionSv: "Tillsätt ris och sesamefrön.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
      ],
    });
    const result = await verifyCandidate(c, mockIngredients, noSimilarity);
    expect(result.allergens).toContain("sesame");
  });

  it("kräver genomstekningssteg även för optional rå fågel", async () => {
    const c = makeCandidate({
      ingredients: [
        {
          canonicalIngredientId: "ris",
          displayNameSv: "ris",
          quantity: 300,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "krossade_tomater",
          displayNameSv: "krossade tomater",
          quantity: 400,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "chicken_breast",
          displayNameSv: "kycklingfilé",
          quantity: 300,
          unit: "GRAM",
          optional: true,
          note: null,
        },
      ],
      steps: [
        {
          stepNumber: 1,
          instructionSv: "Koka riset enligt anvisningen.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
        {
          stepNumber: 2,
          instructionSv: "Tillsätt tomater och kyckling.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
      ],
    });
    const result = await verifyCandidate(c, mockIngredients, noSimilarity);
    expect(result.status).toBe("unverified");
    expect(result.reasons).toContain(
      "Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.",
    );
  });

  it("härleder mjölk-allergen från optional mjölk", async () => {
    const c = makeCandidate({
      ingredients: [
        {
          canonicalIngredientId: "kycklingfile",
          displayNameSv: "kycklingfilé",
          quantity: 300,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "ris",
          displayNameSv: "ris",
          quantity: 300,
          unit: "GRAM",
          optional: false,
          note: null,
        },
        {
          canonicalIngredientId: "milk_3",
          displayNameSv: "mjölk till servering",
          quantity: 2,
          unit: "DECILITER",
          optional: true,
          note: null,
        },
      ],
      steps: [
        {
          stepNumber: 1,
          instructionSv: "Stek kycklingen genomstekt.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
        {
          stepNumber: 2,
          instructionSv: "Tillsätt ris och mjölk.",
          timerSeconds: null,
          temperatureC: null,
          tip: null,
        },
      ],
    });
    const result = await verifyCandidate(c, mockIngredients, noSimilarity);
    expect(result.allergens).toContain("milk");
  });
});
