"""Provider-specific API key validation.

Each supported provider has a lightweight test call that verifies the key
works without consuming significant quota.
"""

from __future__ import annotations

import logging
from typing import Any, ClassVar, cast

import httpx

from llmhosts.keys.models import KeyValidation

logger = logging.getLogger(__name__)

# Timeout for validation HTTP calls (seconds)
_VALIDATION_TIMEOUT = 15.0


class ProviderValidator:
    """Validates API keys by making test calls to each provider's API."""

    # Map provider name -> validation method name
    _VALIDATORS: ClassVar[dict[str, str]] = {
        "openai": "_validate_openai",
        "anthropic": "_validate_anthropic",
        "openrouter": "_validate_openrouter",
        "google": "_validate_google",
        "mistral": "_validate_mistral",
        "groq": "_validate_groq",
        "together": "_validate_together",
        "deepseek": "_validate_deepseek",
    }

    @classmethod
    async def validate(cls, provider: str, api_key: str) -> KeyValidation:
        """Route to the correct provider-specific validator.

        Args:
            provider: Lowercase provider name (e.g. "openai").
            api_key: The decrypted API key to validate.

        Returns:
            KeyValidation with is_valid=True/False and optional error detail.
        """
        method_name = cls._VALIDATORS.get(provider)
        if method_name is None:
            return KeyValidation.failure(provider, f"No validator implemented for provider '{provider}'")
        method = getattr(cls, method_name)
        try:
            return cast("KeyValidation", await method(api_key))
        except httpx.ConnectError as exc:
            logger.warning("Connection error validating %s key: %s", provider, exc)
            return KeyValidation.failure(provider, f"Connection error: could not reach {provider} API")
        except httpx.TimeoutException:
            logger.warning("Timeout validating %s key", provider)
            return KeyValidation.failure(
                provider, f"Timeout: {provider} API did not respond within {_VALIDATION_TIMEOUT}s"
            )
        except Exception as exc:
            logger.exception("Unexpected error validating %s key", provider)
            return KeyValidation.failure(provider, f"Unexpected error: {exc}")

    # ------------------------------------------------------------------
    # Provider-specific validators
    # ------------------------------------------------------------------

    @staticmethod
    async def _validate_openai(api_key: str) -> KeyValidation:
        """Validate an OpenAI key via GET /v1/models."""
        async with httpx.AsyncClient(timeout=_VALIDATION_TIMEOUT) as client:
            resp = await client.get(
                "https://api.openai.com/v1/models",
                headers={"Authorization": f"Bearer {api_key}"},
            )
        if resp.status_code == 200:
            data: dict[str, Any] = resp.json()
            model_count = len(data.get("data", []))
            return KeyValidation.success("openai", models_available=model_count)
        error_msg = _extract_error(resp, "openai")
        return KeyValidation.failure("openai", error_msg)

    @staticmethod
    async def _validate_anthropic(api_key: str) -> KeyValidation:
        """Validate an Anthropic key via GET /v1/models."""
        async with httpx.AsyncClient(timeout=_VALIDATION_TIMEOUT) as client:
            resp = await client.get(
                "https://api.anthropic.com/v1/models",
                headers={
                    "x-api-key": api_key,
                    "anthropic-version": "2023-06-01",
                },
            )
        if resp.status_code == 200:
            data: dict[str, Any] = resp.json()
            model_count = len(data.get("data", []))
            return KeyValidation.success("anthropic", models_available=model_count)
        error_msg = _extract_error(resp, "anthropic")
        return KeyValidation.failure("anthropic", error_msg)

    @staticmethod
    async def _validate_openrouter(api_key: str) -> KeyValidation:
        """Validate an OpenRouter key via GET /api/v1/models."""
        async with httpx.AsyncClient(timeout=_VALIDATION_TIMEOUT) as client:
            resp = await client.get(
                "https://openrouter.ai/api/v1/models",
                headers={"Authorization": f"Bearer {api_key}"},
            )
        if resp.status_code == 200:
            data: dict[str, Any] = resp.json()
            model_count = len(data.get("data", []))
            return KeyValidation.success("openrouter", models_available=model_count)
        error_msg = _extract_error(resp, "openrouter")
        return KeyValidation.failure("openrouter", error_msg)

    @staticmethod
    async def _validate_google(api_key: str) -> KeyValidation:
        """Validate a Google AI (Gemini) key via GET /v1beta/models."""
        async with httpx.AsyncClient(timeout=_VALIDATION_TIMEOUT) as client:
            resp = await client.get(
                "https://generativelanguage.googleapis.com/v1beta/models",
                params={"key": api_key},
            )
        if resp.status_code == 200:
            data: dict[str, Any] = resp.json()
            model_count = len(data.get("models", []))
            return KeyValidation.success("google", models_available=model_count)
        error_msg = _extract_error(resp, "google")
        return KeyValidation.failure("google", error_msg)

    @staticmethod
    async def _validate_mistral(api_key: str) -> KeyValidation:
        """Validate a Mistral key via GET /v1/models."""
        async with httpx.AsyncClient(timeout=_VALIDATION_TIMEOUT) as client:
            resp = await client.get(
                "https://api.mistral.ai/v1/models",
                headers={"Authorization": f"Bearer {api_key}"},
            )
        if resp.status_code == 200:
            data: dict[str, Any] = resp.json()
            model_count = len(data.get("data", []))
            return KeyValidation.success("mistral", models_available=model_count)
        error_msg = _extract_error(resp, "mistral")
        return KeyValidation.failure("mistral", error_msg)

    @staticmethod
    async def _validate_groq(api_key: str) -> KeyValidation:
        """Validate a Groq key via GET /openai/v1/models."""
        async with httpx.AsyncClient(timeout=_VALIDATION_TIMEOUT) as client:
            resp = await client.get(
                "https://api.groq.com/openai/v1/models",
                headers={"Authorization": f"Bearer {api_key}"},
            )
        if resp.status_code == 200:
            data: dict[str, Any] = resp.json()
            model_count = len(data.get("data", []))
            return KeyValidation.success("groq", models_available=model_count)
        error_msg = _extract_error(resp, "groq")
        return KeyValidation.failure("groq", error_msg)

    @staticmethod
    async def _validate_together(api_key: str) -> KeyValidation:
        """Validate a Together AI key via GET /v1/models."""
        async with httpx.AsyncClient(timeout=_VALIDATION_TIMEOUT) as client:
            resp = await client.get(
                "https://api.together.xyz/v1/models",
                headers={"Authorization": f"Bearer {api_key}"},
            )
        if resp.status_code == 200:
            data: Any = resp.json()
            model_count = len(data) if isinstance(data, list) else len(data.get("data", data.get("models", [])))
            return KeyValidation.success("together", models_available=model_count)
        error_msg = _extract_error(resp, "together")
        return KeyValidation.failure("together", error_msg)

    @staticmethod
    async def _validate_deepseek(api_key: str) -> KeyValidation:
        """Validate a DeepSeek key via GET /models (OpenAI-compatible)."""
        async with httpx.AsyncClient(timeout=_VALIDATION_TIMEOUT) as client:
            resp = await client.get(
                "https://api.deepseek.com/models",
                headers={"Authorization": f"Bearer {api_key}"},
            )
        if resp.status_code == 200:
            data: dict[str, Any] = resp.json()
            model_count = len(data.get("data", []))
            return KeyValidation.success("deepseek", models_available=model_count)
        error_msg = _extract_error(resp, "deepseek")
        return KeyValidation.failure("deepseek", error_msg)


# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------


def _extract_error(resp: httpx.Response, provider: str) -> str:
    """Extract a human-readable error message from a failed response."""
    try:
        body = resp.json()
        # OpenAI-style: {"error": {"message": "..."}}
        if isinstance(body, dict):
            err = body.get("error")
            if isinstance(err, dict):
                msg = err.get("message", "")
                if msg:
                    return f"{provider} API error ({resp.status_code}): {msg}"
            # Anthropic-style: {"error": {"type": "...", "message": "..."}}
            if isinstance(err, dict) and "message" in err:
                return f"{provider} API error ({resp.status_code}): {err['message']}"
            # Google-style: {"error": {"message": "...", "status": "..."}}
            if "message" in body:
                return f"{provider} API error ({resp.status_code}): {body['message']}"
    except Exception:
        pass
    return f"{provider} API returned HTTP {resp.status_code}"
