"""Enhanced configuration system for PyGEAI Orchestration.

This module provides a comprehensive configuration management system with support for:
- Environment variables with .env file loading
- Configuration profiles (dev, prod, test)
- YAML/JSON configuration files
- Validation and type safety
- Configuration merging and inheritance
"""

import os
import json
from enum import Enum
from pathlib import Path
from typing import Any, Dict, Optional, Union
from pydantic import BaseModel, Field, field_validator


class ConfigProfile(str, Enum):
    """Configuration profile types."""

    DEVELOPMENT = "development"
    PRODUCTION = "production"
    TEST = "test"
    CUSTOM = "custom"


class LogLevel(str, Enum):
    """Logging levels."""

    DEBUG = "DEBUG"
    INFO = "INFO"
    WARNING = "WARNING"
    ERROR = "ERROR"
    CRITICAL = "CRITICAL"


class CacheConfig(BaseModel):
    """Cache configuration settings."""

    enabled: bool = Field(default=True, description="Enable caching")
    max_size: int = Field(default=1000, description="Maximum cache size")
    ttl: int = Field(default=3600, description="Cache TTL in seconds")
    backend: str = Field(default="memory", description="Cache backend type")

    @field_validator("max_size")
    @classmethod
    def validate_max_size(cls, v: int) -> int:
        """Validate max_size is positive."""
        if v <= 0:
            raise ValueError("max_size must be positive")
        return v

    @field_validator("ttl")
    @classmethod
    def validate_ttl(cls, v: int) -> int:
        """Validate TTL is non-negative."""
        if v < 0:
            raise ValueError("ttl must be non-negative")
        return v


class MetricsConfig(BaseModel):
    """Metrics and monitoring configuration."""

    enabled: bool = Field(default=True, description="Enable metrics collection")
    export_interval: int = Field(default=60, description="Metrics export interval in seconds")
    export_format: str = Field(default="json", description="Metrics export format")
    include_labels: bool = Field(default=True, description="Include label dimensions")

    @field_validator("export_interval")
    @classmethod
    def validate_export_interval(cls, v: int) -> int:
        """Validate export interval is positive."""
        if v <= 0:
            raise ValueError("export_interval must be positive")
        return v


class ExecutionConfig(BaseModel):
    """Pattern execution configuration."""

    max_iterations: int = Field(default=10, description="Maximum pattern iterations")
    timeout: int = Field(default=300, description="Execution timeout in seconds")
    retry_attempts: int = Field(default=3, description="Number of retry attempts")
    retry_delay: float = Field(default=1.0, description="Retry delay in seconds")
    parallel_workers: int = Field(default=4, description="Number of parallel workers")

    @field_validator("max_iterations", "retry_attempts", "parallel_workers")
    @classmethod
    def validate_positive(cls, v: int) -> int:
        """Validate value is positive."""
        if v <= 0:
            raise ValueError("Value must be positive")
        return v

    @field_validator("timeout")
    @classmethod
    def validate_timeout(cls, v: int) -> int:
        """Validate timeout is non-negative."""
        if v < 0:
            raise ValueError("timeout must be non-negative")
        return v


