"""
Meeconic Portal Backend — génération IA « photo magique » via storyface.ai.

Routes:
  GET  /api/portal/themes   → thèmes IA disponibles pour la clé API (cache 10 min)
  POST /api/portal/magic    → génère la photo magique et l'enregistre dans la galerie famille

Garde-fous (chaque génération coûte du crédit storyface) :
  - la famille doit exister, avoir donné son consentement RGPD et appartenir à
    une école active qui a activé l'IA (réglage « ai_enabled ») ;
  - quota par famille (« ai_max_per_family » de l'école, défaut 5) ;
  - limite par IP, et au plus 3 générations simultanées sur le serveur ;
  - le thème doit faire partie de ceux assignés à la clé API.
La photo générée n'est livrée en HD qu'après achat d'un format « magique »
(le portail n'en montre qu'un aperçu filigrané).
"""

import asyncio
import io
import json
import logging
import time
from pathlib import Path
from typing import Optional

import httpx
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel

from core import config
from core.db import get_db
from core.rate_limit import enforce as rate_limit_enforce
from core.security import client_ip
from services import ai_health, alert_service, audit_service, media_service, photo_service, school_service
from utils.retry import with_retries

log = logging.getLogger("meeconic.ia")

router = APIRouter()
_gen_slots = asyncio.Semaphore(3)

# ── Cache thèmes ──────────────────────────────────────────────────────────────

_themes_cache: dict = {"at": 0.0, "themes": []}
_THEMES_TTL = 600  # 10 min


async def fetch_themes() -> list[dict]:
    if not config.STORYFACE_API_KEY:
        return []
    now = time.monotonic()
    if _themes_cache["themes"] and now - _themes_cache["at"] < _THEMES_TTL:
        return _themes_cache["themes"]
    try:
        async with httpx.AsyncClient(timeout=15.0) as client:
            resp = await client.get(
                f"{config.AUTH_SERVER_URL}/api/v2/themes",
                headers={"X-API-Key": config.STORYFACE_API_KEY},
                params={"extended": "true", "app_ids": "1"},
            )
            resp.raise_for_status()
            themes = resp.json().get("themes", [])
            _themes_cache["themes"] = themes
            _themes_cache["at"] = now
            return themes
    except Exception as e:
        log.error("Error fetching storyface themes: %s", e)
        return _themes_cache["themes"]


@router.get("/api/portal/themes")
async def portal_themes():
    themes = await fetch_themes()
    return {
        "enabled": bool(config.STORYFACE_API_KEY),
        "themes": [
            {
                "id":          t.get("id"),
                "name":        t.get("name"),
                "name_fr":     t.get("name_fr") or t.get("name"),
                "name_es":     t.get("name_es") or t.get("name"),
                "description": t.get("description"),
                "thumbnail":   t.get("thumbnail"),
            }
            for t in themes
        ],
    }


# ── Génération ────────────────────────────────────────────────────────────────

class MagicRequest(BaseModel):
    family_code:    str
    photo_filename: str
    theme_id:       str
    aspect_ratio:   Optional[str] = "3:2"


