"""
LLM Provider Factory and Configuration Management.
[AC-ASA-14, AC-ASA-15, AC-ASA-16, AC-ASA-17, AC-ASA-18] LLM provider management.

Design pattern: Factory pattern for pluggable LLM providers.
"""

import json
import logging
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import redis

from app.core.config import get_settings
from app.services.llm.base import LLMClient, LLMConfig
from app.services.llm.openai_client import OpenAIClient

logger = logging.getLogger(__name__)

LLM_CONFIG_FILE = Path("config/llm_config.json")
LLM_CONFIG_REDIS_KEY = "ai_service:config:llm"


@dataclass
class LLMProviderInfo:
    """Information about an LLM provider."""
    name: str
    display_name: str
    description: str
    config_schema: dict[str, Any]


LLM_PROVIDERS: dict[str, LLMProviderInfo] = {
    "openai": LLMProviderInfo(
        name="openai",
        display_name="OpenAI",
        description="OpenAI GPT 系列模型 (GPT-4, GPT-3.5 等)",
        config_schema={
            "type": "object",
            "properties": {
                "api_key": {
                    "type": "string",
                    "title": "API Key",
                    "description": "API Key",
                    "required": True,
                },
                "base_url": {
                    "type": "string",
                    "title": "API Base URL",
                    "description": "API Base URL",
                    "default": "https://api.openai.com/v1",
                },
                "model": {
                    "type": "string",
                    "title": "模型名称",
                    "description": "模型名称",
                    "default": "gpt-4o-mini",
                },
                "max_tokens": {
                    "type": "integer",
                    "title": "最大输出 Token 数",
                    "description": "最大输出 Token 数",
                    "default": 2048,
                },
                "temperature": {
                    "type": "number",
                    "title": "温度参数",
                    "description": "温度参数 (0-2)",
                    "default": 0.7,
                    "minimum": 0,
                    "maximum": 2,
                },
                "timeout_seconds": {
                    "type": "integer",
                    "title": "请求超时(秒)",
                    "description": "LLM 请求超时时间（秒）",
                    "default": 60,
                    "minimum": 5,
                    "maximum": 180,
                },
                "max_retries": {
                    "type": "integer",
                    "title": "最大重试次数",
                    "description": "请求失败后的最大重试次数",
                    "default": 3,
                    "minimum": 0,
                    "maximum": 10,
                },
            },
            "required": ["api_key"],
        },
    ),
    "ollama": LLMProviderInfo(
        name="ollama",
        display_name="Ollama",
        description="Ollama 本地模型 (Llama, Qwen 等)",
        config_schema={
            "type": "object",
            "properties": {
                "base_url": {
                    "type": "string",
                    "title": "Ollama API 地址",
                    "description": "Ollama API 地址",
                    "default": "http://localhost:11434/v1",
                },
                "model": {
                    "type": "string",
                    "title": "模型名称",
                    "description": "模型名称",
                    "default": "llama3.2",
                },
                "max_tokens": {
                    "type": "integer",
                    "title": "最大输出 Token 数",
                    "description": "最大输出 Token 数",
                    "default": 2048,
                },
                "temperature": {
                    "type": "number",
                    "title": "温度参数",
                    "description": "温度参数 (0-2)",
                    "default": 0.7,
                    "minimum": 0,
                    "maximum": 2,
                },
            },
            "required": [],
        },
    ),
    "deepseek": LLMProviderInfo(
        name="deepseek",
        display_name="DeepSeek",
        description="DeepSeek 大模型 (deepseek-chat, deepseek-coder)",
        config_schema={
            "type": "object",
            "properties": {
                "api_key": {
                    "type": "string",
                    "title": "API Key",
                    "description": "DeepSeek API Key",
                    "required": True,
                },
                "base_url": {
                    "type": "string",
                    "title": "API Base URL",
                    "description": "API Base URL",
                    "default": "https://api.deepseek.com/v1",
                },
                "model": {
                    "type": "string",
                    "title": "模型名称",
                    "description": "模型名称 (deepseek-chat, deepseek-coder)",
                    "default": "deepseek-chat",
                },
                "max_tokens": {
                    "type": "integer",
                    "title": "最大输出 Token 数",
                    "description": "最大输出 Token 数",
                    "default": 2048,
                },
                "temperature": {
                    "type": "number",
                    "title": "温度参数",
                    "description": "温度参数 (0-2)",
                    "default": 0.7,
                    "minimum": 0,
                    "maximum": 2,
                },
            },
            "required": ["api_key"],
        },
    ),
    "azure": LLMProviderInfo(
        name="azure",
        display_name="Azure OpenAI",
        description="Azure OpenAI 服务",
        config_schema={
            "type": "object",
            "properties": {
                "api_key": {
                    "type": "string",
                    "title": "API Key",
                    "description": "API Key",
                    "required": True,
                },
                "base_url": {
                    "type": "string",
                    "title": "Azure Endpoint",
                    "description": "Azure Endpoint",
                    "required": True,
                },
                "model": {
                    "type": "string",
                    "title": "部署名称",
                    "description": "部署名称",
                    "required": True,
                },
                "api_version": {
                    "type": "string",
                    "title": "API 版本",
                    "description": "API 版本",
                    "default": "2024-02-15-preview",
                },
                "max_tokens": {
                    "type": "integer",
                    "title": "最大输出 Token 数",
                    "description": "最大输出 Token 数",
                    "default": 2048,
                },
                "temperature": {
                    "type": "number",
                    "title": "温度参数",
                    "description": "温度参数 (0-2)",
                    "default": 0.7,
                    "minimum": 0,
                    "maximum": 2,
                },
            },
            "required": ["api_key", "base_url", "model"],
        },
    ),
}


