"""fddb raw-to-canonical transformers.

Each function takes raw HTML (from the fddb adapter Envelope payload)
and returns canonical domain records.  Parsing logic extracted from
fddb_sync.py.  No I/O — pure data transformation.
"""

import re
from html.parser import HTMLParser
from typing import Any

from byteb4rb1e.homeostat.warehouse.schema import (
    Meal,
    MealItem,
    NutritionDay,
)

MONTHS_EN = {
    "january": 1, "february": 2, "march": 3, "april": 4,
    "may": 5, "june": 6, "july": 7, "august": 8,
    "september": 9, "october": 10, "november": 11,
    "december": 12,
}


def transform_month_diary(
    html: str,
    month: int,
    year: int,
    fetched_at: str,
) -> dict[str, dict[str, Any]]:
    """Parse the fddb month diary HTML into per-day raw data.

    Returns a dict mapping date strings (YYYY-MM-DD) to dicts
    with keys: calories, target, meals (list of {name, items}),
    detail_url.

    This is an intermediate step — the detail page is needed for
    full macro data.  Use ``transform_day_detail`` for that.
    """
    try:
        from bs4 import BeautifulSoup
    except ImportError as e:
        raise ImportError(
            "beautifulsoup4 is required for fddb transformation"
        ) from e

    soup = BeautifulSoup(html, "html.parser")
    days: dict[str, dict[str, Any]] = {}

    content = soup.find(
        "div", class_="contentblockgrey-lower-noheader"
    )
    if not content:
        return days

    current_date = None
    current_meal = None
    current_day_data: dict[str, Any] | None = None
    current_meal_items: list[str] = []
    current_detail_url: str | None = None

    for el in content.find_all(["h3", "h4", "tr", "td"]):
        classes = el.get("class", [])

        if "notepaddate" in classes or (
            el.name == "h3"
            and re.match(
                r"\w+day \d+\.", el.get_text(strip=True)
            )
        ):
            if current_date and current_day_data is not None:
                if current_meal and current_meal_items:
                    current_day_data["meals"].append({
                        "name": current_meal,
                        "items": current_meal_items,
                    })
                current_day_data["detail_url"] = (
                    current_detail_url
                )
                days[current_date] = current_day_data

            text = el.get_text(strip=True)
            match = re.match(r"\w+\s+(\d+)\.\s+(\w+)", text)
            if match:
                day_num = int(match.group(1))
                month_name = match.group(2).lower()
                m = MONTHS_EN.get(month_name, month)
                current_date = (
                    f"{year}-{m:02d}-{day_num:02d}"
                )
            else:
                current_date = None

            current_day_data = {
                "calories": 0,
                "target": 0,
                "meals": [],
                "detail_url": None,
            }
            current_meal = None
            current_meal_items = []
            current_detail_url = None
            continue

        if el.name == "h4" and current_date:
            if current_meal and current_meal_items:
                current_day_data["meals"].append({  # type: ignore[index]
                    "name": current_meal,
                    "items": current_meal_items,
                })
            current_meal = el.get_text(strip=True)
            current_meal_items = []
            continue

        if (
            el.name == "tr"
            and current_date
            and current_meal
        ):
            for link in el.find_all("a"):
                href = link.get("href", "")
                if "/food/" in href or "/lebensmittel/" in href:
                    current_meal_items.append(
                        link.get_text(strip=True)
                    )

        if "notepaddatedown" in classes and current_date:
            text = el.get_text(strip=True)
            cal_match = re.search(r"\((\d+)\s*kcal\)", text)
            if cal_match:
                current_day_data["calories"] = int(  # type: ignore[index]
                    cal_match.group(1)
                )
            req_match = re.search(
                r"Required energy:.*?\((\d+)\s*kcal\)", text
            )
            if req_match:
                current_day_data["target"] = int(  # type: ignore[index]
                    req_match.group(1)
                )
            detail_link = el.find(
                "a", href=re.compile(r"myday20")
            )
            if detail_link:
                current_detail_url = detail_link["href"]

    if current_date and current_day_data is not None:
        if current_meal and current_meal_items:
            current_day_data["meals"].append({
                "name": current_meal,
                "items": current_meal_items,
            })
        current_day_data["detail_url"] = current_detail_url
        days[current_date] = current_day_data

    return days


