"""LLM provider facade — core routing logic.

PSA1: Core imports ONLY from providers/ and loader. No direct plugin imports.
ModelRouter resolves providers via loader.get() so plugins stay swappable.
"""

from __future__ import annotations

import logging

from vastian.config import LLMProvider, Settings
from vastian.providers.llm_provider import BaseLLMProvider

logger = logging.getLogger(__name__)

# Backward compat re-exports (PSA1: resolve via loader)
__all__ = [
    "BaseLLMProvider",
    "ModelRouter",
    "create_provider",
    "OpenAIProvider",  # noqa: F822
    "AnthropicProvider",  # noqa: F822
]


def _get_llm_provider(name: str) -> type[BaseLLMProvider]:
    """Resolve LLM provider class via loader (PSA1: no direct plugin import)."""
    from vastian.bootstrap import register_all
    from vastian.loader import get

    register_all()
    return get(name)


# Lazy re-exports for callers that import OpenAIProvider/AnthropicProvider directly
def __getattr__(name: str) -> type[BaseLLMProvider]:
    if name == "OpenAIProvider":
        return _get_llm_provider("openai_llm")
    if name == "AnthropicProvider":
        return _get_llm_provider("anthropic_llm")
    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


class ModelRouter:
    """
    Intelligent model router that manages both OpenAI and Anthropic providers.

    Auto-detects available providers based on API keys, enables per-agent
    provider/model overrides, and exposes all 4 models (2 OpenAI + 2 Anthropic)
    for flexible routing.

    Available models:
      - OpenAI:    gpt-4o (default), gpt-4o-mini (fast)
      - Anthropic: claude-opus-4-5-20251120 (default), claude-sonnet-4-20250514 (fast)
    """

    def __init__(self, settings: Settings) -> None:
        self.settings = settings
        self.providers: dict[str, BaseLLMProvider] = {}
        self._primary: str | None = None

        # Auto-detect available providers from API keys (PSA1: resolve via loader)
        if settings.openai_api_key:
            try:
                OpenAICls = _get_llm_provider("openai_llm")  # noqa: N806
                self.providers["openai"] = OpenAICls(settings)
                logger.info("ModelRouter: OpenAI provider available (gpt-4o, gpt-4o-mini)")
            except Exception as e:
                logger.warning("ModelRouter: OpenAI init failed: %s", e)

        if settings.anthropic_api_key:
            try:
                AnthropicCls = _get_llm_provider("anthropic_llm")  # noqa: N806
                self.providers["anthropic"] = AnthropicCls(settings)
                logger.info(
                    "ModelRouter: Anthropic provider available "
                    "(claude-opus-4-5-20251120, claude-sonnet-4-20250514)"
                )
            except Exception as e:
                logger.warning("ModelRouter: Anthropic init failed: %s", e)

        # Ollama (local LLM via OpenAI-compatible API)
        import os

        ollama_model = os.environ.get("OLLAMA_MODEL", "")
        if settings.llm_provider == LLMProvider.OLLAMA or ollama_model:
            try:
                OllamaCls = _get_llm_provider("ollama_llm")  # noqa: N806
                self.providers["ollama"] = OllamaCls(settings)
                logger.info(
                    "ModelRouter: Ollama provider available (model: %s)", ollama_model or "llama3"
                )
            except Exception as e:
                logger.warning("ModelRouter: Ollama init failed: %s", e)

        # Determine primary provider
        preferred = settings.llm_provider
        if preferred == LLMProvider.AUTO:
            # Prefer Ollama (local-first), then OpenAI, then Anthropic
            for name in ("ollama", "openai", "anthropic"):
                if name in self.providers:
                    self._primary = name
                    break
        elif preferred == LLMProvider.OPENAI and "openai" in self.providers:
            self._primary = "openai"
        elif preferred == LLMProvider.ANTHROPIC and "anthropic" in self.providers:
            self._primary = "anthropic"
        elif preferred == LLMProvider.OLLAMA and "ollama" in self.providers:
            self._primary = "ollama"

        # Fallback: if the user's preferred provider failed to init but others are
        # available, pick the first available one rather than running with zero providers
        if not self._primary and self.providers:
            self._primary = next(iter(self.providers))
            pref_label = preferred.value if hasattr(preferred, "value") else str(preferred)
            logger.warning(
                "ModelRouter: Preferred provider '%s' unavailable, falling back to '%s'",
                pref_label,
                self._primary,
            )

        if self._primary:
            logger.info("ModelRouter: Primary provider = %s", self._primary)
        else:
            logger.warning("ModelRouter: No LLM providers available!")

    @property
    def primary_provider(self) -> BaseLLMProvider | None:
        """The default provider (used when no override is specified)."""
        if self._primary:
            return self.providers.get(self._primary)
        return None

    def get_provider(self, provider_name: str) -> BaseLLMProvider | None:
        """Get a specific provider by name."""
        return self.providers.get(provider_name)

    def has_provider(self, provider_name: str) -> bool:
        """Check if a provider is available."""
        return provider_name in self.providers

    @property
    def available_models(self) -> list[dict[str, str]]:
        """List all available models across providers."""
        from vastian.config import MODEL_CATALOG

        models = []
        for provider_name, tiers in MODEL_CATALOG.items():
            if provider_name in self.providers:
                for tier, (prov, model_id) in tiers.items():
                    models.append(
                        {
                            "provider": prov,
                            "model": model_id,
                            "tier": tier,
                            "available": True,
                        }
                    )
            else:
                for tier, (prov, model_id) in tiers.items():
                    models.append(
                        {
                            "provider": prov,
                            "model": model_id,
                            "tier": tier,
                            "available": False,
                        }
                    )
        return models

    def resolve_for_agent(
        self, provider_override: str | None = None, model_override: str | None = None
    ) -> tuple[BaseLLMProvider | None, str | None]:
        """
        Resolve which provider and model to use for a given agent.

        Returns (provider_instance, model_name).
        Falls back gracefully: override -> primary -> None.
        """
        # If agent specifies a provider override, use that
        if provider_override and provider_override in self.providers:
            provider = self.providers[provider_override]
            model = model_override  # agent's specific model (or None for provider default)
            return provider, model

        # Otherwise use the primary provider
        primary = self.primary_provider
        if primary is None:
            return None, None

        return primary, model_override


def create_provider(settings: Settings) -> BaseLLMProvider:
    """Factory function to create the appropriate LLM provider.

    For backwards compatibility, returns a single BaseLLMProvider.
    For the full multi-provider experience, use ModelRouter directly.
    """
    if settings.llm_provider == LLMProvider.AUTO:
        # Auto-detect: prefer Ollama (local-first), then OpenAI, then Anthropic
        if settings.openai_api_key:
            return _get_llm_provider("openai_llm")(settings)
        elif settings.anthropic_api_key:
            return _get_llm_provider("anthropic_llm")(settings)
        else:
            import os

            if os.environ.get("OLLAMA_MODEL"):
                return _get_llm_provider("ollama_llm")(settings)
            raise ValueError(
                "No LLM provider configured. Set OPENAI_API_KEY, "
                "ANTHROPIC_API_KEY, or OLLAMA_MODEL."
            )
    elif settings.llm_provider == LLMProvider.OPENAI:
        return _get_llm_provider("openai_llm")(settings)
    elif settings.llm_provider == LLMProvider.ANTHROPIC:
        return _get_llm_provider("anthropic_llm")(settings)
    elif settings.llm_provider == LLMProvider.OLLAMA:
        return _get_llm_provider("ollama_llm")(settings)
    else:
        raise ValueError(f"Unsupported LLM provider: {settings.llm_provider}")