class LLMProviderFactory:
    """
    Factory for creating LLM clients.
    [AC-ASA-14, AC-ASA-15] Dynamic provider creation.
    """

    @classmethod
    def get_providers(cls) -> list[LLMProviderInfo]:
        """Get all registered LLM providers."""
        return list(LLM_PROVIDERS.values())

    @classmethod
    def get_provider_info(cls, name: str) -> LLMProviderInfo | None:
        """Get provider info by name."""
        return LLM_PROVIDERS.get(name)

    @classmethod
    def create_client(
        cls,
        provider: str,
        config: dict[str, Any],
    ) -> LLMClient:
        """
        Create an LLM client for the specified provider.
        [AC-ASA-15] Factory method for client creation.

        Args:
            provider: Provider name (openai, ollama, azure)
            config: Provider configuration

        Returns:
            LLMClient instance

        Raises:
            ValueError: If provider is not supported
        """
        if provider not in LLM_PROVIDERS:
            raise ValueError(f"Unsupported LLM provider: {provider}")

        if provider in ("openai", "ollama", "azure", "deepseek"):
            return OpenAIClient(
                api_key=config.get("api_key"),
                base_url=config.get("base_url"),
                model=config.get("model"),
                default_config=LLMConfig(
                    model=config.get("model", "gpt-4o-mini"),
                    max_tokens=config.get("max_tokens", 2048),
                    temperature=config.get("temperature", 0.7),
                    timeout_seconds=config.get("timeout_seconds", 60),
                    max_retries=config.get("max_retries", 3),
                ),
            )

        raise ValueError(f"Unsupported LLM provider: {provider}")


