"""Versioned, fail-closed YAZIO nutrient and missingness contracts."""
from __future__ import annotations

import math
from dataclasses import dataclass
from typing import Any

NUTRIENT_CONTRACT_VERSION = "yazio_nutrients_v3"
NULL_VALUE_CONTRACT_VERSION = "nutrition_value_status_v1"
VALUE_STATUSES = frozenset({
    "documented_zero", "documented_value", "estimated", "not_reported", "unknown", "not_applicable"
})


@dataclass(frozen=True)
class NutrientContract:
    key: str
    label: str
    unit: str
    metric_id: str
    source_unit: str
    conversion_factor: float
    precision: int = 1
    group: str = "macro"

    def public_conversion(self) -> dict[str, Any]:
        return {
            "source_unit": self.source_unit,
            "canonical_unit": self.unit,
            "factor": self.conversion_factor,
            "contract_version": NUTRIENT_CONTRACT_VERSION,
        }


def _factor(source: str, target: str) -> float:
    if source == target:
        return 1.0
    grams = {"g": 1.0, "mg": 1_000.0, "µg": 1_000_000.0}
    if source == "g" and target in grams:
        return grams[target]
    if source == "mg" and target == "g":
        return 0.001
    if source == "mg" and target == "µg":
        return 1_000.0
    if source == "µg" and target == "mg":
        return 0.001
    if source == "µg" and target == "g":
        return 0.000001
    raise ValueError(f"unsupported conversion: {source}->{target}")


def _nutrient(key: str, label: str, unit: str, metric_id: str, *, group: str, precision: int = 1, source_unit: str | None = None) -> NutrientContract:
    source = source_unit or ("kcal" if unit == "kcal" else "g")
    return NutrientContract(key, label, unit, metric_id, source, _factor(source, unit), precision, group)