class OrchestrationConfig(BaseModel):
    """Main orchestration configuration."""

    profile: ConfigProfile = Field(
        default=ConfigProfile.DEVELOPMENT,
        description="Configuration profile"
    )
    log_level: LogLevel = Field(default=LogLevel.INFO, description="Logging level")
    debug: bool = Field(default=False, description="Enable debug mode")
    cache: CacheConfig = Field(default_factory=CacheConfig, description="Cache settings")
    metrics: MetricsConfig = Field(default_factory=MetricsConfig, description="Metrics settings")
    execution: ExecutionConfig = Field(
        default_factory=ExecutionConfig,
        description="Execution settings"
    )
    custom: Dict[str, Any] = Field(default_factory=dict, description="Custom configuration")

    @classmethod
    def from_env(cls, prefix: str = "PYGEAI_") -> "OrchestrationConfig":
        """Create configuration from environment variables.

        Args:
            prefix: Environment variable prefix

        Returns:
            OrchestrationConfig instance
        """
        config_data: Dict[str, Any] = {}

        profile = os.getenv(f"{prefix}PROFILE")
        if profile:
            config_data["profile"] = profile

        log_level = os.getenv(f"{prefix}LOG_LEVEL")
        if log_level:
            config_data["log_level"] = log_level

        debug = os.getenv(f"{prefix}DEBUG")
        if debug:
            config_data["debug"] = debug.lower() in ("true", "1", "yes")

        cache_enabled = os.getenv(f"{prefix}CACHE_ENABLED")
        if cache_enabled:
            config_data.setdefault("cache", {})
            config_data["cache"]["enabled"] = cache_enabled.lower() in ("true", "1", "yes")

        cache_size = os.getenv(f"{prefix}CACHE_MAX_SIZE")
        if cache_size:
            config_data.setdefault("cache", {})
            config_data["cache"]["max_size"] = int(cache_size)

        metrics_enabled = os.getenv(f"{prefix}METRICS_ENABLED")
        if metrics_enabled:
            config_data.setdefault("metrics", {})
            config_data["metrics"]["enabled"] = metrics_enabled.lower() in ("true", "1", "yes")

        max_iterations = os.getenv(f"{prefix}MAX_ITERATIONS")
        if max_iterations:
            config_data.setdefault("execution", {})
            config_data["execution"]["max_iterations"] = int(max_iterations)

        timeout = os.getenv(f"{prefix}TIMEOUT")
        if timeout:
            config_data.setdefault("execution", {})
            config_data["execution"]["timeout"] = int(timeout)

        return cls(**config_data)

    @classmethod
    def from_file(cls, path: Union[str, Path]) -> "OrchestrationConfig":
        """Load configuration from JSON file.

        Args:
            path: Path to configuration file

        Returns:
            OrchestrationConfig instance

        Raises:
            FileNotFoundError: If file doesn't exist
            ValueError: If file format is invalid
        """
        file_path = Path(path)
        if not file_path.exists():
            raise FileNotFoundError(f"Configuration file not found: {path}")

        with open(file_path, "r") as f:
            data = json.load(f)

        return cls(**data)

    def to_file(self, path: Union[str, Path]) -> None:
        """Save configuration to JSON file.

        Args:
            path: Path to save configuration
        """
        file_path = Path(path)
        file_path.parent.mkdir(parents=True, exist_ok=True)

        with open(file_path, "w") as f:
            json.dump(self.model_dump(), f, indent=2)

    def merge(self, other: "OrchestrationConfig") -> "OrchestrationConfig":
        """Merge with another configuration.

        Args:
            other: Configuration to merge

        Returns:
            New merged configuration
        """
        self_dict = self.model_dump()
        other_dict = other.model_dump()

        merged = self._deep_merge(self_dict, other_dict)
        return OrchestrationConfig(**merged)

    @staticmethod
    def _deep_merge(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
        """Deep merge two dictionaries.

        Args:
            base: Base dictionary
            override: Override dictionary

        Returns:
            Merged dictionary
        """
        result = base.copy()

        for key, value in override.items():
            if key in result and isinstance(result[key], dict) and isinstance(value, dict):
                result[key] = OrchestrationConfig._deep_merge(result[key], value)
            else:
                result[key] = value

        return result

    def get_profile_config(self) -> "OrchestrationConfig":
        """Get profile-specific configuration.

        Returns:
            Configuration with profile-specific defaults
        """
        if self.profile == ConfigProfile.PRODUCTION:
            base_dict = self.model_dump()
            profile_overrides = {
                "log_level": LogLevel.WARNING,
                "debug": False,
                "execution": {
                    "max_iterations": 20,
                    "timeout": 600,
                    "retry_attempts": 5
                }
            }
            merged = self._deep_merge(base_dict, profile_overrides)
            return OrchestrationConfig(**merged)
        elif self.profile == ConfigProfile.TEST:
            base_dict = self.model_dump()
            profile_overrides = {
                "log_level": LogLevel.DEBUG,
                "debug": True,
                "cache": {"enabled": False},
                "metrics": {"enabled": False},
                "execution": {
                    "max_iterations": 5,
                    "timeout": 10
                }
            }
            merged = self._deep_merge(base_dict, profile_overrides)
            return OrchestrationConfig(**merged)
        else:
            return self


class ConfigManager:
    """Configuration manager for global configuration access."""

    _instance: Optional["ConfigManager"] = None
    _config: Optional[OrchestrationConfig] = None

    def __new__(cls) -> "ConfigManager":
        """Ensure singleton instance."""
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    @classmethod
    def initialize(
        cls,
        config: Optional[OrchestrationConfig] = None,
        config_file: Optional[Union[str, Path]] = None,
        from_env: bool = True
    ) -> "ConfigManager":
        """Initialize configuration manager.

        Args:
            config: Configuration instance
            config_file: Path to configuration file
            from_env: Load from environment variables

        Returns:
            ConfigManager instance
        """
        instance = cls()

        if config:
            instance._config = config
        elif config_file:
            instance._config = OrchestrationConfig.from_file(config_file)
            instance._config = instance._config.get_profile_config()
        elif from_env:
            instance._config = OrchestrationConfig.from_env()
            instance._config = instance._config.get_profile_config()
        else:
            instance._config = OrchestrationConfig()
            instance._config = instance._config.get_profile_config()

        return instance

    @classmethod
    def get_config(cls) -> OrchestrationConfig:
        """Get current configuration.

        Returns:
            Current configuration

        Raises:
            RuntimeError: If not initialized
        """
        instance = cls()
        if instance._config is None:
            instance._config = OrchestrationConfig()
        return instance._config

    @classmethod
    def set_config(cls, config: OrchestrationConfig) -> None:
        """Set configuration.

        Args:
            config: Configuration to set
        """
        instance = cls()
        instance._config = config

    @classmethod
    def reset(cls) -> None:
        """Reset configuration to default."""
        instance = cls()
        instance._config = None
