from __future__ import annotations

from typing import Any, Dict, List, Optional
import os

from provider.models import MODELS_BY_PROVIDER, ModelInfo


DEFAULT_CONTEXT_WINDOW = 128000

# provider_type -> env var
_ENV_KEY_MAP: Dict[str, str] = {
    "openai": "OPENAI_API_KEY",
    "anthropic": "ANTHROPIC_API_KEY",
    "minimax": "MINIMAX_API_KEY",
    "glm": "ZHIPU_API_KEY",
    "google": "GEMINI_API_KEY",
}

# Provider 连接默认值（base_url/type 等）
DEFAULT_PROVIDERS: Dict[str, Dict[str, Any]] = {
    "openai": {"type": "openai", "base_url": "https://api.openai.com/v1"},
    "anthropic": {"type": "anthropic", "base_url": "https://api.anthropic.com"},
    "glm": {"type": "glm", "base_url": "https://open.bigmodel.cn/api/coding/paas/v4"},
    "minimax": {"type": "minimax", "base_url": "https://api.minimaxi.com/anthropic"},
    "google": {"type": "google"},
}


class ProviderManager:
    """Global provider+model catalog.

    Responsibilities:
    - Merge provider connection defaults (type/base_url) with config overrides
    - Merge builtin models with config-added models
    - Provide model queries for UI/server/provider clients

    Rules:
    - Config-added models must be specified as "provider/model" and provider must match
    - Violations raise ValueError and should fail startup

    Singleton: yes.
    """

    _instance: Optional["ProviderManager"] = None

    def __init__(self) -> None:
        self._configured = False
        self._default_model: str = ""
        self._models_by_provider: Dict[str, List[ModelInfo]] = {}
        self._models_by_fullname: Dict[str, ModelInfo] = {}

    @classmethod
    def get(cls) -> "ProviderManager":
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    def ensure_configured(self) -> None:
        if not self._configured:
            raise RuntimeError(
                "ProviderManager not configured. Call configure_from_app_config() during server startup."
            )

    @property
    def default_model(self) -> str:
        return self._default_model

    def configure_from_app_config(self, config: Any) -> None:
        self._default_model = getattr(getattr(config, "provider", None), "default", "") or ""

        # Builtin models come from provider.models (static constant table).
        builtin_by_provider = MODELS_BY_PROVIDER


        # Provider configs: merge defaults + user config.
        prov_section = getattr(config, "provider", None)
        user_provs: Dict[str, Any] = getattr(prov_section, "providers", {}) or {}

        merged_provs: Dict[str, Any] = {}
        for name, default_cfg in DEFAULT_PROVIDERS.items():
            base: Dict[str, Any] = {}
            base.update(default_cfg)
            user_cfg = user_provs.get(name)
            if user_cfg is not None:
                if isinstance(user_cfg, dict):
                    base.update(user_cfg)
                else:
                    # exclude_defaults=True: 只取用户显式设置的字段，
                    # 避免 Pydantic 默认值（如 type="openai"）覆盖 DEFAULT_PROVIDERS 的正确值
                    base.update(user_cfg.model_dump(exclude_defaults=True))
            merged_provs[name] = base

        for name, user_cfg in user_provs.items():
            if name in merged_provs:
                continue
            merged_provs[name] = user_cfg if isinstance(user_cfg, dict) else user_cfg.model_dump(exclude_none=True)

        # Convert merged provider configs back to ProviderConfig models for downstream code.
        from core.config import ProviderConfig

        merged_provider_models: Dict[str, ProviderConfig] = {
            name: ProviderConfig(**cfg_dict) for name, cfg_dict in merged_provs.items()
        }
        if prov_section is not None:
            prov_section.providers = merged_provider_models

        models_by_provider: Dict[str, List[ModelInfo]] = {}

        for provider_name, prov_cfg in merged_provider_models.items():
            provider_default_cw = getattr(prov_cfg, "context_window", None)

            # Builtins
            builtin_models: List[Any] = list(builtin_by_provider.get(provider_name, []) or [])
            out: List[ModelInfo] = []
            seen = set()
            for mi in builtin_models:
                info = ModelInfo(
                    provider=mi.provider,
                    type=mi.type,
                    name=mi.name,
                    context_window=getattr(mi, "context_window", None),
                )
                key = f"{info.provider}/{info.name}"
                if key in seen:
                    continue
                out.append(info)
                seen.add(key)

            # Config extras:
            # - allow "model" (bare name) within provider section; normalize to "provider/model"
            # - allow "provider/model" but provider prefix must match the section
            cfg_models = getattr(prov_cfg, "models", None)
            for m in list(cfg_models or []):
                if not isinstance(m, str) or not m.strip():
                    continue

                if "/" in m:
                    prov, name = m.split("/", 1)
                    if prov != provider_name:
                        raise ValueError(
                            f"provider.models 中的模型 provider 前缀必须与所属 provider 匹配: {m} (expected {provider_name}/...)"
                        )
                else:
                    prov, name = provider_name, m

                key = f"{prov}/{name}"
                if key in seen:
                    continue
                out.append(
                    ModelInfo(
                        provider=prov,
                        type="chat",
                        name=name,
                        context_window=provider_default_cw or DEFAULT_CONTEXT_WINDOW,
                    )
                )
                seen.add(key)

            models_by_provider[provider_name] = out

        self._models_by_provider = models_by_provider
        self._models_by_fullname = {
            f"{mi.provider}/{mi.name}": mi
            for _, lst in models_by_provider.items()
            for mi in lst
        }
        self._configured = True

    def resolve_api_key(self, provider_type: str, provider_config: Any) -> Optional[str]:
        """Resolve API key with precedence: config value  env var fallback."""
        api_key = getattr(provider_config, "api_key", None)
        if api_key:
            return api_key
        env_var = _ENV_KEY_MAP.get((provider_type or "").lower())
        if env_var:
            return os.getenv(env_var)
        return None

    def get_client_kwargs(self, provider_name: str, model: str, provider_config: Any) -> Dict[str, Any]:
        """Build kwargs for BaseModelClient construction.

        context_window 优先级：provider config 显式配置 > 内置模型表 > 默认值。
        使用第三方代理时，用户可在 provider 上设置 context_window 覆盖内置值。
        """
        self.ensure_configured()

        provider_type = getattr(provider_config, "type", "")
        api_key = self.resolve_api_key(provider_type, provider_config)

        # 优先使用 provider config 上的 context_window（用户显式覆盖）
        provider_cw = getattr(provider_config, "context_window", None)

        context_window = None
        if provider_cw:
            context_window = provider_cw
        else:
            mi = self.get_model_info(f"{provider_name}/{model}")
            if mi and mi.context_window:
                context_window = mi.context_window

        return {
            "model": model,
            "api_key": api_key,
            "base_url": getattr(provider_config, "base_url", None),
            "context_window": context_window,
        }

    def list_models(self) -> List[Dict[str, Any]]:
        self.ensure_configured()
        out: List[Dict[str, Any]] = []
        for _, lst in self._models_by_provider.items():
            for mi in lst:
                out.append(
                    {
                        "provider": mi.provider,
                        "type": mi.type,
                        "name": mi.name,
                        "context_window": mi.context_window if mi.context_window is not None else DEFAULT_CONTEXT_WINDOW,
                    }
                )
        return out

    def get_model_info(self, full_name: str) -> Optional[ModelInfo]:
        self.ensure_configured()
        return self._models_by_fullname.get(full_name)
