"""Unified LLM router — Ollama, DeepSeek, Gemini, Groq, OpenRouter, custom OpenAI-compatible."""

from __future__ import annotations

import json
from typing import Any

import httpx

from app.config import settings
from app.db import execute, fetch_all, fetch_one
from app.services import ollama

# Preset catalog for Settings UI (signup links + default models)
LLM_PRESETS: dict[str, dict[str, Any]] = {
    "ollama": {
        "label": "Ollama (lokaal)",
        "api_base_url": "",
        "models": ["qwen3:8b", "gemma3:12b", "llama3.2", "mistral"],
        "needs_key": False,
        "hint": "Geen API key — draait op je Ollama server.",
    },
    "deepseek": {
        "label": "DeepSeek",
        "api_base_url": "https://api.deepseek.com/v1",
        "models": ["deepseek-chat", "deepseek-reasoner"],
        "needs_key": True,
        "signup_url": "https://platform.deepseek.com/",
        "hint": "Goedkoop · sterk voor code en analyse.",
    },
    "gemini": {
        "label": "Google Gemini",
        "api_base_url": "https://generativelanguage.googleapis.com/v1beta/openai",
        "models": ["gemini-2.0-flash", "gemini-1.5-flash", "gemini-1.5-pro"],
        "needs_key": True,
        "signup_url": "https://aistudio.google.com/apikey",
        "hint": "Gratis tier via Google AI Studio.",
    },
    "groq": {
        "label": "Groq (snel · gratis tier)",
        "api_base_url": "https://api.groq.com/openai/v1",
        "models": ["llama-3.3-70b-versatile", "llama-3.1-8b-instant", "mixtral-8x7b-32768"],
        "needs_key": True,
        "signup_url": "https://console.groq.com/",
        "hint": "Zeer snelle inference · gratis limiet.",
    },
    "openrouter": {
        "label": "OpenRouter",
        "api_base_url": "https://openrouter.ai/api/v1",
        "models": [
            "google/gemini-2.0-flash-exp:free",
            "deepseek/deepseek-r1:free",
            "meta-llama/llama-3.3-70b-instruct:free",
        ],
        "needs_key": True,
        "signup_url": "https://openrouter.ai/",
        "hint": "Veel gratis modellen via één API.",
    },
    "mistral": {
        "label": "Mistral AI",
        "api_base_url": "https://api.mistral.ai/v1",
        "models": ["mistral-small-latest", "open-mistral-nemo"],
        "needs_key": True,
        "signup_url": "https://console.mistral.ai/",
        "hint": "EU-hosted · gratis proef tier.",
    },
    "custom_openai": {
        "label": "Custom OpenAI-compatible",
        "api_base_url": "",
        "models": [],
        "needs_key": True,
        "hint": "Elke API die /v1/chat/completions ondersteunt.",
    },
}


def list_presets() -> list[dict[str, Any]]:
    out = []
    for key, meta in LLM_PRESETS.items():
        row = dict(meta)
        row["id"] = key
        out.append(row)
    return out


def _mask_provider(row: dict[str, Any] | None) -> dict[str, Any] | None:
    if not row:
        return None
    out = dict(row)
    for k, v in list(out.items()):
        if hasattr(v, "isoformat"):
            out[k] = v.isoformat()
    if isinstance(out.get("extra_config"), str):
        try:
            out["extra_config"] = json.loads(out["extra_config"])
        except Exception:
            out["extra_config"] = {}
    out["api_key_set"] = bool(row.get("api_key"))
    out.pop("api_key", None)
    preset = LLM_PRESETS.get(out.get("provider_type") or "", {})
    out["preset_label"] = preset.get("label", out.get("provider_type"))
    out["needs_key"] = preset.get("needs_key", True)
    return out


def list_providers() -> list[dict[str, Any]]:
    rows = fetch_all("SELECT * FROM llm_providers ORDER BY is_default DESC, is_active DESC, id ASC")
    return [_mask_provider(r) for r in rows if r]


def get_provider(provider_id: int | None = None) -> dict[str, Any] | None:
    if provider_id:
        return fetch_one("SELECT * FROM llm_providers WHERE id = %s", (provider_id,))
    row = fetch_one(
        "SELECT * FROM llm_providers WHERE is_default = TRUE ORDER BY id LIMIT 1"
    )
    if row:
        return row
    row = fetch_one(
        "SELECT * FROM llm_providers WHERE is_active = TRUE ORDER BY id LIMIT 1"
    )
    if row:
        return row
    return fetch_one("SELECT * FROM llm_providers ORDER BY id LIMIT 1")


def resolve_provider(provider_id: int | None = None) -> dict[str, Any]:
    row = get_provider(provider_id)
    if not row:
        return {
            "id": 0,
            "label": "Ollama lokaal",
            "provider_type": "ollama",
            "api_base_url": settings.OLLAMA_URL,
            "api_key": "",
            "model": settings.OLLAMA_MODEL,
            "extra_config": {},
        }
    return row