NUTRIENT_CONTRACTS: dict[str, NutrientContract] = {
    "energy.energy": _nutrient("energy.energy", "Energie", "kcal", "nutrition.energy", group="macro"),
    "nutrient.protein": _nutrient("nutrient.protein", "Protein", "g", "nutrition.protein", group="macro"),
    "nutrient.carb": _nutrient("nutrient.carb", "Kohlenhydrate", "g", "nutrition.carbohydrates", group="macro"),
    "nutrient.fat": _nutrient("nutrient.fat", "Fett", "g", "nutrition.fat", group="macro"),
    "nutrient.dietaryfiber": _nutrient("nutrient.dietaryfiber", "Ballaststoffe", "g", "nutrition.fiber", group="macro"),
    "nutrient.sugar": _nutrient("nutrient.sugar", "Zucker", "g", "nutrition.sugar", group="factor"),
    "nutrient.saturated": _nutrient("nutrient.saturated", "Gesättigte Fettsäuren", "g", "nutrition.saturated_fat", group="factor"),
    "nutrient.salt": _nutrient("nutrient.salt", "Salz", "g", "nutrition.salt", group="factor"),
    "nutrient.sodium": _nutrient("nutrient.sodium", "Natrium", "mg", "nutrition.sodium", group="mineral"),
    "nutrient.alcohol": _nutrient("nutrient.alcohol", "Alkohol", "g", "nutrition.alcohol", group="factor"),
    "nutrient.cholesterol": _nutrient("nutrient.cholesterol", "Cholesterin", "mg", "nutrition.cholesterol", group="other"),
    "nutrient.monounsaturated": _nutrient("nutrient.monounsaturated", "Einfach ungesättigte Fettsäuren", "g", "nutrition.monounsaturated_fat", group="other"),
    "nutrient.polyunsaturated": _nutrient("nutrient.polyunsaturated", "Mehrfach ungesättigte Fettsäuren", "g", "nutrition.polyunsaturated_fat", group="other"),
    "nutrient.transfat": _nutrient("nutrient.transfat", "Transfettsäuren", "g", "nutrition.trans_fat", group="other"),
    "nutrient.water": _nutrient("nutrient.water", "Wasser", "g", "nutrition.water", group="other"),
    "mineral.calcium": _nutrient("mineral.calcium", "Calcium", "mg", "nutrition.calcium", group="mineral"),
    "mineral.chlorine": _nutrient("mineral.chlorine", "Chlorid", "mg", "nutrition.chloride", group="mineral"),
    "mineral.copper": _nutrient("mineral.copper", "Kupfer", "mg", "nutrition.copper", group="mineral", precision=2),
    "mineral.fluorine": _nutrient("mineral.fluorine", "Fluorid", "mg", "nutrition.fluoride", group="mineral", precision=2),
    "mineral.iodine": _nutrient("mineral.iodine", "Jod", "µg", "nutrition.iodine", group="mineral"),
    "mineral.iron": _nutrient("mineral.iron", "Eisen", "mg", "nutrition.iron", group="mineral"),
    "mineral.magnesium": _nutrient("mineral.magnesium", "Magnesium", "mg", "nutrition.magnesium", group="mineral"),
    "mineral.manganese": _nutrient("mineral.manganese", "Mangan", "mg", "nutrition.manganese", group="mineral", precision=2),
    "mineral.phosphorus": _nutrient("mineral.phosphorus", "Phosphor", "mg", "nutrition.phosphorus", group="mineral"),
    "mineral.potassium": _nutrient("mineral.potassium", "Kalium", "mg", "nutrition.potassium", group="mineral"),
    "mineral.selenium": _nutrient("mineral.selenium", "Selen", "µg", "nutrition.selenium", group="mineral"),
    "mineral.sulfur": _nutrient("mineral.sulfur", "Schwefel", "mg", "nutrition.sulfur", group="mineral"),
    "mineral.zinc": _nutrient("mineral.zinc", "Zink", "mg", "nutrition.zinc", group="mineral"),
    "vitamin.a": _nutrient("vitamin.a", "Vitamin A", "µg", "nutrition.vitamin_a", group="vitamin"),
    "vitamin.b1": _nutrient("vitamin.b1", "Vitamin B1", "mg", "nutrition.vitamin_b1", group="vitamin", precision=2),
    "vitamin.b2": _nutrient("vitamin.b2", "Vitamin B2", "mg", "nutrition.vitamin_b2", group="vitamin", precision=2),
    "vitamin.b3": _nutrient("vitamin.b3", "Niacin (Vitamin B3)", "mg", "nutrition.vitamin_b3", group="vitamin"),
    "vitamin.b5": _nutrient("vitamin.b5", "Pantothensäure (Vitamin B5)", "mg", "nutrition.vitamin_b5", group="vitamin", precision=2),
    "vitamin.b6": _nutrient("vitamin.b6", "Vitamin B6", "mg", "nutrition.vitamin_b6", group="vitamin", precision=2),
    "vitamin.b7": _nutrient("vitamin.b7", "Biotin (Vitamin B7)", "µg", "nutrition.vitamin_b7", group="vitamin"),
    "vitamin.b11": _nutrient("vitamin.b11", "Folat (Vitamin B11)", "µg", "nutrition.folate", group="vitamin"),
    "vitamin.b12": _nutrient("vitamin.b12", "Vitamin B12", "µg", "nutrition.vitamin_b12", group="vitamin", precision=2),
    "vitamin.c": _nutrient("vitamin.c", "Vitamin C", "mg", "nutrition.vitamin_c", group="vitamin"),
    "vitamin.d": _nutrient("vitamin.d", "Vitamin D", "µg", "nutrition.vitamin_d", group="vitamin", precision=2),
    "vitamin.e": _nutrient("vitamin.e", "Vitamin E", "mg", "nutrition.vitamin_e", group="vitamin", precision=2),
    "vitamin.k": _nutrient("vitamin.k", "Vitamin K", "µg", "nutrition.vitamin_k", group="vitamin"),
}
NUTRIENT_BY_METRIC = {contract.metric_id: contract for contract in NUTRIENT_CONTRACTS.values()}


def normalize_nutrient_status(
    key: str,
    value: Any,
    unit: Any,
    *,
    explicit_zero: bool = True,
) -> dict[str, Any]:
    """Normalize an exact source unit; unknown or contradictory units fail closed."""
    contract = NUTRIENT_CONTRACTS.get(key)
    if contract is None:
        return {"status": "not_applicable", "value": None, "unit": None}
    if value is None or value == "" or isinstance(value, bool):
        return {"status": "not_reported", "value": None, "unit": contract.unit}
    try:
        number = float(value)
    except (TypeError, ValueError):
        return {"status": "unknown", "value": None, "unit": contract.unit}
    raw_unit = "" if unit is None else str(unit).strip().replace("ug", "µg")
    if not math.isfinite(number) or raw_unit not in {contract.source_unit, contract.unit}:
        return {"status": "unknown", "value": None, "unit": contract.unit, "raw_unit": raw_unit or None}
    factor = contract.conversion_factor if raw_unit == contract.source_unit else 1.0
    normalized = number * factor
    if normalized == 0 and not explicit_zero:
        return {"status": "unknown", "value": None, "unit": contract.unit, "raw_unit": raw_unit, "factor": factor}
    return {
        "status": "documented_zero" if normalized == 0 else "documented_value",
        "value": normalized,
        "unit": contract.unit,
        "raw_value": number,
        "raw_unit": raw_unit,
        "factor": factor,
        "contract_version": NUTRIENT_CONTRACT_VERSION,
    }


def normalize_nutrient(key: str, value: Any, unit: Any) -> tuple[float, str] | None:
    result = normalize_nutrient_status(key, value, unit)
    if result["status"] not in {"documented_zero", "documented_value"}:
        return None
    return float(result["value"]), str(result["unit"])
