#!/usr/bin/env python3
"""
Configuration Processor - handles conversion between user configuration and MCP canonical configuration
Lenient to users, strict to MCP spec
"""

import logging
from copy import deepcopy
from typing import Dict, Any

logger = logging.getLogger(__name__)


class ConfigProcessor:
    """
    Configuration Processor: handles conversion between user configuration and MCP canonical configuration

    Design philosophy:
    1. Lenient to users: allow extra fields, transport optional
    2. Strict to MCP: ensure format fully complies with requirements
    3. Intelligent inference: automatically handle transport field
    """

    # Standard fields supported by MCP client/server
    MCP_REMOTE_FIELDS = {
        "url", "transport", "headers", "timeout", "keep_alive"
    }

    MCP_LOCAL_FIELDS = {
        "command", "args", "env", "working_dir", "timeout"
    }

    # Supported transport types
    VALID_TRANSPORTS = {
        "streamable-http", "sse", "stdio"
    }

    @classmethod
    def process_user_config_for_mcpstore(cls, user_config: Dict[str, Any]) -> Dict[str, Any]:
        """
        Convert user configuration to MCP-compatible configuration

        Args:
            user_config: User's original configuration

        Returns:
            MCP-compatible configuration
        """
        if not isinstance(user_config, dict) or "mcpServers" not in user_config:
            logger.warning("Invalid config format, returning as-is")
            return user_config

        # Deep copy to avoid modifying original configuration
        mcpstore_config = deepcopy(user_config)

        # Process each service
        services_to_remove = []
        for service_name, service_config in mcpstore_config["mcpServers"].items():
            try:
                processed_config = cls._process_single_service(service_config)
                mcpstore_config["mcpServers"][service_name] = processed_config
                logger.debug(f"Successfully processed service '{service_name}' for MCPStore")
            except Exception as e:
                logger.error(f"Failed to process service '{service_name}': {e}")
                # Provide more detailed error information
                if "missing" in str(e).lower():
                    logger.warning(
                        f"Service '{service_name}' has missing required fields - removing from MCPStore config")
                elif "url" in str(e).lower() and "command" in str(e).lower():
                    logger.warning(
                        f"Service '{service_name}' has conflicting url/command fields - removing from MCPStore config")
                else:
                    logger.warning(
                        f"Service '{service_name}' has configuration errors - removing from MCPStore config: {e}")

                services_to_remove.append(service_name)
                continue

        # Remove problematic services
        for service_name in services_to_remove:
            del mcpstore_config["mcpServers"][service_name]

        return mcpstore_config

    @classmethod
    def _process_single_service(cls, service_config: Dict[str, Any]) -> Dict[str, Any]:
        """
        Process single service configuration

        Args:
            service_config: Configuration of single service

        Returns:
            Processed service configuration
        """
        if not isinstance(service_config, dict):
            return service_config

        # Deep copy to avoid modifying original configuration
        processed = deepcopy(service_config)

        # Determine service type
        if "url" in processed:
            # Remote service
            processed = cls._process_remote_service(processed)
        elif "command" in processed:
            # Local service
            processed = cls._process_local_service(processed)
        else:
            logger.warning("Service config missing both 'url' and 'command', keeping as-is")

        return processed

    @classmethod
    def _process_remote_service(cls, config: Dict[str, Any]) -> Dict[str, Any]:
        """
        Process remote service configuration

        Args:
            config: Remote service configuration

        Returns:
            Processed configuration
        """
        # 1. Intelligently infer transport field
        config = cls._infer_transport(config)

        # 2. Clean non-MCP fields (keep user-defined fields in logs)
        user_fields = set(config.keys()) - cls.MCP_REMOTE_FIELDS
        if user_fields:
            logger.debug(f"Removing user-defined fields for MCP: {user_fields}")

        # 3. Keep only MCP supported fields
        mcp_config = {
            key: value for key, value in config.items()
            if key in cls.MCP_REMOTE_FIELDS
        }

        # 4. Ensure required fields exist
        if "url" not in mcp_config:
            raise ValueError("Remote service missing required 'url' field")

        return mcp_config

    @classmethod
    def _process_local_service(cls, config: Dict[str, Any]) -> Dict[str, Any]:
        """
        Process local service configuration

        Args:
            config: Local service configuration

        Returns:
            Processed configuration
        """
        # 1. Remove transport field (local services don't need it)
        if "transport" in config:
            logger.debug("Removing 'transport' field from local service (not needed)")
            config = deepcopy(config)
            del config["transport"]

        # 2. Clean non-MCP fields
        user_fields = set(config.keys()) - cls.MCP_LOCAL_FIELDS
        if user_fields:
            logger.debug(f"Removing user-defined fields for MCP: {user_fields}")

        # 3. Keep only MCP supported fields
        mcp_config = {
            key: value for key, value in config.items()
            if key in cls.MCP_LOCAL_FIELDS
        }

        # 4. Ensure required fields exist
        if "command" not in mcp_config:
            raise ValueError("Local service missing required 'command' field")

        return mcp_config

    @classmethod
    def _infer_transport(cls, config: Dict[str, Any]) -> Dict[str, Any]:
        """
        智能推断transport字段

        Args:
            config: 服务配置

        Returns:
            包含正确transport字段的配置
        """
        config = deepcopy(config)
        url = config.get("url", "")

        # 如果用户已经指定了transport，验证并保留
        if "transport" in config:
            transport = config["transport"]
            if transport in cls.VALID_TRANSPORTS:
                logger.debug(f"Using user-specified transport: {transport}")
                return config
            else:
                logger.warning(f"Invalid transport '{transport}', will auto-infer")
                del config["transport"]

        # 自动推断transport
        if "/sse" in url.lower():
            # URL包含/sse，使用SSE传输
            config["transport"] = "sse"
            logger.debug(f"Auto-inferred transport 'sse' from URL: {url}")
        else:
            # 默认使用streamable-http
            config["transport"] = "streamable-http"
            logger.debug(f"Auto-inferred transport 'streamable-http' for URL: {url}")

        return config

    @classmethod
    def validate_user_config(cls, config: Dict[str, Any]) -> tuple[bool, str]:
        """
        验证用户配置的基本有效性（宽松验证）

        Args:
            config: 用户配置

        Returns:
            (是否有效, 错误信息)
        """
        try:
            # 1. 检查基本结构
            if not isinstance(config, dict):
                return False, "Config must be a dictionary"

            if "mcpServers" not in config:
                return False, "Config missing 'mcpServers' field"

            if not isinstance(config["mcpServers"], dict):
                return False, "'mcpServers' must be a dictionary"

            # 2. 检查每个服务
            for service_name, service_config in config["mcpServers"].items():
                if not isinstance(service_config, dict):
                    return False, f"Service '{service_name}' config must be a dictionary"

                # 检查必要字段
                has_url = "url" in service_config
                has_command = "command" in service_config

                if not has_url and not has_command:
                    return False, f"Service '{service_name}' missing both 'url' and 'command' fields"

                if has_url and has_command:
                    return False, f"Service '{service_name}' cannot have both 'url' and 'command' fields"

            return True, "Config is valid"

        except Exception as e:
            return False, f"Config validation error: {e}"

    @classmethod
    def get_user_friendly_error(cls, mcpstore_error: str) -> str:
        """
        将MCPStore错误转换为用户友好的错误信息

        Args:
            mcpstore_error: MCPStore的原始错误信息

        Returns:
            用户友好的错误信息
        """
        error_lower = mcpstore_error.lower()

        # 配置验证错误
        if "validation errors" in error_lower:
            return "Service configuration has validation errors. This may be due to user-defined fields that are not supported by MCPStore."

        if "field required" in error_lower:
            return "Missing required field. Please ensure your service has either 'url' or 'command' field."

        if "extra inputs are not permitted" in error_lower:
            return "Configuration contains unsupported fields. MCPStore will automatically filter these for MCPStore compatibility."

        if "input should be" in error_lower:
            return "Invalid field value. Please check your service configuration format."

        # HTTP 状态码错误（常见于远端服务连接）
        if "client error" in error_lower and "for url" in error_lower:
            status_hint = ""
            if "404" in error_lower:
                status_hint = "HTTP 404 Not Found"
            elif "400" in error_lower:
                status_hint = "HTTP 400 Bad Request"
            elif "401" in error_lower:
                status_hint = "HTTP 401 Unauthorized"
            elif "403" in error_lower:
                status_hint = "HTTP 403 Forbidden"
            elif "405" in error_lower:
                status_hint = "HTTP 405 Method Not Allowed"
            elif "422" in error_lower:
                status_hint = "HTTP 422 Unprocessable Entity"
            elif "429" in error_lower:
                status_hint = "HTTP 429 Too Many Requests"
            elif "500" in error_lower:
                status_hint = "HTTP 500 Internal Server Error"
            elif "502" in error_lower:
                status_hint = "HTTP 502 Bad Gateway"
            elif "503" in error_lower:
                status_hint = "HTTP 503 Service Unavailable"
            elif "504" in error_lower:
                status_hint = "HTTP 504 Gateway Timeout"
            else:
                status_hint = "HTTP client error"

            return (
                f"Remote service returned {status_hint}. "
                "Please verify the service URL/path/transport or authentication settings."
            )

        # 网络相关错误
        if "getaddrinfo failed" in error_lower:
            return "Cannot resolve the service URL. Please check the URL and network connection."

        if "connection refused" in error_lower:
            return "Connection refused. Please verify the service is running and accessible."

        if "connection closed" in error_lower:
            return "Connection was closed by the service. The service may not be ready or may have crashed."

        if "timeout" in error_lower:
            return "Connection timeout. The service may be slow to respond or unreachable."

        if "connection" in error_lower:
            return "Connection failed. Please verify the service is running and accessible."

        # SSL/TLS 证书相关错误（英文提示）
        if (
                "certificate verify failed" in error_lower
                or "ssl: certificate_verify_failed" in error_lower
                or "certificate has expired" in error_lower
                or ("ssl" in error_lower and "certificate" in error_lower)
                or "sslerror" in error_lower
        ):
            return (
                "SSL certificate verification failed: the certificate is expired or untrusted. "
                "Please update the server certificate or configure a trusted CA bundle in development. "
                "For internal testing, you may temporarily switch to HTTP to diagnose."
            )

        # TLS 握手失败（Node/undici 常见文案）
        if "handshake failure" in error_lower or "sslv3 alert handshake failure" in error_lower:
            return (
                "TLS handshake failed. The upstream may require different TLS versions/ciphers or a proper CA trust store. "
                "Please update Node/OpenSSL/CA bundle, verify the upstream URL, or temporarily test over HTTP in development."
            )

        # Node/undici fetch 错误（服务启动时上游拉取失败）
        if "fetch failed" in error_lower and (
                "undici" in error_lower or "node" in error_lower or "typeerror" in error_lower):
            return (
                "Service failed to fetch upstream data during startup (Node/undici). "
                "Check network connectivity, the upstream URL, and HTTPS/TLS requirements."
            )

        # 文件系统相关错误
        if "no such file" in error_lower or "file not found" in error_lower:
            return "Required file not found. Please ensure all command files exist and are accessible."

        if "permission denied" in error_lower or "access denied" in error_lower:
            return "Permission denied. Please check file permissions and execution rights."

        # 返回原始错误（已经足够友好的情况）
        return mcpstore_error
