/**
 * Verifieringspipeline för AI-genererade receptkandidater (docs/32 §3).
 *
 * Obligatorisk grind — inget blir verified utan att passera alla steg:
 * 1. Ingrediensmappning mot canonical_ingredients (hård spärr).
 * 2. Allergener HÄRLEDDA ur ingredienserna (aldrig AI-påstådda).
 * 3. Näring BERÄKNAD via nutrition-engine (aldrig AI-siffra).
 * 4. Språk-/kvalitetskontroll (rimliga steg/tider).
 * 5. Dedup mot recipe_similarities.
 *
 * Först då verificationStatus=verified; annars unverified & osynlig.
 */

import {
  computeRecipeNutrition,
  type RecipeIngredientForCalc,
  type IngredientNutritionSource,
} from "@app/nutrition-engine";
import {
  deriveRecipeAllergens,
  type IngredientSafetyInfo,
} from "@app/recipe-engine";
import type { NutritionValues, Allergen } from "@app/shared-types";
import type { RecipeCandidate, VerificationResult } from "./types.js";

export interface CanonicalIngredientLookup {
  getById(id: string):
    | {
        id: string;
        nutritionPer100: import("@app/shared-types").NutritionDeclaration;
        defaultUnit: import("@app/shared-types").Unit;
        densityGPerMl?: number | null;
        gramsPerPiece?: number | null;
        allergens: Allergen[];
        isVegan: boolean;
        isVegetarian: boolean;
        containsGluten: boolean;
        containsLactose: boolean;
        isPork: boolean;
        isBeef: boolean;
        isAlcohol: boolean;
      }
    | undefined;
}

export interface SimilarityLookup {
  hasSimilarity(recipeTitle: string): Promise<boolean>;
}

export interface VerificationOptions {
  /** Max tillåten förberedelsetid (minuter). */
  maxPrepTimeMinutes?: number;
  /** Max tillåten koktid (minuter). */
  maxCookTimeMinutes?: number;
  /** Max total tid (minuter). */
  maxTotalTimeMinutes?: number;
  /** Minsta antal steg. */
  minSteps?: number;
  /** Minsta antal ingredienser. */
  minIngredients?: number;
  /** Om true, krävs att receptet har minst en icke-valfri protein-källa. */
  requireProtein?: boolean;
}

const DEFAULT_OPTIONS: Required<VerificationOptions> = {
  maxPrepTimeMinutes: 120,
  maxCookTimeMinutes: 180,
  maxTotalTimeMinutes: 240,
  minSteps: 2,
  minIngredients: 3,
  requireProtein: false,
};

/** Ingredienser som kräver ett säkerhetssteg med genomstekning/temperatur (spec §61.3). */
const RAW_PROTEIN_REQUIRING_SAFE_COOKING = new Set([
  // Fågel
  "chicken_breast",
  "chicken_thigh",
  // Fläsk/nöt/köttfärs
  "pork_loin",
  "minced_beef",
  "minced_mixed",
  "meatball_pork_beef",
  "falukorv",
  // Ägg
  "egg",
  // Fisk/skaldjur
  "cod",
  "salmon",
  "shrimp",
  "anchovy_swedish",
  "pickled_herring",
]);

const SAFE_COOKING_KEYWORDS_SV = [
  /\bgenomstekt\b/i,
  /\bgenomkokt\b/i,
  /\bgenomgrillad\b/i,
  /\bgenomv\w+\b/i,
  /\binte längre rosa\b/i,
  /\binte rosa\b/i,
  /\bflagnar\b/i,
  /\bgenomkokt\b/i,
  /\bkärntemperatur\b/i,
  /\binnertemperatur\b/i,
  /\btemperatur\b/i,
  /\b°\s*c\b/i,
  /\bgrader\b/i,
  /\btill(?:s)? den är klar\b/i,
  /\btill(?:s)? köttet släpper vätskan\b/i,
  /\b72\s*c?\b/i,
  /\b74\s*c?\b/i,
  /\b75\s*c?\b/i,
  /\b63\s*c?\b/i,
  /\b65\s*c?\b/i,
  /\b70\s*c?\b/i,
];

/** Fraser som INTE räcker som säkerhetsbevis – de beskriver bara utseende/tid. */
const UNSAFE_APPEARANCE_ONLY_SV = [
  /\bgyllenbrun\b/i,
  /\bgyllene\b/i,
  /\bkrispig\b/i,
  /\bkrispiga\b/i,
  /\bfint färg\b/i,
  /\bfint färgade\b/i,
  /\bfärgad\b/i,
  /\bfräsch\b/i,
  /\bfräscha\b/i,
];

