"""Server-side AI enhancement (optional central LLM key).

A company can store ONE Anthropic/OpenAI key on the server; clients then send
their dictations/summaries to POST /v1/enhance instead of each carrying a key.
The key never leaves the server (not in /v1/status, logs or discovery).

The server runs under real uvicorn asyncio, so httpx.AsyncClient is used
directly here (no QtAsyncio executor dance — that is a client-only constraint).

Default models are sensible fast choices; the operator can pin `llm_model`.
"""
from __future__ import annotations

import httpx
from loguru import logger

ANTHROPIC_BASE = "https://api.anthropic.com/v1"
ANTHROPIC_VERSION = "2023-06-01"
OPENAI_BASE = "https://api.openai.com/v1"
_DEFAULT_MODEL = {"anthropic": "claude-haiku-4-5", "openai": "gpt-4o-mini"}
_MAX_TOKENS = 8192
_TIMEOUT = httpx.Timeout(120.0, connect=10.0)


class EnhanceError(Exception):
    """Enhancement failed. `client_message` is safe to return to the caller
    (never leaks the key or provider internals)."""

    def __init__(self, client_message: str) -> None:
        super().__init__(client_message)
        self.client_message = client_message


def provider_of(config) -> str:
    p = (getattr(config, "llm_provider", "") or "").strip().lower()
    return p if p in ("anthropic", "openai") else ""


def is_available(config) -> bool:
    """True when the server is configured to enhance (provider + key set)."""
    return bool(provider_of(config) and getattr(config, "llm_api_key", ""))


def _model(config, provider: str) -> str:
    m = (getattr(config, "llm_model", "") or "").strip()
    return m or _DEFAULT_MODEL[provider]


async def enhance(config, *, text: str, system_prompt: str) -> str:
    """Run one enhancement round with the server's key. Raises EnhanceError
    with a client-safe message on any failure."""
    provider = provider_of(config)
    key = getattr(config, "llm_api_key", "") or ""
    if not provider or not key:
        raise EnhanceError("Server enhancement is not configured.")
    model = _model(config, provider)
    try:
        if provider == "anthropic":
            return await _anthropic(text, system_prompt, model, key)
        return await _openai(text, system_prompt, model, key)
    except EnhanceError:
        raise
    except httpx.HTTPStatusError as e:
        # Log the detail server-side; return a generic message to the caller.
        logger.warning("Enhance provider error {}: {}",
                       e.response.status_code, e.response.text[:200])
        raise EnhanceError("The enhancement provider rejected the request.")
    except httpx.HTTPError as e:
        logger.warning("Enhance transport error: {}", e)
        raise EnhanceError("Could not reach the enhancement provider.")


async def _anthropic(text: str, system: str, model: str, key: str) -> str:
    async with httpx.AsyncClient(timeout=_TIMEOUT) as c:
        r = await c.post(
            f"{ANTHROPIC_BASE}/messages",
            headers={"x-api-key": key, "anthropic-version": ANTHROPIC_VERSION},
            json={"model": model, "max_tokens": _MAX_TOKENS, "system": system,
                  "messages": [{"role": "user", "content": text}]},
        )
    r.raise_for_status()
    body = r.json()
    if body.get("stop_reason") == "max_tokens":
        raise EnhanceError("The enhanced text was too long and got cut off.")
    parts = body.get("content", [])
    return "".join(p.get("text", "") for p in parts if p.get("type") == "text")


async def _openai(text: str, system: str, model: str, key: str) -> str:
    async with httpx.AsyncClient(timeout=_TIMEOUT) as c:
        r = await c.post(
            f"{OPENAI_BASE}/chat/completions",
            headers={"Authorization": f"Bearer {key}"},
            json={"model": model, "max_tokens": _MAX_TOKENS,
                  "messages": [{"role": "system", "content": system},
                               {"role": "user", "content": text}]},
        )
    r.raise_for_status()
    choices = r.json().get("choices", [])
    if not choices:
        return ""
    if choices[0].get("finish_reason") == "length":
        raise EnhanceError("The enhanced text was too long and got cut off.")
    return choices[0].get("message", {}).get("content") or ""
