"""Provider-agnostic entry point for working with large language models."""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, Dict, Optional, Protocol, Sequence, Union

if TYPE_CHECKING:
    from .openai_client import ImageInput, OpenAIResponsesClient
else:
    ImageInput = Union[str, Path]


class SupportsGenerateResponse(Protocol):
    """Protocol describing provider clients."""

    def generate_response(
        self,
        *,
        api_key: str,
        prompt: str,
        model: str,
        max_tokens: int = 32000,
        reasoning_effort: Optional[str] = None,
        images: Optional[Sequence[ImageInput]] = None,
    ) -> str:
        ...

    def list_models(self, *, api_key: str) -> Sequence[dict[str, Optional[str]]]:
        ...


class LLMClient:
    """Central client capable of routing requests to different providers."""

    def __init__(
        self,
        providers: Optional[Dict[str, SupportsGenerateResponse]] = None,
    ) -> None:
        self._providers: Dict[str, SupportsGenerateResponse] = {}
        default_providers = providers or self._discover_default_providers()
        for name, client in default_providers.items():
            self.register_provider(name, client)

        if not self._providers:
            raise RuntimeError(
                "No provider implementations registered. Install the required extras "
                "for your target provider (e.g. `pip install openai`)."
            )

    def register_provider(self, name: str, client: SupportsGenerateResponse) -> None:
        """Register or overwrite a provider implementation."""
        if not name:
            raise ValueError("Provider name must be provided.")
        if client is None:
            raise ValueError("Provider client must be provided.")

        self._providers[name.lower()] = client

    def generate_response(
        self,
        *,
        provider: str,
        api_key: str,
        prompt: str,
        model: str,
        max_tokens: int = 32000,
        reasoning_effort: Optional[str] = None,
        images: Optional[Sequence[ImageInput]] = None,
    ) -> str:
        """Generate a response using the selected provider."""
        if not provider:
            raise ValueError("provider must be provided.")

        provider_client = self._providers.get(provider.lower())
        if provider_client is None:
            available = ", ".join(sorted(self._providers)) or "<none>"
            raise ValueError(
                f"Unknown provider '{provider}'. Available providers: {available}."
            )

        return provider_client.generate_response(
            api_key=api_key,
            prompt=prompt,
            model=model,
            max_tokens=max_tokens,
            reasoning_effort=reasoning_effort,
            images=images,
        )

    def list_models(
        self,
        *,
        provider: str,
        api_key: str,
    ) -> Sequence[dict[str, Optional[str]]]:
        """List models available for the specified provider."""
        if not provider:
            raise ValueError("provider must be provided.")

        provider_client = self._providers.get(provider.lower())
        if provider_client is None:
            available = ", ".join(sorted(self._providers)) or "<none>"
            raise ValueError(
                f"Unknown provider '{provider}'. Available providers: {available}."
            )

        return provider_client.list_models(api_key=api_key)

    @staticmethod
    def _discover_default_providers() -> Dict[str, SupportsGenerateResponse]:
        providers: Dict[str, SupportsGenerateResponse] = {}
        try:
            from .openai_client import OpenAIResponsesClient  # type: ignore
        except ModuleNotFoundError as exc:
            if exc.name != "openai":
                raise
            return providers

        providers["openai"] = OpenAIResponsesClient()

        try:
            from .gemini_client import GeminiClient  # type: ignore
        except ModuleNotFoundError as exc:
            if exc.name not in {"google", "google.genai"}:
                raise
        else:
            providers["gemini"] = GeminiClient()

        try:
            from .anthropic_client import AnthropicClient  # type: ignore
        except ModuleNotFoundError as exc:
            if exc.name != "anthropic":
                raise
        else:
            providers["anthropic"] = AnthropicClient()

        try:
            from .grok_client import GrokClient  # type: ignore
        except ModuleNotFoundError as exc:
            if exc.name != "xai_sdk":
                raise
        else:
            grok_client = GrokClient()
            providers["grok"] = grok_client
            providers.setdefault("xai", grok_client)

        return providers
