"""LLM provider client — OpenAI / Anthropic / Gemini via httpx.

Uses keys from `.env`. Falls back to the next configured provider, then to local generation.
"""

from __future__ import annotations

import json
import re
from typing import Any

import httpx

from app.config import get_settings


class LLMError(Exception):
    pass


def _extract_json(text: str) -> Any:
    text = text.strip()
    if text.startswith("```"):
        text = re.sub(r"^```(?:json)?\s*", "", text)
        text = re.sub(r"\s*```$", "", text)
    return json.loads(text)


async def _openai_chat(system: str, user: str, *, model: str, api_key: str) -> str:
    async with httpx.AsyncClient(timeout=90.0) as client:
        res = await client.post(
            "https://api.openai.com/v1/chat/completions",
            headers={"Authorization": f"Bearer {api_key}"},
            json={
                "model": model,
                "temperature": 0.4,
                "response_format": {"type": "json_object"},
                "messages": [
                    {"role": "system", "content": system},
                    {"role": "user", "content": user},
                ],
            },
        )
        res.raise_for_status()
        return res.json()["choices"][0]["message"]["content"]


async def _anthropic_chat(system: str, user: str, *, model: str, api_key: str) -> str:
    async with httpx.AsyncClient(timeout=90.0) as client:
        res = await client.post(
            "https://api.anthropic.com/v1/messages",
            headers={
                "x-api-key": api_key,
                "anthropic-version": "2023-06-01",
                "content-type": "application/json",
            },
            json={
                "model": model,
                "max_tokens": 4096,
                "system": system,
                "messages": [{"role": "user", "content": user}],
            },
        )
        res.raise_for_status()
        data = res.json()
        return "".join(part.get("text", "") for part in data.get("content", []))


async def _gemini_chat(system: str, user: str, *, model: str, api_key: str) -> str:
    url = (
        f"https://generativelanguage.googleapis.com/v1beta/models/"
        f"{model}:generateContent?key={api_key}"
    )
    async with httpx.AsyncClient(timeout=90.0) as client:
        res = await client.post(
            url,
            json={
                "systemInstruction": {"parts": [{"text": system}]},
                "contents": [{"role": "user", "parts": [{"text": user}]}],
                "generationConfig": {
                    "temperature": 0.4,
                    "responseMimeType": "application/json",
                },
            },
        )
        res.raise_for_status()
        data = res.json()
        return data["candidates"][0]["content"]["parts"][0]["text"]


def _provider_available(name: str) -> bool:
    s = get_settings()
    if name in ("openai", "gpt-4o", "gpt"):
        return bool(s.openai_api_key.strip())
    if name in ("anthropic", "claude"):
        return bool(s.anthropic_api_key.strip())
    if name in ("gemini", "google"):
        return bool(s.google_api_key.strip())
    return False


async def chat_json(
    *,
    system: str,
    user: str,
    provider: str | None = None,
) -> tuple[dict[str, Any], str, bool]:
    """Return (parsed_json, provider_used, used_live_llm).

    If no API keys are configured, raises LLMError so callers can use local generation.
    """
    s = get_settings()
    primary = (provider or s.default_llm_provider or "openai").lower()
    fallback = (s.fallback_llm_provider or "gemini").lower()
    order = [primary]
    if fallback not in order:
        order.append(fallback)
    for name in ("openai", "anthropic", "gemini"):
        if name not in order:
            order.append(name)

    last_error: Exception | None = None
    for name in order:
        if not _provider_available(name):
            continue
        try:
            if name in ("openai", "gpt-4o", "gpt"):
                raw = await _openai_chat(
                    system, user, model=s.openai_model, api_key=s.openai_api_key
                )
                return _extract_json(raw), "openai", True
            if name in ("anthropic", "claude"):
                raw = await _anthropic_chat(
                    system, user, model=s.anthropic_model, api_key=s.anthropic_api_key
                )
                return _extract_json(raw), "anthropic", True
            if name in ("gemini", "google"):
                raw = await _gemini_chat(
                    system, user, model=s.google_model, api_key=s.google_api_key
                )
                return _extract_json(raw), "gemini", True
        except Exception as exc:  # noqa: BLE001 — try next provider
            last_error = exc
            continue

    if last_error:
        raise LLMError(f"All LLM providers failed: {last_error}") from last_error
    raise LLMError("No LLM API keys configured in .env")
