#!/usr/bin/env python3
"""YAZIO daily nutrition sync for JARVIS health DB.

Fetches today's YAZIO diary at night, enriches products, and writes a compact,
idempotent nutrition summary into:
- health_data.db: ernaehrung (one row per logged item)
- health_data.db: tagebuch (one daily summary)

Cron semantics: stdout is delivered. Therefore this script prints only when data
changed/imported or on errors. If nothing changed, it stays silent.
"""
from __future__ import annotations

import hashlib
import json
import os
import sqlite3
import sys
from datetime import date, timedelta
from pathlib import Path
from typing import Any

import requests

YAZIO_BASE_URL = "https://yzapi.yazio.com/v15"
YAZIO_CLIENT_ID = os.getenv("YAZIO_CLIENT_ID", "")
YAZIO_CLIENT_SECRET = os.getenv("YAZIO_CLIENT_SECRET", "")
YAZIO_USERNAME = os.getenv("YAZIO_USERNAME", "")
YAZIO_PASSWORD = os.getenv("YAZIO_PASSWORD", "")
DB_PATH = Path(os.getenv("HEALTH_DB_PATH", "/home/agent/.hermes/assets/Gesundheit/health_data.db"))


def get_token() -> str:
    resp = requests.post(f"{YAZIO_BASE_URL}/oauth/token", json={
        "client_id": YAZIO_CLIENT_ID,
        "client_secret": YAZIO_CLIENT_SECRET,
        "username": YAZIO_USERNAME,
        "password": YAZIO_PASSWORD,
        "grant_type": "password",
    }, timeout=30)
    resp.raise_for_status()
    return resp.json()["access_token"]


def api_get(path: str, token: str, params: dict[str, Any] | None = None) -> Any:
    resp = requests.get(
        f"{YAZIO_BASE_URL}{path}",
        params=params,
        headers={"Authorization": f"Bearer {token}", "Accept-Language": "de-DE"},
        timeout=30,
    )
    resp.raise_for_status()
    return resp.json()


def fnum(x: Any) -> float:
    try:
        return float(x or 0)
    except Exception:
        return 0.0


def nutrient(nutrients: dict[str, Any], key: str, amount_g: float) -> float:
    # YAZIO product nutrients are effectively per gram for macros; energy is J per gram.
    val = fnum(nutrients.get(key))
    if key == "energy.energy":
        return round(val * amount_g / 1000.0, 1)  # kJ -> kcal-ish as existing workflow used
    return round(val * amount_g, 2)


def normalize_items(diary: dict[str, Any], token: str) -> list[dict[str, Any]]:
    items: list[dict[str, Any]] = []
    product_cache: dict[str, dict[str, Any]] = {}

    for item in diary.get("products", []) or []:
        pid = str(item.get("product_id") or "")
        if not pid:
            continue
        if pid not in product_cache:
            try:
                product_cache[pid] = api_get(f"/products/{pid}", token)
            except Exception:
                product_cache[pid] = {}
        prod = product_cache[pid]
        nutrients = prod.get("nutrients") or {}
        amount = fnum(item.get("amount"))
        items.append({
            "meal": str(item.get("daytime") or "unknown"),
            "name": prod.get("name") or f"Produkt {pid}",
            "amount_g": amount,
            "kcal": nutrient(nutrients, "energy.energy", amount),
            "protein_g": nutrient(nutrients, "nutrient.protein", amount),
            "carb_g": nutrient(nutrients, "nutrient.carb", amount),
            "fat_g": nutrient(nutrients, "nutrient.fat", amount),
            "source_type": "product",
            "product_id": pid,
        })

    # Fallbacks for entries that may not have product_id/name details.
    for bucket, source_type in [("simple_products", "simple_product"), ("recipe_portions", "recipe")]:
        for item in diary.get(bucket, []) or []:
            amount = fnum(item.get("amount") or item.get("serving_quantity") or 0)
            name = item.get("name") or item.get("title") or item.get("recipe_name") or source_type
            items.append({
                "meal": str(item.get("daytime") or "unknown"),
                "name": name,
                "amount_g": amount,
                "kcal": fnum(item.get("energy") or item.get("kcal")),
                "protein_g": fnum(item.get("protein")),
                "carb_g": fnum(item.get("carb")),
                "fat_g": fnum(item.get("fat")),
                "source_type": source_type,
                "product_id": str(item.get("id") or ""),
            })
    return items


