# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Open Food Facts Skill

Barcode lookup, product search, and allergen checking for packaged foods.
Uses the Open Food Facts open database — free, no authentication required.

API: https://world.openfoodfacts.org/api/v2/
"""

import logging

from familiar.core.api_cache import cached
from familiar.skills._api_utils import APIError, api_get

logger = logging.getLogger(__name__)

BASE_URL = "https://world.openfoodfacts.org"
USER_AGENT = "Familiar/1.14.54 (https://github.com/omegcrash/familiar)"


def _headers() -> dict:
    return {"User-Agent": USER_AGENT}


# ---------------------------------------------------------------------------
# Tool handlers
# ---------------------------------------------------------------------------


def barcode_lookup(data: dict) -> str:
    """Look up a food product by barcode."""
    barcode = str(data.get("barcode", "")).strip()
    if not barcode:
        return "Please provide a barcode (UPC/EAN)."

    # Basic validation — barcodes are numeric, 8-14 digits
    clean = barcode.replace("-", "").replace(" ", "")
    if not clean.isdigit() or len(clean) < 8 or len(clean) > 14:
        return "Invalid barcode format. Expected 8-14 digit UPC/EAN code."

    try:
        result = _barcode_cached(clean)

        status = result.get("status", 0)
        if status != 1:
            return f"Product not found for barcode {barcode}."

        product = result.get("product", {})
        return _format_product(product, barcode)

    except APIError as e:
        return str(e)
    except Exception as e:
        logger.error("Open Food Facts barcode error: %s", e)
        return f"Error looking up barcode: {e}"


@cached(prefix="off_barcode", ttl=86400)
def _barcode_cached(barcode: str) -> dict:
    return api_get(
        f"{BASE_URL}/api/v2/product/{barcode}.json",
        headers=_headers(),
        service_name="Open Food Facts",
    )


def product_search(data: dict) -> str:
    """Search for food products by name."""
    query = data.get("query", "").strip()
    if not query:
        return "Please provide a product name to search for."

    page_size = min(int(data.get("limit", 10)), 24)

    try:
        result = _search_cached(query, page_size)

        products = result.get("products", [])
        count = result.get("count", len(products))

        if not products:
            return f"No products found for '{query}'."

        lines = [f"**Found {count} products for '{query}'** (showing {len(products)}):", ""]

        for i, p in enumerate(products[:page_size], 1):
            name = p.get("product_name", "Unknown Product")
            brand = p.get("brands", "")
            barcode = p.get("code", "")
            nutriscore = p.get("nutrition_grades", "")
            categories = p.get("categories", "")

            label = name
            if brand:
                label += f" ({brand})"

            lines.append(f"{i}. **{label}**")
            if barcode:
                lines.append(f"   Barcode: {barcode}")
            if nutriscore:
                lines.append(f"   Nutri-Score: {nutriscore.upper()}")
            if categories:
                cats = categories.split(",")[:3]
                lines.append(f"   Categories: {', '.join(c.strip() for c in cats)}")
            lines.append("")

        lines.append("Use `barcode_lookup` with a barcode for full product details.")
        return "\n".join(lines)

    except APIError as e:
        return str(e)
    except Exception as e:
        logger.error("Open Food Facts search error: %s", e)
        return f"Error searching products: {e}"


@cached(prefix="off_search", ttl=3600)
def _search_cached(query: str, page_size: int) -> dict:
    return api_get(
        f"{BASE_URL}/cgi/search.pl",
        params={
            "search_terms": query,
            "page_size": page_size,
            "json": 1,
            "search_simple": 1,
        },
        headers=_headers(),
        service_name="Open Food Facts",
    )


def allergen_check(data: dict) -> str:
    """Check a product for allergens by barcode."""
    barcode = str(data.get("barcode", "")).strip()
    if not barcode:
        return "Please provide a barcode to check for allergens."

    clean = barcode.replace("-", "").replace(" ", "")
    if not clean.isdigit() or len(clean) < 8 or len(clean) > 14:
        return "Invalid barcode format. Expected 8-14 digit UPC/EAN code."

    try:
        result = _barcode_cached(clean)

        status = result.get("status", 0)
        if status != 1:
            return f"Product not found for barcode {barcode}."

        product = result.get("product", {})
        name = product.get("product_name", "Unknown Product")
        brand = product.get("brands", "")

        label = name
        if brand:
            label += f" ({brand})"

        lines = [f"**Allergen Report: {label}**", ""]

        # Allergens
        allergens = product.get("allergens", "")
        allergens_tags = product.get("allergens_tags", [])
        allergens_from = product.get("allergens_from_ingredients", "")

        if allergens or allergens_tags:
            lines.append("**Contains:**")
            if allergens_tags:
                for tag in allergens_tags:
                    # Tags like "en:milk", "en:gluten" — clean up
                    allergen = tag.split(":")[-1].replace("-", " ").title()
                    lines.append(f"  - {allergen}")
            elif allergens:
                lines.append(f"  {allergens}")
            lines.append("")

        if allergens_from:
            lines.append(f"**From ingredients:** {allergens_from}")
            lines.append("")

        # Traces / may contain
        traces = product.get("traces", "")
        traces_tags = product.get("traces_tags", [])
        if traces or traces_tags:
            lines.append("**May contain (traces):**")
            if traces_tags:
                for tag in traces_tags:
                    trace = tag.split(":")[-1].replace("-", " ").title()
                    lines.append(f"  - {trace}")
            elif traces:
                lines.append(f"  {traces}")
            lines.append("")

        # Ingredients
        ingredients_text = product.get("ingredients_text", "")
        if ingredients_text:
            lines.append(f"**Ingredients:** {ingredients_text[:500]}")
            lines.append("")

        if not allergens and not allergens_tags and not traces and not traces_tags:
            lines.append("No allergen data available for this product.")
            lines.append("Check the product label directly.")

        return "\n".join(lines)

    except APIError as e:
        return str(e)
    except Exception as e:
        logger.error("Open Food Facts allergen error: %s", e)
        return f"Error checking allergens: {e}"


def _format_product(product: dict, barcode: str) -> str:
    """Format a product dict into display text."""
    name = product.get("product_name", "Unknown Product")
    brand = product.get("brands", "")
    categories = product.get("categories", "")
    nutriscore = product.get("nutrition_grades", "")
    quantity = product.get("quantity", "")

    label = name
    if brand:
        label += f" ({brand})"

    lines = [f"**{label}**", f"Barcode: {barcode}", ""]

    if quantity:
        lines.append(f"Quantity: {quantity}")
    if categories:
        cats = categories.split(",")[:5]
        lines.append(f"Categories: {', '.join(c.strip() for c in cats)}")
    if nutriscore:
        lines.append(f"Nutri-Score: {nutriscore.upper()}")
    lines.append("")

    # Nutrition
    nutriments = product.get("nutriments", {})
    if nutriments:
        lines.append("**Nutrition per 100g:**")
        nutrition_keys = [
            ("energy-kcal_100g", "Calories", "kcal"),
            ("fat_100g", "Fat", "g"),
            ("saturated-fat_100g", "Saturated fat", "g"),
            ("carbohydrates_100g", "Carbohydrates", "g"),
            ("sugars_100g", "Sugars", "g"),
            ("fiber_100g", "Fiber", "g"),
            ("proteins_100g", "Protein", "g"),
            ("salt_100g", "Salt", "g"),
            ("sodium_100g", "Sodium", "g"),
        ]
        for key, label, unit in nutrition_keys:
            val = nutriments.get(key)
            if val is not None:
                if isinstance(val, float):
                    lines.append(f"  {label}: {val:.1f} {unit}")
                else:
                    lines.append(f"  {label}: {val} {unit}")
        lines.append("")

    # Allergens
    allergens = product.get("allergens_tags", [])
    if allergens:
        allergen_names = [a.split(":")[-1].replace("-", " ").title() for a in allergens]
        lines.append(f"**Allergens:** {', '.join(allergen_names)}")

    # Ingredients
    ingredients = product.get("ingredients_text", "")
    if ingredients:
        lines.append(f"**Ingredients:** {ingredients[:400]}")

    return "\n".join(lines)


# ---------------------------------------------------------------------------
# Tool definitions
# ---------------------------------------------------------------------------

TOOLS = [
    {
        "name": "barcode_lookup",
        "description": (
            "Look up a food product by UPC/EAN barcode."
            " Returns product name, brand, nutrition facts, allergens, and ingredients."
            " Uses Open Food Facts — free, no setup required."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "barcode": {
                    "type": "string",
                    "description": "UPC/EAN barcode (8-14 digits)",
                },
            },
            "required": ["barcode"],
        },
        "handler": barcode_lookup,
        "category": "openfoodfacts",
    },
    {
        "name": "product_search",
        "description": (
            "Search for food products by name."
            " Returns product name, brand, Nutri-Score, and barcode."
            " Use barcode_lookup for full product details."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "Product name to search for",
                },
                "limit": {
                    "type": "integer",
                    "description": "Max results (1-24, default 10)",
                    "default": 10,
                },
            },
            "required": ["query"],
        },
        "handler": product_search,
        "category": "openfoodfacts",
    },
    {
        "name": "allergen_check",
        "description": (
            "Check a food product for allergens by barcode."
            " Shows confirmed allergens, traces/cross-contamination warnings,"
            " and ingredient list. Essential for dietary safety."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "barcode": {
                    "type": "string",
                    "description": "UPC/EAN barcode to check for allergens",
                },
            },
            "required": ["barcode"],
        },
        "handler": allergen_check,
        "category": "openfoodfacts",
    },
]