async def chat_messages(
    messages: list[dict[str, str]],
    *,
    provider_id: int | None = None,
    model: str | None = None,
    timeout: float = 120.0,
) -> tuple[str, dict[str, Any]]:
    """Returns (reply_text, meta dict with provider info)."""
    prov = resolve_provider(provider_id)
    ptype = (prov.get("provider_type") or "ollama").lower()
    use_model = model or prov.get("model") or settings.OLLAMA_MODEL
    ollama_timeout = min(timeout, 85.0) if ptype == "ollama" else timeout

    if ptype == "ollama":
        try:
            reply = await ollama.chat_messages(messages, timeout=ollama_timeout, model=use_model)
        except Exception as exc:
            raise RuntimeError(
                f"Ollama timeout/ offline ({exc}). "
                "Voeg DeepSeek of Gemini toe via Instellingen → AI / LLM voor snelle cloud-chat."
            ) from exc
        return reply, {
            "provider_id": prov.get("id"),
            "provider_type": "ollama",
            "provider_label": prov.get("label") or "Ollama",
            "model": use_model,
        }

    api_key = (prov.get("api_key") or "").strip()
    if not api_key:
        raise RuntimeError(
            f"Geen API key voor {prov.get('label') or ptype} — voeg key toe in Instellingen → AI / LLM"
        )

    base = (prov.get("api_base_url") or "").strip().rstrip("/")
    if not base:
        preset = LLM_PRESETS.get(ptype, {})
        base = (preset.get("api_base_url") or "").rstrip("/")
    if not base:
        raise RuntimeError(f"Geen API URL voor provider {prov.get('label')}")

    extra = prov.get("extra_config") or {}
    if isinstance(extra, str):
        try:
            extra = json.loads(extra)
        except Exception:
            extra = {}

    url = f"{base}/chat/completions"
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    }
    if ptype == "openrouter":
        headers["HTTP-Referer"] = extra.get("referer", "https://foodlinkk.local")
        headers["X-Title"] = extra.get("title", "Foodlinkk Command Center")

    payload: dict[str, Any] = {
        "model": use_model,
        "messages": messages,
        "temperature": float(extra.get("temperature", 0.4)),
        "max_tokens": int(extra.get("max_tokens", 2048)),
    }

    async with httpx.AsyncClient(timeout=timeout) as client:
        resp = await client.post(url, headers=headers, json=payload)
        if resp.status_code >= 400:
            detail = resp.text[:500]
            try:
                detail = resp.json().get("error", {}).get("message", detail)
            except Exception:
                pass
            raise RuntimeError(f"{prov.get('label')}: {detail}")
        data = resp.json()
        choices = data.get("choices") or []
        if not choices:
            raise RuntimeError(f"{prov.get('label')}: leeg antwoord")
        content = (choices[0].get("message") or {}).get("content") or ""
        return content.strip(), {
            "provider_id": prov.get("id"),
            "provider_type": ptype,
            "provider_label": prov.get("label"),
            "model": use_model,
        }


async def generate(
    prompt: str,
    system: str | None = None,
    *,
    provider_id: int | None = None,
    model: str | None = None,
    timeout: float = 120.0,
) -> str:
    messages: list[dict[str, str]] = []
    if system:
        messages.append({"role": "system", "content": system})
    messages.append({"role": "user", "content": prompt})
    reply, _meta = await chat_messages(
        messages, provider_id=provider_id, model=model, timeout=timeout
    )
    return reply


async def test_provider(provider_id: int) -> tuple[bool, str]:
    prov = fetch_one("SELECT * FROM llm_providers WHERE id = %s", (provider_id,))
    if not prov:
        return False, "Provider niet gevonden"
    try:
        reply, meta = await chat_messages(
            [{"role": "user", "content": "Antwoord met exact één woord: OK"}],
            provider_id=provider_id,
            timeout=60.0,
        )
        msg = f"{meta.get('provider_label')} · {meta.get('model')} — {reply[:80]}"
        execute(
            """UPDATE llm_providers SET last_test_status = %s, last_test_message = %s,
               last_test_at = NOW(), updated_at = NOW() WHERE id = %s""",
            ("ok", msg, provider_id),
        )
        return True, msg
    except Exception as exc:
        execute(
            """UPDATE llm_providers SET last_test_status = %s, last_test_message = %s,
               last_test_at = NOW(), updated_at = NOW() WHERE id = %s""",
            ("error", str(exc)[:500], provider_id),
        )
        return False, str(exc)


def set_default(provider_id: int) -> None:
    execute("UPDATE llm_providers SET is_default = FALSE, updated_at = NOW()")
    execute(
        "UPDATE llm_providers SET is_default = TRUE, is_active = TRUE, updated_at = NOW() WHERE id = %s",
        (provider_id,),
    )
