"""ModelRelay API client for LLM reasoning calls."""

import json
import os
import sys
import time
from typing import Any, Iterator, Optional

import httpx

MODELRELAY_BASE_URL = "https://api.modelrelay.ai/api/v1"
DEFAULT_MODEL = "gemini-3-flash-preview"
_MODEL_CACHE_TTL_SECONDS = 300
_MODEL_LIST_CACHE: list[dict[str, Any]] | None = None
_MODEL_LIST_CACHE_AT: float | None = None


def resolve_model(explicit: Optional[str]) -> str:
    """Resolve the model name with env overrides when no explicit model is provided."""
    if explicit and explicit.strip():
        return explicit.strip()
    env_model = os.environ.get("RECALL_RLM_MODEL") or os.environ.get("MODELRELAY_MODEL")
    if env_model and env_model.strip():
        return env_model.strip()
    return DEFAULT_MODEL


class ModelRelayError(Exception):
    """Raised when ModelRelay API returns an error."""

    def __init__(self, status_code: int, message: str, response_body: str = ""):
        self.status_code = status_code
        self.message = message
        self.response_body = response_body
        super().__init__(f"ModelRelay API error {status_code}: {message}")


def get_api_key() -> str:
    """Get ModelRelay API key from environment."""
    key = os.environ.get("MODELRELAY_API_KEY")
    if not key:
        raise ValueError("MODELRELAY_API_KEY environment variable not set")
    return key


def _build_input(messages: list[dict]) -> list[dict]:
    """Convert OpenAI-style messages to ModelRelay input format."""
    result = []
    for msg in messages:
        role = msg["role"]
        content = msg["content"]

        # ModelRelay InputItem format:
        # {"type": "message", "role": "user", "content": [{"type": "text", "text": "..."}]}
        result.append({
            "type": "message",
            "role": role,
            "content": [{"type": "text", "text": content}],
        })

    return result


def _log_event(message: str, **metadata: object) -> None:
    payload = {"message": message, **metadata}
    print(json.dumps(payload, ensure_ascii=True), file=sys.stderr, flush=True)


class TokenUsage:
    """Token usage from an API call."""
    def __init__(self, input_tokens: int = 0, output_tokens: int = 0):
        self.input_tokens = input_tokens
        self.output_tokens = output_tokens
        self.total_tokens = input_tokens + output_tokens


def list_models() -> list[dict[str, Any]]:
    """Fetch available models from ModelRelay."""
    global _MODEL_LIST_CACHE, _MODEL_LIST_CACHE_AT
    now = time.time()
    if _MODEL_LIST_CACHE is not None and _MODEL_LIST_CACHE_AT is not None:
        if now - _MODEL_LIST_CACHE_AT < _MODEL_CACHE_TTL_SECONDS:
            return _MODEL_LIST_CACHE

    headers = {
        "Accept": "application/json",
        "X-ModelRelay-Client": "rcl-rlm-python",
    }
    api_key = os.environ.get("MODELRELAY_API_KEY")
    if api_key:
        headers["X-ModelRelay-Api-Key"] = api_key

    try:
        with httpx.Client(timeout=20.0) as client:
            response = client.get(f"{MODELRELAY_BASE_URL}/models", headers=headers)
            response.raise_for_status()
            payload = response.json()
    except Exception as exc:  # noqa: BLE001 - network boundary
        _log_event("modelrelay_models_fetch_failed", error=str(exc))
        return []

    models = payload.get("models")
    if isinstance(models, list):
        _MODEL_LIST_CACHE = models
        _MODEL_LIST_CACHE_AT = now
        return models

    _log_event("modelrelay_models_parse_failed", payload_type=str(type(payload)))
    return []


def get_model_info(model_id: str) -> dict[str, Any] | None:
    """Return model metadata for a given model id."""
    if not model_id:
        return None
    for entry in list_models():
        if entry.get("model_id") == model_id:
            return entry
    return None


def get_model_context_window(model_id: str) -> int | None:
    """Return the max context window (tokens) for a model if known."""
    info = get_model_info(model_id)
    if not info:
        return None
    value = info.get("context_window")
    try:
        return int(value)
    except (TypeError, ValueError):
        return None


