"""
Configuration Analysis Engine

Analyzes configuration classes to extract field information, types, defaults, and requirements.
Handles both Pydantic v1 and v2 configurations with robust field detection.
"""

import sys
import importlib.util
from typing import Dict, Any, Set, Optional
from pathlib import Path


class ConfigurationAnalyzer:
    """
    Analyzes configuration classes to extract comprehensive field information.
    
    Supports:
    - Pydantic v1 and v2 configurations
    - Type annotation analysis
    - Default value detection
    - Required/optional field classification
    """
    
    def __init__(self, configs_dir: str):
        """
        Initialize the configuration analyzer.
        
        Args:
            configs_dir: Directory containing configuration files
        """
        self.configs_dir = Path(configs_dir)
    
    def load_config_from_python(self, config_path: Path, builder_name: str) -> Dict[str, Any]:
        """
        Load configuration from Python file with robust import handling.
        
        Args:
            config_path: Path to the configuration file
            builder_name: Name of the builder (for class name inference)
            
        Returns:
            Configuration analysis dictionary
        """
        try:
            # Try to import the module directly
            module_name = f"config_{builder_name}_step"
            
            # Add both the configs directory and the project root to sys.path temporarily
            configs_dir_str = str(self.configs_dir)
            project_root_str = str(self.configs_dir.parent.parent.parent)
            
            paths_to_add = []
            if configs_dir_str not in sys.path:
                sys.path.insert(0, configs_dir_str)
                paths_to_add.append(configs_dir_str)
            if project_root_str not in sys.path:
                sys.path.insert(0, project_root_str)
                paths_to_add.append(project_root_str)
            
            try:
                spec = importlib.util.spec_from_file_location(module_name, config_path)
                module = importlib.util.module_from_spec(spec)
                
                # Set up the module's __package__ to help with relative imports
                module.__package__ = 'src.cursus.steps.configs'
                
                sys.modules[module_name] = module  # Add to sys.modules to help with relative imports
                spec.loader.exec_module(module)
            finally:
                # Clean up sys.path
                for path in paths_to_add:
                    if path in sys.path:
                        sys.path.remove(path)
            
            # Look for configuration class (CamelCase name)
            possible_names = [
                f"{builder_name.title().replace('_', '')}Config",
                f"{''.join(word.capitalize() for word in builder_name.split('_'))}Config",
                f"{''.join(word.capitalize() for word in builder_name.split('_'))}StepConfig",  # StepConfig pattern
                f"CurrencyConversionConfig",  # Specific case
                f"DummyTrainingConfig",       # Specific case
                f"BatchTransformStepConfig"   # Specific case
            ]
            
            config_class = None
            config_class_name = None
            
            for name in possible_names:
                if hasattr(module, name):
                    config_class = getattr(module, name)
                    config_class_name = name
                    break
            
            if config_class is None:
                # List all classes in the module for debugging
                classes = [name for name in dir(module) if isinstance(getattr(module, name), type)]
                raise ValueError(f"Configuration class not found in {config_path}. Available classes: {classes}")
            
            # Analyze the configuration class
            return self.analyze_config_class(config_class, config_class_name)
            
        except Exception as e:
            # Return a simplified analysis if we can't load the module
            return {
                'class_name': f"{builder_name}Config",
                'fields': {},
                'required_fields': set(),
                'optional_fields': set(),
                'default_values': {},
                'load_error': str(e)
            }
    
    def analyze_config_class(self, config_class, class_name: str) -> Dict[str, Any]:
        """
        Analyze configuration class to extract comprehensive field information.
        
        Args:
            config_class: The configuration class to analyze
            class_name: Name of the configuration class
            
        Returns:
            Dictionary containing field analysis results
        """
        analysis = {
            'class_name': class_name,
            'fields': {},
            'required_fields': set(),
            'optional_fields': set(),
            'default_values': {}
        }
        
        # Get class annotations (type hints)
        if hasattr(config_class, '__annotations__'):
            for field_name, field_type in config_class.__annotations__.items():
                # Determine if field is optional based on type annotation
                is_optional = self._is_optional_field(field_type, field_name, config_class)
                
                analysis['fields'][field_name] = {
                    'type': str(field_type),
                    'required': not is_optional
                }
                
                if is_optional:
                    analysis['optional_fields'].add(field_name)
                else:
                    analysis['required_fields'].add(field_name)
        
        # Check for default values in class definition and Pydantic Field definitions
        for attr_name in dir(config_class):
            if not attr_name.startswith('_'):
                attr_value = getattr(config_class, attr_name, None)
                if not callable(attr_value):
                    analysis['default_values'][attr_name] = attr_value
                    # If a field has a default value, it's optional
                    if attr_name in analysis['required_fields']:
                        analysis['required_fields'].remove(attr_name)
                        analysis['optional_fields'].add(attr_name)
                        if attr_name in analysis['fields']:
                            analysis['fields'][attr_name]['required'] = False
        
        return analysis
    
    def _is_optional_field(self, field_type, field_name: str, config_class) -> bool:
        """
        Determine if a field is optional based on its type annotation and Field definition.
        
        Supports both Pydantic v1 and v2 field detection with comprehensive type analysis.
        
        Args:
            field_type: The type annotation for the field
            field_name: Name of the field
            config_class: The configuration class
            
        Returns:
            True if the field is optional, False if required
        """
        import typing
        
        # First priority: Check Pydantic field info for definitive answer
        try:
            if hasattr(config_class, 'model_fields'):
                # Pydantic v2 style
                field_info = config_class.model_fields.get(field_name)
                if field_info and hasattr(field_info, 'is_required'):
                    # Use Pydantic's own determination of required/optional
                    return not field_info.is_required()
            elif hasattr(config_class, '__fields__'):
                # Pydantic v1 style
                field_info = config_class.__fields__.get(field_name)
                if field_info and hasattr(field_info, 'required'):
                    return not field_info.required
        except Exception:
            # If Pydantic info fails, fall back to other methods
            pass
        
        # Second priority: Check for Optional[Type] or Union[Type, None] patterns in type annotation
        type_str = str(field_type)
        if 'Optional[' in type_str or 'Union[' in type_str:
            # Handle Optional[Type] which is Union[Type, None]
            if hasattr(typing, 'get_origin') and hasattr(typing, 'get_args'):
                origin = typing.get_origin(field_type)
                if origin is typing.Union:
                    args = typing.get_args(field_type)
                    # Check if None is one of the union types
                    if type(None) in args:
                        return True
            # Fallback string-based check
            elif 'NoneType' in type_str or ', None' in type_str or 'None]' in type_str:
                return True
        
        # Third priority: Check if the field has a class-level default value
        if hasattr(config_class, field_name):
            default_value = getattr(config_class, field_name)
            # If it's not a callable (method) and not a Field descriptor, it's a default
            if not callable(default_value):
                return True
        
        # If none of the above, assume required
        return False
    
    def get_configuration_schema(self, config_analysis: Dict[str, Any]) -> Dict[str, Any]:
        """
        Extract configuration schema in a standardized format.
        
        Args:
            config_analysis: Result from analyze_config_class
            
        Returns:
            Standardized configuration schema
        """
        return {
            'configuration': {
                'required': list(config_analysis.get('required_fields', set())),
                'optional': list(config_analysis.get('optional_fields', set())),
                'fields': config_analysis.get('fields', {}),
                'defaults': config_analysis.get('default_values', {})
            }
        }
