"""Configuration manager implementation for MCP Proxy.

This module provides configuration loading from environment variables,
validation, and default value management.
"""

import os
from typing import Optional, Dict, Any
from pydantic import ValidationError

from ..models import ProxyConfig
from ..exceptions import ConfigurationError, InvalidConfigError, MissingConfigError


class ConfigurationManagerImpl:
    """Implementation of configuration management.
    
    Loads configuration from environment variables with fallback to defaults,
    validates all parameters, and provides clear error messages for invalid
    configurations.
    """
    
    # Environment variable names
    ENV_MCP_SERVER_URL = "MCP_SERVER_URL"
    ENV_OAUTH_PROVIDER_URL = "OAUTH_PROVIDER_URL"
    ENV_CLIENT_NAME = "MCP_CLIENT_NAME"
    ENV_SCOPES = "MCP_SCOPES"
    ENV_CONNECTION_TIMEOUT = "MCP_CONNECTION_TIMEOUT"
    ENV_RETRY_ATTEMPTS = "MCP_RETRY_ATTEMPTS"
    ENV_LOG_LEVEL = "MCP_LOG_LEVEL"
    ENV_MAX_BACKOFF_SECONDS = "MCP_MAX_BACKOFF_SECONDS"
    
    def __init__(self):
        """Initialize configuration manager."""
        self._config: Optional[ProxyConfig] = None
    
    async def load(self) -> ProxyConfig:
        """Load configuration from environment variables.
        
        Loads configuration parameters from environment variables with fallback
        to default values for optional parameters. Required parameters must be
        provided via environment variables.
        
        Returns:
            ProxyConfig: Validated configuration object
            
        Raises:
            MissingConfigError: If required configuration is missing
            InvalidConfigError: If configuration validation fails
        """
        # Load required parameters
        mcp_server_url = os.getenv(self.ENV_MCP_SERVER_URL)
        if not mcp_server_url:
            raise MissingConfigError(
                self.ENV_MCP_SERVER_URL,
                details="MCP server URL must be provided via environment variable"
            )
        
        oauth_provider_url = os.getenv(self.ENV_OAUTH_PROVIDER_URL)
        if not oauth_provider_url:
            raise MissingConfigError(
                self.ENV_OAUTH_PROVIDER_URL,
                details="OAuth provider URL must be provided via environment variable"
            )
        
        # Load optional parameters with defaults
        config_dict: Dict[str, Any] = {
            "mcp_server_url": mcp_server_url,
            "oauth_provider_url": oauth_provider_url,
        }
        
        # Client name (optional)
        client_name = os.getenv(self.ENV_CLIENT_NAME)
        if client_name:
            config_dict["client_name"] = client_name
        
        # Scopes (optional, comma-separated)
        scopes_str = os.getenv(self.ENV_SCOPES)
        if scopes_str:
            scopes = [s.strip() for s in scopes_str.split(",") if s.strip()]
            if scopes:
                config_dict["scopes"] = scopes
        
        # Connection timeout (optional)
        timeout_str = os.getenv(self.ENV_CONNECTION_TIMEOUT)
        if timeout_str:
            try:
                config_dict["connection_timeout"] = int(timeout_str)
            except ValueError:
                raise InvalidConfigError(
                    f"Invalid connection timeout: {timeout_str}. Must be an integer.",
                    details={"parameter": self.ENV_CONNECTION_TIMEOUT, "value": timeout_str}
                )
        
        # Retry attempts (optional)
        retry_str = os.getenv(self.ENV_RETRY_ATTEMPTS)
        if retry_str:
            try:
                config_dict["retry_attempts"] = int(retry_str)
            except ValueError:
                raise InvalidConfigError(
                    f"Invalid retry attempts: {retry_str}. Must be an integer.",
                    details={"parameter": self.ENV_RETRY_ATTEMPTS, "value": retry_str}
                )
        
        # Log level (optional)
        log_level = os.getenv(self.ENV_LOG_LEVEL)
        if log_level:
            config_dict["log_level"] = log_level
        
        # Max backoff seconds (optional)
        backoff_str = os.getenv(self.ENV_MAX_BACKOFF_SECONDS)
        if backoff_str:
            try:
                config_dict["max_backoff_seconds"] = int(backoff_str)
            except ValueError:
                raise InvalidConfigError(
                    f"Invalid max backoff seconds: {backoff_str}. Must be an integer.",
                    details={"parameter": self.ENV_MAX_BACKOFF_SECONDS, "value": backoff_str}
                )
        
        # Validate and create config
        try:
            self._config = ProxyConfig(**config_dict)
        except ValidationError as e:
            # Convert Pydantic validation errors to our custom exception
            error_messages = []
            for error in e.errors():
                field = ".".join(str(loc) for loc in error["loc"])
                msg = error["msg"]
                error_messages.append(f"{field}: {msg}")
            
            raise InvalidConfigError(
                f"Configuration validation failed: {'; '.join(error_messages)}",
                details={"validation_errors": e.errors()}
            )
        
        return self._config
    
    def validate(self, config: ProxyConfig) -> bool:
        """Validate a configuration object.
        
        Performs validation on a ProxyConfig object to ensure all parameters
        are valid. This method is useful for validating configurations created
        programmatically rather than loaded from environment variables.
        
        Args:
            config: Configuration object to validate
            
        Returns:
            bool: True if configuration is valid
            
        Raises:
            InvalidConfigError: If configuration is invalid
        """
        try:
            # Pydantic models validate on construction, but we can also
            # trigger validation explicitly
            config.model_validate(config.model_dump())
            return True
        except ValidationError as e:
            error_messages = []
            for error in e.errors():
                field = ".".join(str(loc) for loc in error["loc"])
                msg = error["msg"]
                error_messages.append(f"{field}: {msg}")
            
            raise InvalidConfigError(
                f"Configuration validation failed: {'; '.join(error_messages)}",
                details={"validation_errors": e.errors()}
            )
    
    def get_defaults(self) -> ProxyConfig:
        """Get default configuration values.
        
        Returns a ProxyConfig object with all default values. This is useful
        for documentation and testing purposes. Note that required fields
        (mcp_server_url and oauth_provider_url) are set to placeholder values.
        
        Returns:
            ProxyConfig: Configuration with default values
        """
        return ProxyConfig(
            mcp_server_url="https://example.com/mcp",
            oauth_provider_url="https://oauth.example.com",
            client_name="mcp-proxy-client",
            scopes=["mcp:read", "mcp:write"],
            connection_timeout=30,
            retry_attempts=3,
            log_level="info",
            max_backoff_seconds=60,
        )
    
    def get_current_config(self) -> Optional[ProxyConfig]:
        """Get the currently loaded configuration.
        
        Returns:
            Optional[ProxyConfig]: Current configuration or None if not loaded
        """
        return self._config
