from __future__ import annotations

from collections.abc import AsyncIterator
from typing import Any

from app.core.config import settings
from app.llm.registry import ModelRegistry, ModelSpec
from app.llm.types import (
    ChatCompletionResult,
    LLMAuthError,
    LLMClientError,
    ModelInfo,
    ToolCompletionResult,
)


class LLMRouter:
    """Resolve model ID → provider and delegate LLM calls.

    Public methods mirror the historical LLMClient surface so call sites
    can migrate with minimal changes.
    """

    def __init__(self, providers: dict[str, Any] | None = None):
        self._providers: dict[str, Any] = providers or {}

    def resolve_model(self, model_id: str | None) -> ModelSpec:
        return ModelRegistry.resolve(model_id)

    def _get_provider(self, provider_name: str) -> Any:
        if provider_name in self._providers:
            return self._providers[provider_name]
        provider = self._build_provider(provider_name)
        self._providers[provider_name] = provider
        return provider

    def _build_provider(self, provider_name: str) -> Any:
        if provider_name == "openai":
            from app.llm.providers.openai import OpenAIProvider

            return OpenAIProvider()

        if provider_name == "deepseek":
            if not str(settings.DEEPSEEK_API_KEY).strip() and not settings.is_local():
                raise LLMAuthError(
                    "DEEPSEEK_API_KEY is required outside local/dev",
                    status_code=401,
                )
            from app.llm.providers.deepseek import DeepSeekProvider

            return DeepSeekProvider()

        if provider_name == "anthropic":
            if not str(settings.ANTHROPIC_API_KEY).strip() and not settings.is_local():
                raise LLMAuthError(
                    "ANTHROPIC_API_KEY is required outside local/dev",
                    status_code=401,
                )
            from app.llm.providers.anthropic import AnthropicProvider

            return AnthropicProvider()

        if provider_name == "gemini":
            if not str(settings.GEMINI_API_KEY).strip() and not settings.is_local():
                raise LLMAuthError(
                    "GEMINI_API_KEY is required outside local/dev",
                    status_code=401,
                )
            from app.llm.providers.gemini import GeminiProvider

            return GeminiProvider()

        raise LLMClientError(
            f"LLM provider '{provider_name}' is not supported. "
            "Expected one of: openai, deepseek, anthropic, gemini."
        )

    async def complete(
        self,
        messages: list[dict[str, Any]],
        *,
        model: str | None = None,
        response_format: dict[str, Any] | None = None,
    ) -> ChatCompletionResult:
        spec = ModelRegistry.resolve(model)
        provider = self._get_provider(spec.provider)
        return await provider.complete(
            messages,
            model=spec.api_model,
            response_format=response_format,
        )

    async def complete_with_tools(
        self,
        messages: list[dict[str, Any]],
        tools: list[dict[str, Any]],
        *,
        model: str | None = None,
        tool_choice: str | dict[str, Any] | None = "auto",
    ) -> ToolCompletionResult:
        spec = ModelRegistry.resolve(model)
        provider = self._get_provider(spec.provider)
        return await provider.complete_with_tools(
            messages,
            tools,
            model=spec.api_model,
            tool_choice=tool_choice,
        )

    async def complete_json(
        self,
        messages: list[dict[str, Any]],
        *,
        model: str | None = None,
    ) -> dict[str, Any]:
        spec = ModelRegistry.resolve(model)
        provider = self._get_provider(spec.provider)
        return await provider.complete_json(messages, model=spec.api_model)

    async def stream(
        self,
        messages: list[dict[str, Any]],
        *,
        model: str | None = None,
    ) -> AsyncIterator[str]:
        spec = ModelRegistry.resolve(model)
        provider = self._get_provider(spec.provider)
        async for chunk in provider.stream(messages, model=spec.api_model):
            yield chunk

    async def list_models(self) -> list[ModelInfo]:
        merged: list[ModelInfo] = []
        seen: set[str] = set()

        candidates = [
            (
                "openai",
                bool(str(settings.OPENAI_API_KEY).strip()) or settings.is_local(),
            ),
            ("anthropic", bool(str(settings.ANTHROPIC_API_KEY).strip())),
            ("gemini", bool(str(settings.GEMINI_API_KEY).strip())),
            ("deepseek", bool(str(settings.DEEPSEEK_API_KEY).strip())),
        ]

        for name, enabled in candidates:
            if not enabled:
                continue
            try:
                provider = self._get_provider(name)
                for info in await provider.list_models():
                    if info.id in seen:
                        continue
                    seen.add(info.id)
                    merged.append(info)
            except LLMClientError:
                continue

        return merged