function requiresSafeCookingStep(ingredientIds: string[]): boolean {
  return ingredientIds.some((id) => RAW_PROTEIN_REQUIRING_SAFE_COOKING.has(id));
}

function hasSafeCookingStep(steps: RecipeCandidate["steps"]): boolean {
  return steps.some((s) => {
    const instruction = s.instructionSv;
    const hasPositive = SAFE_COOKING_KEYWORDS_SV.some((re) => re.test(instruction));
    const onlyAppearance = UNSAFE_APPEARANCE_ONLY_SV.some((re) => re.test(instruction)) &&
      !SAFE_COOKING_KEYWORDS_SV.some((re) => re.test(instruction));
    return hasPositive && !onlyAppearance;
  });
}

/**
 * Kör en kandidat genom verifieringspipelinen.
 * Returnerar alltid ett VerificationResult; status sätts beroende på om
 * alla grindar passerades.
 */
export async function verifyCandidate(
  candidate: RecipeCandidate,
  ingredients: CanonicalIngredientLookup,
  similarity: SimilarityLookup | null,
  options: VerificationOptions = {},
): Promise<VerificationResult> {
  const opts = { ...DEFAULT_OPTIONS, ...options };
  const reasons: string[] = [];

  // ── 0. Hård avvisning av inkompletta kandidater ──────────────────────────
  if (!candidate.titleSv || candidate.titleSv.trim().length === 0) {
    reasons.push("Receptet saknar titel.");
  }
  if (!Array.isArray(candidate.ingredients) || candidate.ingredients.length === 0) {
    reasons.push("Receptet saknar ingredienser.");
  }
  if (!Array.isArray(candidate.steps) || candidate.steps.length === 0) {
    reasons.push("Receptet saknar tillagningssteg.");
  }
  if (candidate.portions == null || candidate.portions <= 0) {
    reasons.push("Receptet saknar giltigt antal portioner.");
  }
  if (candidate.prepTimeMinutes == null || candidate.cookTimeMinutes == null || candidate.totalTimeMinutes == null) {
    reasons.push("Receptet saknar tidsangivelser.");
  }

  // ── 1. Ingrediensmappning (hård spärr) ───────────────────────────────────
  const canonicalIds: string[] = [];
  const nutritionSources = new Map<string, IngredientNutritionSource>();
  const safetyInfos = new Map<string, IngredientSafetyInfo>();

  for (const ing of candidate.ingredients) {
    const canonical = ingredients.getById(ing.canonicalIngredientId);
    if (!canonical) {
      reasons.push(
        `Ingrediens "${ing.canonicalIngredientId}" finns inte i canonical_ingredients — hård spärr.`,
      );
      continue;
    }
    canonicalIds.push(ing.canonicalIngredientId);

    // Bygg nutrition source för steg 3
    nutritionSources.set(ing.canonicalIngredientId, {
      densityGPerMl: canonical.densityGPerMl ?? undefined,
      gramsPerPiece: canonical.gramsPerPiece ?? undefined,
      nutritionPer100: canonical.nutritionPer100,
    });

    // Bygg safety info för steg 2
    safetyInfos.set(ing.canonicalIngredientId, {
      id: canonical.id,
      allergens: canonical.allergens,
      isVegan: canonical.isVegan,
      isVegetarian: canonical.isVegetarian,
      containsGluten: canonical.containsGluten,
      containsLactose: canonical.containsLactose,
      isPork: canonical.isPork,
      isBeef: canonical.isBeef,
      isAlcohol: canonical.isAlcohol,
      dataVerified: true,
    });
  }

  // Om någon ingrediens saknas → avvisat direkt
  if (reasons.length > 0) {
    return {
      candidate,
      status: "rejected",
      reasons,
      nutritionPerPortion: null,
      allergens: [],
      canonicalIngredientIds: canonicalIds,
    };
  }

  // ── 2. Allergener (härledda, aldrig AI) ──────────────────────────────────
  // AI:s eventuella påståenden om allergener ignoreras fullständigt.
  // Säkerheten härleds alltid deterministiskt ur canonical_ingredients.
  const aiClaims = candidate.aiClaimedAllergens ?? [];
  const derivedAllergens = deriveRecipeAllergens(canonicalIds, safetyInfos);
  const allergens = derivedAllergens;

  // ── 3. Näring (beräknad via nutrition-engine) ────────────────────────────
  const calcIngredients: RecipeIngredientForCalc[] = candidate.ingredients.map((ing) => ({
    canonicalIngredientId: ing.canonicalIngredientId,
    quantity: ing.quantity,
    unit: ing.unit as import("@app/shared-types").Unit,
    optional: ing.optional,
  }));

  const nutritionResult = computeRecipeNutrition(
    calcIngredients,
    candidate.portions,
    nutritionSources,
  );

  if (nutritionResult.uncomputableIngredientIds.length > 0) {
    reasons.push(
      `Näring kunde inte beräknas för: ${nutritionResult.uncomputableIngredientIds.join(", ")}`,
    );
  }

  // ── 4. Språk-/kvalitetskontroll ──────────────────────────────────────────
  if (candidate.prepTimeMinutes > opts.maxPrepTimeMinutes) {
    reasons.push(
      `Förberedelsetid ${candidate.prepTimeMinutes} min överstiger max ${opts.maxPrepTimeMinutes} min.`,
    );
  }
  if (candidate.cookTimeMinutes > opts.maxCookTimeMinutes) {
    reasons.push(
      `Koktid ${candidate.cookTimeMinutes} min överstiger max ${opts.maxCookTimeMinutes} min.`,
    );
  }
  const totalTime = candidate.prepTimeMinutes + candidate.cookTimeMinutes;
  if (totalTime > opts.maxTotalTimeMinutes) {
    reasons.push(`Total tid ${totalTime} min överstiger max ${opts.maxTotalTimeMinutes} min.`);
  }
  if (candidate.steps.length < opts.minSteps) {
    reasons.push(`Endast ${candidate.steps.length} steg — minst ${opts.minSteps} krävs.`);
  }
  if (candidate.ingredients.length < opts.minIngredients) {
    reasons.push(
      `Endast ${candidate.ingredients.length} ingredienser — minst ${opts.minIngredients} krävs.`,
    );
  }
  if (candidate.totalTimeMinutes !== totalTime) {
    reasons.push(
      `totalTimeMinutes (${candidate.totalTimeMinutes}) matchar inte prep+cook (${totalTime}).`,
    );
  }

  // Kontrollera att stegen är rimliga
  for (const step of candidate.steps) {
    if (!step.instructionSv || step.instructionSv.length < 10) {
      reasons.push(`Steg ${step.stepNumber} har för kort instruktion.`);
    }
    if (step.timerSeconds != null && step.timerSeconds < 0) {
      reasons.push(`Steg ${step.stepNumber} har negativ timer.`);
    }
    if (step.temperatureC != null && (step.temperatureC < 0 || step.temperatureC > 350)) {
      reasons.push(`Steg ${step.stepNumber} har orimlig temperatur (${step.temperatureC}°C).`);
    }
  }

  // ── 4b. Matsäkerhetslint: rå fågel/fläsk/ägg/fisk kräver genomstekningssteg ──
  if (requiresSafeCookingStep(canonicalIds) && !hasSafeCookingStep(candidate.steps)) {
    reasons.push(
      "Receptet innehåller rått kött/fågel/ägg/fisk men saknar steg för genomstekning/temperatur.",
    );
  }

  // ── 5. Dedup mot befintliga recept ────────────────────────────────────────
  if (similarity) {
    const isDup = await similarity.hasSimilarity(candidate.titleSv);
    if (isDup) {
      reasons.push(`Titel "${candidate.titleSv}" flaggad som potentiell dubblett.`);
    }
  }

  // ── Resultat ─────────────────────────────────────────────────────────────
  const status: VerificationResult["status"] =
    reasons.length === 0 ? "verified" : "unverified";

  return {
    candidate,
    status,
    reasons,
    nutritionPerPortion: nutritionResult.perPortion,
    allergens,
    canonicalIngredientIds: canonicalIds,
  };
}

/**
 * Kör en batch kandidater genom pipelinen.
 */
export async function verifyBatch(
  candidates: RecipeCandidate[],
  ingredients: CanonicalIngredientLookup,
  similarity: SimilarityLookup | null,
  options?: VerificationOptions,
): Promise<VerificationResult[]> {
  return Promise.all(candidates.map((c) => verifyCandidate(c, ingredients, similarity, options)));
}