@router.post("/api/portal/magic")
async def generate_magic(body: MagicRequest, request: Request):
    if not config.STORYFACE_API_KEY:
        raise HTTPException(503, detail="Génération IA non configurée.")

    code_in = body.family_code.strip().upper()
    ip = client_ip(request)
    rate_limit_enforce(f"magic-ip:{ip}", max_attempts=20, window_seconds=3600)

    if not media_service.valid_code(code_in):
        raise HTTPException(404, detail="Code introuvable.")
    with get_db() as conn:
        fam = conn.execute(
            "SELECT f.code, f.school_id, f.consent_at, s.active FROM families f "
            "LEFT JOIN schools s ON s.id=f.school_id WHERE f.code=? OR REPLACE(f.code,'-','')=? "
            "ORDER BY (f.code=?) DESC LIMIT 1", (code_in, photo_service.norm_code(code_in), code_in)).fetchone()
    if not fam or not fam["school_id"] or not fam["active"]:
        raise HTTPException(404, detail="Code introuvable.")
    if not fam["consent_at"]:
        raise HTTPException(403, detail="Le consentement RGPD est requis.")
    code, school_id = fam["code"], fam["school_id"]

    settings = school_service.get_settings(school_id)
    quota = int(settings["ai_max_per_family"])
    if settings["ai_enabled"] != "true" or quota <= 0:
        raise HTTPException(403, detail="La photo magique n'est pas disponible pour cette école.")
    # Quota par famille = nombre de photos magiques ya generadas (un fallo no consume nada).
    used = sum(1 for f in photo_service.list_family_files(school_id, code) if f.name.startswith("ia_"))
    if used >= quota:
        raise HTTPException(403, detail="Quota de photos magiques atteint pour cette famille.")

    photo_path = media_service.safe_photo_path(school_id, code, body.photo_filename)
    if photo_path is None or photo_path.name.startswith("ia_"):
        raise HTTPException(404, detail="Photo introuvable pour ce code famille.")

    themes = await fetch_themes()
    theme = next((t for t in themes if str(t.get("id")) == str(body.theme_id)), None)
    if theme is None:
        raise HTTPException(400, detail="Thème IA inconnu.")

    image_bytes = await asyncio.to_thread(photo_path.read_bytes)
    mime = "image/jpeg" if photo_path.suffix.lower() in (".jpg", ".jpeg") else "image/png"

    async def _generate() -> str:
        async with httpx.AsyncClient(timeout=180.0) as client:
            resp = await client.post(
                f"{config.AUTH_SERVER_URL}/api/v1/picasso-fast-seed",
                files={"image": (photo_path.name, image_bytes, mime)},
                data={
                    "theme_id":     body.theme_id,
                    "app_id":       "1",
                    "aspect_ratio": body.aspect_ratio or "3:2",
                    "short_code":   config.STORYFACE_SHORT_CODE,
                },
                headers={"X-API-Key": config.STORYFACE_API_KEY},
            )
        if not resp.is_success:
            log.error("Storyface IA error %s: %s", resp.status_code, resp.text[:500])
            if resp.status_code in (401, 402, 403):
                alert_service.notify("storyface-auth", "Storyface : clé API refusée ou crédit épuisé",
                                     f"HTTP {resp.status_code}: {resp.text[:300]}")
            # El status viaja en el mensaje: utils.retry decide con él si merece reintento (5xx/429 sí, 4xx no).
            raise HTTPException(502, detail=f"Storyface IA failed (HTTP {resp.status_code}): {resp.text[:200]}")
        url = resp.json().get("result_url")
        if not url:
            raise HTTPException(502, detail="Réponse IA invalide (sans result_url).")
        async with httpx.AsyncClient(timeout=60.0) as client:
            r = await client.get(url)
            r.raise_for_status()
        return r.content

    async with _gen_slots:
        try:
            content = await with_retries(_generate, label=f"ia {school_id}/{code}")   # solo repite lo transitorio
        except Exception as exc:
            ai_health.record_failure(school_id, ai_health.TASK_IA, exc)               # el fallo DEFINITIVO
            log.error("AI generation failed (%s/%s): %s", school_id, code, exc)
            raise HTTPException(502, detail="La génération IA a échoué — réessayez.")
        ai_health.record_success(school_id)

    def _to_webp(raw: bytes) -> bytes:
        from PIL import Image
        img = Image.open(io.BytesIO(raw))
        buf = io.BytesIO()
        img.save(buf, "WEBP", quality=88, method=4)
        return buf.getvalue()

    try:
        out_bytes = await asyncio.to_thread(_to_webp, content)
        ext = "webp"
    except Exception:
        out_bytes = content
        ext = "jpg"

    code_dir = photo_path.parent                   # la generada vive junto a la foto de la que sale
    filename = f"ia_{body.theme_id}_{int(time.time())}.{ext}"
    out_path = code_dir / filename
    await asyncio.to_thread(out_path.write_bytes, out_bytes)
    photo_service.invalidate_families_cache(school_id)

    label = f"{theme.get('name_fr') or theme.get('name')}"
    try:
        meta_file = code_dir / "meta.json"
        meta = json.loads(meta_file.read_text(encoding="utf-8")) if meta_file.exists() else {}
        meta.setdefault("photo_names", {})[filename] = label
        meta_file.write_text(json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8")
    except Exception as exc:
        log.warning("meta.json update failed: %s", exc)

    audit_service.log_event("ia.generate", ip_address=ip, resource_type="family", resource_id=code,
                            school_id=school_id, details={"theme": str(body.theme_id), "file": filename})
    log.info("Magic photo generated: %s/%s (theme %s)", school_id, filename, body.theme_id)
    return {
        "photo": {
            "id":         out_path.stem,
            "filename":   filename,
            "url":        media_service.preview_url(code, filename, "full"),
            "thumb_url":  media_service.preview_url(code, filename, "thumb"),
            "child_name": label,
            "kind":       "ai",
        }
    }
