"""
Configuration management for workflow testing framework.

This module provides centralized configuration management for the testing
framework, including test parameters, thresholds, and execution settings.
"""

import os
from pathlib import Path
from typing import Dict, Any, Optional, List
from dataclasses import dataclass, field
import json

from precipgen.utils import get_logger


@dataclass
class WorkflowTestConfig:
    """Configuration for a specific workflow test."""
    workflow_name: str
    enabled: bool = True
    timeout_seconds: int = 300
    retry_attempts: int = 3
    validation_threshold: float = 0.85
    performance_threshold_seconds: float = 60.0
    memory_limit_mb: int = 1024
    custom_parameters: Dict[str, Any] = field(default_factory=dict)


@dataclass
class PropertyTestConfig:
    """Configuration for property-based testing."""
    max_examples: int = 100
    deadline_ms: int = 10000
    stateful_step_count: int = 50
    suppress_health_check: List[str] = field(default_factory=list)
    database_file: Optional[str] = None


@dataclass
class PerformanceTestConfig:
    """Configuration for performance testing."""
    enable_profiling: bool = False
    memory_monitoring: bool = True
    cpu_monitoring: bool = True
    benchmark_iterations: int = 5
    warmup_iterations: int = 2
    performance_log_file: Optional[str] = None


class TestConfig:
    """
    Centralized configuration management for workflow testing framework.
    
    Manages test parameters, thresholds, and execution settings for all
    workflow tests with support for environment-based overrides.
    """
    
    def __init__(self, config_file: Optional[str] = None):
        """
        Initialize TestConfig with optional configuration file.
        
        Args:
            config_file: Optional path to JSON configuration file
        """
        self.logger = get_logger("workflow_testing.test_config")
        self.config_file = config_file
        self._config = self._load_default_config()
        
        if config_file:
            self._load_config_file(config_file)
        
        self._apply_environment_overrides()
        
        self.logger.info("TestConfig initialized")
    
    @classmethod
    def from_file(cls, config_file: str) -> 'TestConfig':
        """
        Create TestConfig instance from configuration file.
        
        Args:
            config_file: Path to configuration file
            
        Returns:
            TestConfig instance
        """
        return cls(config_file=config_file)
    
    def _load_default_config(self) -> Dict[str, Any]:
        """Load default configuration values."""
        return {
            "base_output_dir": "./precipgen_output",
            "test_data_dir": "./tests/data",
            "log_level": "INFO",
            "log_file": None,
            "parallel_execution": False,
            "max_workers": 4,
            
            # Workflow configurations
            "workflows": {
                "directory_organization": WorkflowTestConfig(
                    workflow_name="directory_organization",
                    timeout_seconds=120,
                    validation_threshold=0.90
                ),
                "data_acquisition": WorkflowTestConfig(
                    workflow_name="data_acquisition",
                    timeout_seconds=600,  # Longer for network operations
                    validation_threshold=0.90
                ),
                "data_analysis": WorkflowTestConfig(
                    workflow_name="data_analysis",
                    timeout_seconds=300,
                    validation_threshold=0.85
                ),
                "simulation": WorkflowTestConfig(
                    workflow_name="simulation",
                    timeout_seconds=900,  # Longer for large simulations
                    validation_threshold=0.85,
                    memory_limit_mb=2048
                ),
                "validation": WorkflowTestConfig(
                    workflow_name="validation",
                    timeout_seconds=300,
                    validation_threshold=0.85
                ),
                "integration": WorkflowTestConfig(
                    workflow_name="integration",
                    timeout_seconds=1200,  # Longer for end-to-end tests
                    validation_threshold=0.85
                ),
                "performance": WorkflowTestConfig(
                    workflow_name="performance",
                    timeout_seconds=600,
                    validation_threshold=0.80
                ),
                "error_handling": WorkflowTestConfig(
                    workflow_name="error_handling",
                    timeout_seconds=300,
                    validation_threshold=0.90
                ),
                "output_format": WorkflowTestConfig(
                    workflow_name="output_format",
                    timeout_seconds=300,
                    validation_threshold=0.90
                )
            },
            
            # Property-based testing
            "property_testing": PropertyTestConfig(
                max_examples=100,
                deadline_ms=10000
            ),
            
            # Performance testing
            "performance": PerformanceTestConfig(
                enable_profiling=False,
                memory_monitoring=True,
                benchmark_iterations=5
            ),
            
            # Gold standard testing
            "gold_standard": {
                "station_id": "USC00426128",  # Salt Lake City
                "coordinates": (40.7589, -111.8883),
                "location_name": "Salt Lake City, UT",
                "validation_threshold": 0.85,
                "expected_parameters": 48,  # 12 months × 4 parameters
                "simulation_days": 3650,  # 10 years
                "trend_analysis": True
            },
            
            # Error handling
            "error_handling": {
                "max_retry_attempts": 3,
                "retry_delay_seconds": 1.0,
                "graceful_degradation": True,
                "detailed_error_logging": True
            },
            
            # Test data generation
            "test_data": {
                "min_years": 10,
                "max_years": 50,
                "min_wet_day_frequency": 0.1,
                "max_wet_day_frequency": 0.8,
                "precipitation_range": (0.1, 100.0),
                "parameter_ranges": {
                    "p_ww": (0.0, 1.0),
                    "p_wd": (0.0, 1.0),
                    "alpha": (0.1, 10.0),
                    "beta": (0.1, 100.0)
                }
            }
        }
    
    def _load_config_file(self, config_file: str) -> None:
        """
        Load configuration from JSON file.
        
        Args:
            config_file: Path to configuration file
        """
        config_path = Path(config_file)
        
        if not config_path.exists():
            self.logger.warning(f"Configuration file not found: {config_file}")
            return
        
        try:
            with open(config_path, 'r') as f:
                file_config = json.load(f)
            
            # Merge with default config
            self._deep_merge(self._config, file_config)
            self.logger.info(f"Loaded configuration from: {config_file}")
            
        except (json.JSONDecodeError, IOError) as e:
            self.logger.error(f"Failed to load configuration file {config_file}: {e}")
    
    def _apply_environment_overrides(self) -> None:
        """Apply environment variable overrides to configuration."""
        env_mappings = {
            "PRECIPGEN_TEST_OUTPUT_DIR": ["base_output_dir"],
            "PRECIPGEN_TEST_LOG_LEVEL": ["log_level"],
            "PRECIPGEN_TEST_LOG_FILE": ["log_file"],
            "PRECIPGEN_TEST_PARALLEL": ["parallel_execution"],
            "PRECIPGEN_TEST_MAX_WORKERS": ["max_workers"],
            "PRECIPGEN_TEST_PROPERTY_EXAMPLES": ["property_testing", "max_examples"],
            "PRECIPGEN_TEST_VALIDATION_THRESHOLD": ["workflows", "data_analysis", "validation_threshold"]
        }
        
        for env_var, config_path in env_mappings.items():
            env_value = os.getenv(env_var)
            if env_value is not None:
                try:
                    # Convert string values to appropriate types
                    if env_var.endswith("_PARALLEL"):
                        env_value = env_value.lower() in ("true", "1", "yes")
                    elif env_var.endswith(("_WORKERS", "_EXAMPLES")):
                        env_value = int(env_value)
                    elif env_var.endswith("_THRESHOLD"):
                        env_value = float(env_value)
                    
                    # Set the configuration value
                    self._set_nested_value(self._config, config_path, env_value)
                    self.logger.info(f"Applied environment override: {env_var}={env_value}")
                    
                except (ValueError, TypeError) as e:
                    self.logger.warning(f"Invalid environment value for {env_var}: {env_value} ({e})")
    
    def _deep_merge(self, base_dict: Dict[str, Any], update_dict: Dict[str, Any]) -> None:
        """
        Deep merge update_dict into base_dict.
        
        Args:
            base_dict: Base dictionary to update
            update_dict: Dictionary with updates
        """
        for key, value in update_dict.items():
            if key in base_dict and isinstance(base_dict[key], dict) and isinstance(value, dict):
                self._deep_merge(base_dict[key], value)
            else:
                base_dict[key] = value
    
    def _set_nested_value(self, config_dict: Dict[str, Any], 
                         path: List[str], value: Any) -> None:
        """
        Set a nested configuration value using a path list.
        
        Args:
            config_dict: Configuration dictionary
            path: List of keys representing the path
            value: Value to set
        """
        current = config_dict
        for key in path[:-1]:
            if key not in current:
                current[key] = {}
            current = current[key]
        current[path[-1]] = value
    
    def get_workflow_config(self, workflow_name: str) -> WorkflowTestConfig:
        """
        Get configuration for a specific workflow.
        
        Args:
            workflow_name: Name of the workflow
            
        Returns:
            WorkflowTestConfig object
            
        Raises:
            KeyError: If workflow is not configured
        """
        if workflow_name not in self._config["workflows"]:
            raise KeyError(f"No configuration found for workflow: {workflow_name}")
        
        workflow_data = self._config["workflows"][workflow_name]
        
        # If it's already a WorkflowTestConfig object, return it
        if isinstance(workflow_data, WorkflowTestConfig):
            return workflow_data
        
        # If it's a dictionary (from config updates), convert to WorkflowTestConfig
        if isinstance(workflow_data, dict):
            return WorkflowTestConfig(
                workflow_name=workflow_name,
                enabled=workflow_data.get("enabled", True),
                timeout_seconds=workflow_data.get("timeout_seconds", 300),
                retry_attempts=workflow_data.get("retry_attempts", 3),
                validation_threshold=workflow_data.get("validation_threshold", 0.85),
                performance_threshold_seconds=workflow_data.get("performance_threshold_seconds", 60.0),
                memory_limit_mb=workflow_data.get("memory_limit_mb", 1024),
                custom_parameters=workflow_data.get("custom_parameters", {})
            )
        
        raise ValueError(f"Invalid workflow configuration type for {workflow_name}: {type(workflow_data)}")
    
    def get_workflow_timeout(self, workflow_name: str) -> int:
        """
        Get timeout in seconds for a specific workflow.
        
        Args:
            workflow_name: Name of the workflow
            
        Returns:
            Timeout in seconds
        """
        try:
            workflow_config = self.get_workflow_config(workflow_name)
            return workflow_config.timeout_seconds
        except KeyError:
            # Return default timeout for unknown workflows
            self.logger.warning(f"No timeout configured for workflow '{workflow_name}', using default")
            return 300  # 5 minutes default
    
    def get_property_test_config(self) -> PropertyTestConfig:
        """Get property-based testing configuration."""
        return self._config["property_testing"]
    
    def get_performance_config(self) -> PerformanceTestConfig:
        """Get performance testing configuration."""
        return self._config["performance"]
    
    def get_gold_standard_config(self) -> Dict[str, Any]:
        """Get gold standard testing configuration."""
        return self._config["gold_standard"]
    
    def get_base_output_dir(self) -> str:
        """Get base output directory for test results."""
        return self._config["base_output_dir"]
    
    def get_test_data_dir(self) -> str:
        """Get test data directory."""
        return self._config["test_data_dir"]
    
    def get_log_config(self) -> Dict[str, Any]:
        """Get logging configuration."""
        return {
            "level": self._config["log_level"],
            "log_file": self._config.get("log_file")
        }
    
    def is_parallel_execution_enabled(self) -> bool:
        """Check if parallel test execution is enabled."""
        return self._config.get("parallel_execution", False)
    
    def get_max_workers(self) -> int:
        """Get maximum number of worker processes for parallel execution."""
        return self._config.get("max_workers", 4)
    
    def get_error_handling_config(self) -> Dict[str, Any]:
        """Get error handling configuration."""
        return self._config["error_handling"]
    
    def get_test_data_config(self) -> Dict[str, Any]:
        """Get test data generation configuration."""
        return self._config["test_data"]
    
    def update_config(self, updates: Dict[str, Any]) -> None:
        """
        Update configuration with new values.
        
        Args:
            updates: Dictionary of configuration updates
        """
        self._deep_merge(self._config, updates)
        self.logger.info("Configuration updated")
    
    def save_config(self, output_file: str) -> None:
        """
        Save current configuration to file.
        
        Args:
            output_file: Path to output configuration file
        """
        try:
            # Convert dataclass objects to dictionaries for JSON serialization
            serializable_config = self._make_serializable(self._config)
            
            with open(output_file, 'w') as f:
                json.dump(serializable_config, f, indent=2)
            
            self.logger.info(f"Configuration saved to: {output_file}")
            
        except (IOError, TypeError) as e:
            self.logger.error(f"Failed to save configuration to {output_file}: {e}")
    
    def _make_serializable(self, obj: Any) -> Any:
        """
        Convert configuration objects to JSON-serializable format.
        
        Args:
            obj: Object to convert
            
        Returns:
            JSON-serializable object
        """
        if hasattr(obj, '__dict__'):
            # Convert dataclass to dictionary
            return {k: self._make_serializable(v) for k, v in obj.__dict__.items()}
        elif isinstance(obj, dict):
            return {k: self._make_serializable(v) for k, v in obj.items()}
        elif isinstance(obj, (list, tuple)):
            return [self._make_serializable(item) for item in obj]
        else:
            return obj
    
    def validate_config(self) -> List[str]:
        """
        Validate configuration and return list of issues.
        
        Returns:
            List of validation error messages
        """
        issues = []
        
        # Validate base directories
        base_dir = Path(self._config["base_output_dir"])
        if not base_dir.parent.exists():
            issues.append(f"Parent directory does not exist: {base_dir.parent}")
        
        # Validate workflow configurations
        for workflow_name, workflow_config in self._config["workflows"].items():
            if workflow_config.timeout_seconds <= 0:
                issues.append(f"Invalid timeout for {workflow_name}: {workflow_config.timeout_seconds}")
            
            if not 0.0 <= workflow_config.validation_threshold <= 1.0:
                issues.append(f"Invalid validation threshold for {workflow_name}: {workflow_config.validation_threshold}")
        
        # Validate property testing config
        prop_config = self._config["property_testing"]
        if prop_config.max_examples <= 0:
            issues.append(f"Invalid max_examples: {prop_config.max_examples}")
        
        # Validate gold standard config
        gold_config = self._config["gold_standard"]
        coords = gold_config["coordinates"]
        if not (-90 <= coords[0] <= 90 and -180 <= coords[1] <= 180):
            issues.append(f"Invalid coordinates: {coords}")
        
        return issues
