import json
import re

from fastapi import HTTPException
import httpx

from app.core.config import get_settings


def build_llm_headers() -> dict[str, str]:
    settings = get_settings()
    headers = {"Content-Type": "application/json"}

    if settings.llm_api_key:
        headers["Authorization"] = f"Bearer {settings.llm_api_key}"

    return headers


def build_llm_timeout() -> httpx.Timeout:
    settings = get_settings()
    timeout_seconds = max(settings.llm_request_timeout_seconds, 5.0)
    connect_timeout = min(timeout_seconds, 10.0)
    return httpx.Timeout(timeout_seconds, connect=connect_timeout)


def _normalize_error_text(value: str) -> str:
    return " ".join((value or "").split()).strip()


def _looks_like_html_error(value: str) -> bool:
    normalized = value.casefold()
    return (
        "<!doctype html" in normalized
        or "<html" in normalized
        or "</html>" in normalized
        or bool(re.search(r"<[a-z][^>]*>", normalized))
    )


def _fallback_http_detail(status_code: int) -> str:
    if status_code == 404:
        return "endpoint LLM non raggiungibile"
    if status_code == 401:
        return "autenticazione LLM non valida"
    if status_code == 403:
        return "accesso al servizio LLM negato"
    if status_code >= 500:
        return "servizio LLM temporaneamente non disponibile"
    return "risposta upstream non valida"


def describe_llm_http_error(exc: httpx.HTTPError) -> str:
    if isinstance(exc, httpx.HTTPStatusError):
        response = exc.response
        detail = ""

        try:
            payload = response.json()
            if isinstance(payload, dict):
                error_payload = payload.get("error")
                if isinstance(error_payload, dict):
                    detail = str(error_payload.get("message") or error_payload.get("detail") or "").strip()
                if not detail:
                    detail = str(payload.get("detail") or payload.get("message") or "").strip()
                if not detail:
                    detail = json.dumps(payload, ensure_ascii=True)
        except Exception:
            detail = response.text.strip()

        detail = _normalize_error_text(detail)[:400]
        if _looks_like_html_error(detail):
            detail = _fallback_http_detail(response.status_code)
        if detail:
            return f"HTTP {response.status_code}: {detail}"
        return f"HTTP {response.status_code}: {_fallback_http_detail(response.status_code)}"

    request = getattr(exc, "request", None)
    request_url = str(request.url) if request is not None else ""
    message = str(exc).strip()
    error_name = exc.__class__.__name__

    if message and request_url:
        return f"{error_name} verso {request_url}: {message}"
    if message:
        return f"{error_name}: {message}"
    if request_url:
        return f"{error_name} verso {request_url}"
    return error_name


async def resolve_llm_model(client: httpx.AsyncClient, base_url: str, headers: dict[str, str]) -> str:
    settings = get_settings()
    if settings.assistant_model:
        return settings.assistant_model

    response = await client.get(f"{base_url}/models", headers=headers)
    response.raise_for_status()
    payload = response.json()
    for item in payload.get("data", []):
        if isinstance(item, dict) and item.get("id"):
            return str(item["id"])

    raise HTTPException(status_code=502, detail="Nessun modello disponibile sull'endpoint LLM configurato")


def extract_llm_reply(payload: dict[str, object]) -> str:
    reply = payload.get("choices", [{}])[0].get("message", {}).get("content")  # type: ignore[index]
    if not reply:
        reply = payload.get("choices", [{}])[0].get("text")  # type: ignore[index]
    if not reply:
        raise HTTPException(status_code=502, detail="Risposta LLM priva di contenuto utilizzabile")
    return str(reply).strip()


async def request_llm_chat_completion(
    messages: list[dict[str, str]],
    *,
    temperature: float | None = None,
) -> tuple[str, str]:
    settings = get_settings()
    base_url = settings.llm_base_url.rstrip("/")
    headers = build_llm_headers()

    try:
        async with httpx.AsyncClient(timeout=build_llm_timeout()) as client:
            model = await resolve_llm_model(client, base_url, headers)
            response = await client.post(
                f"{base_url}/chat/completions",
                headers=headers,
                json={
                    "model": model,
                    "temperature": settings.assistant_temperature if temperature is None else temperature,
                    "messages": messages,
                },
            )
            response.raise_for_status()
    except httpx.HTTPError as exc:
        raise HTTPException(status_code=502, detail=f"Errore comunicazione LLM: {describe_llm_http_error(exc)}") from exc

    data = response.json()
    return extract_llm_reply(data), model