def transform_day_detail(
    html: str,
    date: str,
    fetched_at: str,
    day_data: dict[str, Any] | None = None,
) -> tuple[
    list[NutritionDay], list[Meal], list[MealItem]
]:
    """Parse the fddb detail page HTML into canonical records.

    Returns (nutrition_days, meals, meal_items).

    If *day_data* is provided (from ``transform_month_diary``),
    it supplies calories/target; otherwise these are extracted
    from the detail page.
    """
    try:
        from bs4 import BeautifulSoup
    except ImportError as e:
        raise ImportError(
            "beautifulsoup4 is required for fddb transformation"
        ) from e

    soup = BeautifulSoup(html, "html.parser")
    macros = {"protein_g": 0.0, "carbs_g": 0.0, "fat_g": 0.0}
    parsed_meals: list[dict[str, Any]] = []

    data_table = None
    for table in soup.find_all("table"):
        text = table.get_text(strip=True)
        if "Product" in text and "Calorific" in text:
            data_table = table
            break

    if data_table:
        meal_pattern = re.compile(
            r"^(Breakfast|Dinner|Lunch|Snack\s*\d*):"
            r".*products?$",
            re.IGNORECASE,
        )
        current_meal: dict[str, Any] | None = None

        for tr in data_table.find_all("tr"):
            cells = tr.find_all(["td", "th"])
            if not cells:
                continue
            first = cells[0].get_text(strip=True)
            if first == "Product":
                continue

            if meal_pattern.match(first):
                if current_meal:
                    parsed_meals.append(current_meal)
                meal_name = re.match(
                    r"^([^:]+)", first
                ).group(1).strip()  # type: ignore[union-attr]
                vals = _extract_row_values(cells)
                current_meal = {
                    "name": meal_name,
                    "calories": vals["kcal"],
                    "macros": {
                        "protein_g": vals["protein"],
                        "carbs_g": vals["carbs"],
                        "fat_g": vals["fat"],
                    },
                    "items": [],
                }
                continue

            vals = _extract_row_values(cells)
            time_match = re.match(r"^\d{1,2}:\d{2}", first)
            if time_match and current_meal:
                item_name = re.sub(
                    r"^\d{1,2}:\d{2}", "", first
                ).strip()
                current_meal["items"].append(item_name)
            elif not time_match and not first:
                macros = {
                    "protein_g": float(vals["protein"]),
                    "carbs_g": float(vals["carbs"]),
                    "fat_g": float(vals["fat"]),
                }

        if current_meal:
            parsed_meals.append(current_meal)
    else:
        text = soup.get_text()
        fat_m = re.search(
            r"Fat\s*(\d[\d.,]*)\s*g\s*Equals",
            text, re.IGNORECASE,
        )
        if fat_m:
            macros["fat_g"] = round(
                float(fat_m.group(1).replace(",", "."))
            )
        carb_m = re.search(
            r"Carbohydrates\s*(\d[\d.,]*)\s*g\s*Equals",
            text, re.IGNORECASE,
        )
        if carb_m:
            macros["carbs_g"] = round(
                float(carb_m.group(1).replace(",", "."))
            )
        prot_m = re.search(
            r"Protein\s*(\d[\d.,]*)\s*g\s*Equals",
            text, re.IGNORECASE,
        )
        if prot_m:
            macros["protein_g"] = round(
                float(prot_m.group(1).replace(",", "."))
            )

    cals = (
        day_data["calories"] if day_data else 0
    )
    target = (
        day_data["target"] if day_data else 0
    )

    nutrition_days = [
        NutritionDay(
            source="fddb",
            fetched_at=fetched_at,
            date=date,
            calories_consumed=cals,
            calories_target=target,
            protein_g=macros["protein_g"],
            carbs_g=macros["carbs_g"],
            fat_g=macros["fat_g"],
        )
    ]

    meals = []
    meal_items = []
    for m in parsed_meals:
        meal_macros = m.get("macros", {})
        meals.append(
            Meal(
                source="fddb",
                fetched_at=fetched_at,
                date=date,
                meal_name=m["name"],
                calories=m["calories"],
                protein_g=float(
                    meal_macros.get("protein_g", 0)
                ),
                carbs_g=float(
                    meal_macros.get("carbs_g", 0)
                ),
                fat_g=float(
                    meal_macros.get("fat_g", 0)
                ),
            )
        )
        for item_name in m.get("items", []):
            meal_items.append(
                MealItem(
                    source="fddb",
                    fetched_at=fetched_at,
                    date=date,
                    meal_name=m["name"],
                    item_name=item_name,
                )
            )

    if not parsed_meals and day_data:
        for m in day_data.get("meals", []):
            meals.append(
                Meal(
                    source="fddb",
                    fetched_at=fetched_at,
                    date=date,
                    meal_name=m["name"],
                    calories=0,
                    protein_g=0.0,
                    carbs_g=0.0,
                    fat_g=0.0,
                )
            )
            for item_name in m.get("items", []):
                meal_items.append(
                    MealItem(
                        source="fddb",
                        fetched_at=fetched_at,
                        date=date,
                        meal_name=m["name"],
                        item_name=item_name,
                    )
                )

    return nutrition_days, meals, meal_items


def _extract_row_values(
    cells: list,
) -> dict[str, int]:
    vals = {"kcal": 0, "fat": 0, "carbs": 0, "protein": 0}
    numeric = []
    for cell in cells[1:]:
        text = cell.get_text(strip=True)
        match = re.search(r"([\d.,]+)", text)
        if match:
            try:
                numeric.append(
                    float(match.group(1).replace(",", "."))
                )
            except ValueError:
                numeric.append(0.0)

    if len(numeric) >= 5:
        vals["kcal"] = round(numeric[1])
        vals["fat"] = round(numeric[2])
        vals["carbs"] = round(numeric[3])
        vals["protein"] = round(numeric[4])
    elif len(numeric) >= 3:
        vals["kcal"] = round(numeric[0])
        vals["fat"] = round(numeric[1]) if len(numeric) > 1 else 0
        vals["carbs"] = round(numeric[2]) if len(numeric) > 2 else 0
        vals["protein"] = round(numeric[3]) if len(numeric) > 3 else 0

    return vals