def get_model_max_output_tokens(model_id: str) -> int | None:
    """Return the max output tokens for a model if known."""
    info = get_model_info(model_id)
    if not info:
        return None
    value = info.get("max_output_tokens")
    try:
        return int(value)
    except (TypeError, ValueError):
        return None


def chat_completion(
    messages: list[dict],
    model: Optional[str] = None,
    temperature: float = 0.0,
    max_tokens: int = 4096,
    max_retries: int = 3,
    return_usage: bool = False,
) -> str | tuple[str, TokenUsage]:
    """Make a responses request to ModelRelay with retry logic for transient errors."""
    import time

    model = resolve_model(model)
    api_key = get_api_key()

    request_body = {
        "model": model,
        "input": _build_input(messages),
        "max_output_tokens": max_tokens,
        "temperature": temperature,
    }

    last_error = None
    for attempt in range(max_retries):
        with httpx.Client(timeout=60.0) as client:
            response = client.post(
                f"{MODELRELAY_BASE_URL}/responses",
                headers={
                    "X-ModelRelay-Api-Key": api_key,
                    "X-ModelRelay-Client": "rcl-rlm-python",
                    "Content-Type": "application/json",
                    "Accept": "application/json",
                },
                json=request_body,
            )
            if response.status_code >= 500:
                # Retry on 5xx errors (transient server issues)
                error_text = response.text
                last_error = ModelRelayError(response.status_code, error_text, error_text)
                if attempt < max_retries - 1:
                    wait_time = 2 ** attempt  # Exponential backoff: 1s, 2s, 4s
                    _log_event(
                        "modelrelay_retry",
                        status_code=response.status_code,
                        wait_seconds=wait_time,
                        attempt=attempt + 1,
                        max_retries=max_retries,
                    )
                    time.sleep(wait_time)
                    continue
                _log_event(
                    "modelrelay_error",
                    status_code=response.status_code,
                    error_text=error_text,
                )
                raise last_error
            if response.status_code >= 400:
                error_text = response.text
                _log_event(
                    "modelrelay_error",
                    status_code=response.status_code,
                    error_text=error_text,
                )
                raise ModelRelayError(response.status_code, error_text, error_text)
            data = response.json()
            break
    else:
        raise last_error

    # Extract text from output items
    # OutputItem format: {"type": "message", "role": "assistant", "content": [{"type": "text", "text": "..."}]}
    output = data.get("output", [])
    text_parts = []
    for item in output:
        if item.get("type") == "message" and item.get("role") == "assistant":
            for part in item.get("content", []):
                if part.get("type") == "text":
                    text_parts.append(part.get("text", ""))

    result_text = "".join(text_parts)

    if return_usage:
        # Extract token usage from response
        usage = data.get("usage", {})
        token_usage = TokenUsage(
            input_tokens=usage.get("input_tokens", 0),
            output_tokens=usage.get("output_tokens", 0),
        )
        return result_text, token_usage

    return result_text


def chat_completion_stream(
    messages: list[dict],
    model: Optional[str] = None,
    temperature: float = 0.0,
    max_tokens: int = 4096,
) -> Iterator[str]:
    """Stream a responses request to ModelRelay."""
    model = resolve_model(model)
    api_key = get_api_key()

    request_body = {
        "model": model,
        "input": _build_input(messages),
        "max_output_tokens": max_tokens,
        "temperature": temperature,
    }

    with httpx.Client(timeout=120.0) as client:
        with client.stream(
            "POST",
            f"{MODELRELAY_BASE_URL}/responses",
            headers={
                "X-ModelRelay-Api-Key": api_key,
                "X-ModelRelay-Client": "rcl-rlm-python",
                "Content-Type": "application/json",
                "Accept": "application/x-ndjson; profile=\"responses-stream/v2\"",
            },
            json=request_body,
        ) as response:
            response.raise_for_status()
            import json
            for line in response.iter_lines():
                if not line:
                    continue
                try:
                    event = json.loads(line)
                    if event.get("type") == "content_delta":
                        delta = event.get("delta", {})
                        if delta.get("type") == "text":
                            yield delta.get("content", "")
                except json.JSONDecodeError:
                    continue