class LLMConfigManager:
    """
    Manager for LLM configuration.
    [AC-ASA-16, AC-ASA-17, AC-ASA-18] Configuration management with hot-reload and persistence.
    """

    def __init__(self):
        from app.core.config import get_settings

        settings = get_settings()
        self._settings = settings
        self._redis_client: redis.Redis | None = None

        self._current_provider: str = settings.llm_provider
        self._current_config: dict[str, Any] = {
            "api_key": settings.llm_api_key,
            "base_url": settings.llm_base_url,
            "model": settings.llm_model,
            "max_tokens": settings.llm_max_tokens,
            "temperature": settings.llm_temperature,
            "timeout_seconds": settings.llm_timeout_seconds,
            "max_retries": settings.llm_max_retries,
        }
        self._client: LLMClient | None = None

        self._load_from_redis()
        self._load_from_file()

    def _load_from_redis(self) -> None:
        """Load configuration from Redis if exists."""
        try:
            if not self._settings.redis_enabled:
                return
            self._redis_client = redis.from_url(
                self._settings.redis_url,
                encoding="utf-8",
                decode_responses=True,
            )
            saved_raw = self._redis_client.get(LLM_CONFIG_REDIS_KEY)
            if not saved_raw:
                return
            saved = json.loads(saved_raw)
            self._current_provider = saved.get("provider", self._current_provider)
            saved_config = saved.get("config", {})
            if saved_config:
                self._current_config.update(saved_config)
            logger.info(f"[AC-ASA-16] Loaded LLM config from Redis: provider={self._current_provider}")
        except Exception as e:
            logger.warning(f"[AC-ASA-16] Failed to load LLM config from Redis: {e}")

    def _save_to_redis(self) -> None:
        """Save configuration to Redis."""
        try:
            if not self._settings.redis_enabled:
                return
            if self._redis_client is None:
                self._redis_client = redis.from_url(
                    self._settings.redis_url,
                    encoding="utf-8",
                    decode_responses=True,
                )
            self._redis_client.set(
                LLM_CONFIG_REDIS_KEY,
                json.dumps({
                    "provider": self._current_provider,
                    "config": self._current_config,
                }, ensure_ascii=False),
            )
            logger.info(f"[AC-ASA-16] Saved LLM config to Redis: provider={self._current_provider}")
        except Exception as e:
            logger.warning(f"[AC-ASA-16] Failed to save LLM config to Redis: {e}")

    def _load_from_redis(self) -> None:
        """Load configuration from Redis if exists."""
        try:
            if not self._settings.redis_enabled:
                return
            self._redis_client = redis.from_url(
                self._settings.redis_url,
                encoding="utf-8",
                decode_responses=True,
            )
            saved_raw = self._redis_client.get(LLM_CONFIG_REDIS_KEY)
            if not saved_raw:
                return
            saved = json.loads(saved_raw)
            self._current_provider = saved.get("provider", self._current_provider)
            saved_config = saved.get("config", {})
            if saved_config:
                self._current_config.update(saved_config)
            logger.info(f"[AC-ASA-16] Loaded LLM config from Redis: provider={self._current_provider}")
        except Exception as e:
            logger.warning(f"[AC-ASA-16] Failed to load LLM config from Redis: {e}")

    def _load_from_file(self) -> None:
        """Load configuration from file if exists."""
        try:
            if LLM_CONFIG_FILE.exists():
                with open(LLM_CONFIG_FILE, encoding='utf-8') as f:
                    saved = json.load(f)
                    self._current_provider = saved.get("provider", self._current_provider)
                    saved_config = saved.get("config", {})
                    if saved_config:
                        self._current_config.update(saved_config)
                    logger.info(f"[AC-ASA-16] Loaded LLM config from file: provider={self._current_provider}")
        except Exception as e:
            logger.warning(f"[AC-ASA-16] Failed to load LLM config from file: {e}")

    def _save_to_file(self) -> None:
        """Save configuration to file."""
        try:
            LLM_CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
            with open(LLM_CONFIG_FILE, 'w', encoding='utf-8') as f:
                json.dump({
                    "provider": self._current_provider,
                    "config": self._current_config,
                }, f, indent=2, ensure_ascii=False)
            logger.info(f"[AC-ASA-16] Saved LLM config to file: provider={self._current_provider}")
        except Exception as e:
            logger.error(f"[AC-ASA-16] Failed to save LLM config to file: {e}")

    def get_current_config(self) -> dict[str, Any]:
        """Get current LLM configuration."""
        return {
            "provider": self._current_provider,
            "config": self._current_config.copy(),
        }

    async def update_config(
        self,
        provider: str,
        config: dict[str, Any],
    ) -> bool:
        """
        Update LLM configuration.
        [AC-ASA-16] Hot-reload configuration with persistence.

        Args:
            provider: Provider name
            config: New configuration

        Returns:
            True if update successful
        """
        if provider not in LLM_PROVIDERS:
            raise ValueError(f"Unsupported LLM provider: {provider}")

        provider_info = LLM_PROVIDERS[provider]
        validated_config = self._validate_config(provider_info, config)

        if self._client:
            await self._client.close()
            self._client = None

        self._current_provider = provider
        self._current_config = validated_config

        self._save_to_redis()
        self._save_to_file()

        logger.info(f"[AC-ASA-16] LLM config updated: provider={provider}")
        return True

    def _validate_config(
        self,
        provider_info: LLMProviderInfo,
        config: dict[str, Any],
    ) -> dict[str, Any]:
        """Validate configuration against provider schema."""
        schema_props = provider_info.config_schema.get("properties", {})
        required_fields = provider_info.config_schema.get("required", [])

        validated = {}
        for key, prop_schema in schema_props.items():
            if key in config:
                validated[key] = config[key]
            elif "default" in prop_schema:
                validated[key] = prop_schema["default"]
            elif key in required_fields:
                raise ValueError(f"Missing required config: {key}")
        return validated

    def get_client(self) -> LLMClient:
        """Get or create LLM client with current config."""
        if self._client is None:
            self._client = LLMProviderFactory.create_client(
                self._current_provider,
                self._current_config,
            )
        return self._client

    async def test_connection(
        self,
        test_prompt: str = "你好，请简单介绍一下自己。",
        provider: str | None = None,
        config: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """
        Test LLM connection.
        [AC-ASA-17, AC-ASA-18] Connection testing.

        Args:
            test_prompt: Test prompt to send
            provider: Optional provider to test (uses current if not specified)
            config: Optional config to test (uses current if not specified)

        Returns:
            Test result with success status, response, and metrics
        """
        import time

        test_provider = provider or self._current_provider
        test_config = config if config else self._current_config

        logger.info(f"[AC-ASA-17] Test connection: provider={test_provider}, model={test_config.get('model')}")

        if test_provider not in LLM_PROVIDERS:
            return {
                "success": False,
                "error": f"Unsupported provider: {test_provider}",
            }

        try:
            client = LLMProviderFactory.create_client(test_provider, test_config)

            start_time = time.time()
            response = await client.generate(
                messages=[{"role": "user", "content": test_prompt}],
            )
            latency_ms = (time.time() - start_time) * 1000

            await client.close()

            return {
                "success": True,
                "response": response.content,
                "latency_ms": round(latency_ms, 2),
                "prompt_tokens": response.usage.get("prompt_tokens", 0),
                "completion_tokens": response.usage.get("completion_tokens", 0),
                "total_tokens": response.usage.get("total_tokens", 0),
                "model": response.model,
                "message": f"连接成功，模型: {response.model}",
            }

        except Exception as e:
            logger.error(f"[AC-ASA-18] LLM test failed: {e}")
            return {
                "success": False,
                "error": str(e),
                "message": f"连接失败: {str(e)}",
            }

    async def close(self) -> None:
        """Close the current client."""
        if self._client:
            await self._client.close()
            self._client = None


_llm_config_manager: LLMConfigManager | None = None


def get_llm_config_manager() -> LLMConfigManager:
    """Get or create LLM config manager instance."""
    global _llm_config_manager
    if _llm_config_manager is None:
        _llm_config_manager = LLMConfigManager()
    return _llm_config_manager