def ensure_state_table(con: sqlite3.Connection) -> None:
    con.execute("""
        CREATE TABLE IF NOT EXISTS sync_state (
            key TEXT PRIMARY KEY,
            value TEXT,
            updated_at TEXT DEFAULT CURRENT_TIMESTAMP
        )
    """)
    con.commit()


def sync_date(target: date, token: str) -> tuple[bool, str]:
    day = target.isoformat()
    diary = api_get("/user/consumed-items", token, {"date": day})
    items = normalize_items(diary, token)
    if not items:
        return False, f"{day}: keine YAZIO-Einträge"

    payload = json.dumps(items, ensure_ascii=False, sort_keys=True)
    fp = hashlib.sha256(payload.encode()).hexdigest()
    state_key = f"yazio:{day}:fingerprint"

    con = sqlite3.connect(DB_PATH)
    con.row_factory = sqlite3.Row
    try:
        ensure_state_table(con)
        old = con.execute("SELECT value FROM sync_state WHERE key=?", (state_key,)).fetchone()
        if old and old["value"] == fp:
            return False, f"{day}: unverändert"

        # Replace only previous automated YAZIO rows for that date.
        con.execute("DELETE FROM ernaehrung WHERE datum=? AND COALESCE(notizen,'') LIKE '%Quelle: YAZIO API%'", (day,))
        con.execute("DELETE FROM tagebuch WHERE datum=? AND COALESCE(verknuepft,'')='yazio_api'", (day,))

        totals = {"kcal": 0.0, "protein": 0.0, "carb": 0.0, "fat": 0.0}
        lines = []
        for it in items:
            totals["kcal"] += fnum(it["kcal"])
            totals["protein"] += fnum(it["protein_g"])
            totals["carb"] += fnum(it["carb_g"])
            totals["fat"] += fnum(it["fat_g"])
            desc = f"{it['name']} ({it['amount_g']:g} g): {it['kcal']:g} kcal, {it['protein_g']:g} g Protein, {it['carb_g']:g} g KH, {it['fat_g']:g} g Fett"
            con.execute("""
                INSERT INTO ernaehrung (datum, mahlzeit, beschreibung, wirkung, notizen)
                VALUES (?, ?, ?, ?, ?)
            """, (day, it["meal"], desc, "", f"Quelle: YAZIO API; Typ: {it['source_type']}; ID: {it['product_id']}"))
            lines.append(f"- [{it['meal']}] {desc}")

        summary = (
            f"YAZIO-Ernährung {day}\n\n" + "\n".join(lines) +
            f"\n\nSumme: {totals['kcal']:.1f} kcal | {totals['protein']:.1f} g Protein | "
            f"{totals['carb']:.1f} g Kohlenhydrate | {totals['fat']:.1f} g Fett"
        )
        con.execute("""
            INSERT INTO tagebuch (datum, kategori, titel, inhalt, wirkung, verknuepft, notizen)
            VALUES (?, ?, ?, ?, ?, ?, ?)
        """, (day, "ernaehrung", f"YAZIO-Ernährung: {day}", summary, "", "yazio_api", f"Automatisch via YAZIO API; fingerprint={fp}"))
        con.execute("""
            INSERT INTO sync_state(key,value,updated_at) VALUES(?,?,CURRENT_TIMESTAMP)
            ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=CURRENT_TIMESTAMP
        """, (state_key, fp))
        con.commit()
        return True, f"{day}: {len(items)} Einträge importiert/aktualisiert"
    finally:
        con.close()


def main() -> int:
    try:
        token = get_token()
        # Today at 23:15 gives the current day's diary. Also check yesterday to catch late edits.
        targets = [date.today(), date.today() - timedelta(days=1)]
        changed = []
        notes = []
        for target in targets:
            did_change, msg = sync_date(target, token)
            notes.append(msg)
            if did_change:
                changed.append(msg)
        if changed:
            print("YAZIO-Sync aktualisiert:\n" + "\n".join(changed))
        return 0
    except Exception as e:
        print(f"YAZIO-Sync Fehler: {e}", file=sys.stderr)
        return 1

if __name__ == "__main__":
    raise SystemExit(main())
