"""
Parser configuration and options management
"""
from typing import Dict, Any, Optional, List, Union
import chardet
from .exceptions import ConfigurationError, EncodingError


class ParserConfig:
    """Configuration settings for CutySoup parser."""
    
    # Default configuration values
    DEFAULT_CONFIG = {
        'encoding': 'utf-8',
        'auto_detect_encoding': True,
        'strict_parsing': False,
        'preserve_whitespace': False,
        'self_closing_tags': ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 
                             'link', 'meta', 'param', 'source', 'track', 'wbr'],
        'normalize_attributes': True,
        'case_sensitive_attributes': False,
        'max_tree_depth': 1000,
        'enable_caching': True,
        'prewarm_python_cache': False,
        'cache_size_limit': 100,
        'debug_mode': False,
        'error_handling': 'lenient',  # 'strict', 'lenient', 'ignore'
        'memory_limit_mb': 500,  # Max memory usage in MB
        'timeout_seconds': 30,  # Parsing timeout
    }
    
    def __init__(self, **kwargs):
        """Initialize parser configuration."""
        self._config = self.DEFAULT_CONFIG.copy()
        self.update(**kwargs)
        self._validate_config()
    
    def update(self, **kwargs) -> None:
        """Update configuration settings."""
        for key, value in kwargs.items():
            if key not in self.DEFAULT_CONFIG:
                raise ConfigurationError(f"Unknown configuration option: {key}")
            self._config[key] = value
        
        self._validate_config()
    
    def get(self, key: str, default: Any = None) -> Any:
        """Get configuration value."""
        return self._config.get(key, default)
    
    def set(self, key: str, value: Any) -> None:
        """Set configuration value."""
        if key not in self.DEFAULT_CONFIG:
            raise ConfigurationError(f"Unknown configuration option: {key}")
        self._config[key] = value
        self._validate_config()
    
    def to_dict(self) -> Dict[str, Any]:
        """Return configuration as dictionary."""
        return self._config.copy()
    
    def reset_to_defaults(self) -> None:
        """Reset configuration to default values."""
        self._config = self.DEFAULT_CONFIG.copy()
    
    def _validate_config(self) -> None:
        """Validate configuration settings."""
        # Validate encoding
        encoding = self._config.get('encoding')
        if encoding and not isinstance(encoding, str):
            raise ConfigurationError(f"Encoding must be string, got {type(encoding)}")
        
        # Validate boolean settings
        bool_settings = ['auto_detect_encoding', 'strict_parsing', 'preserve_whitespace',
                        'normalize_attributes', 'case_sensitive_attributes', 'enable_caching',
                        'prewarm_python_cache', 'debug_mode']
        for setting in bool_settings:
            value = self._config.get(setting)
            if value is not None and not isinstance(value, bool):
                raise ConfigurationError(f"{setting} must be boolean, got {type(value)}")
        
        # Validate numeric settings
        max_depth = self._config.get('max_tree_depth')
        if max_depth is not None and (not isinstance(max_depth, int) or max_depth <= 0):
            raise ConfigurationError("max_tree_depth must be positive integer")
        
        cache_size = self._config.get('cache_size_limit')
        if cache_size is not None and (not isinstance(cache_size, int) or cache_size < 0):
            raise ConfigurationError("cache_size_limit must be non-negative integer")
        
        memory_limit = self._config.get('memory_limit_mb')
        if memory_limit is not None and (not isinstance(memory_limit, (int, float)) or memory_limit <= 0):
            raise ConfigurationError("memory_limit_mb must be positive number")
        
        timeout = self._config.get('timeout_seconds')
        if timeout is not None and (not isinstance(timeout, (int, float)) or timeout <= 0):
            raise ConfigurationError("timeout_seconds must be positive number")
        
        # Validate error handling mode
        error_handling = self._config.get('error_handling')
        if error_handling not in ['strict', 'lenient', 'ignore']:
            raise ConfigurationError("error_handling must be 'strict', 'lenient', or 'ignore'")
        
        # Validate self-closing tags list
        self_closing = self._config.get('self_closing_tags')
        if self_closing is not None and not isinstance(self_closing, list):
            raise ConfigurationError("self_closing_tags must be list")
    
    @property
    def encoding(self) -> str:
        """Get encoding setting."""
        return self._config['encoding']
    
    @property
    def auto_detect_encoding(self) -> bool:
        """Get auto-detect encoding setting."""
        return self._config['auto_detect_encoding']
    
    @property
    def strict_parsing(self) -> bool:
        """Get strict parsing setting."""
        return self._config['strict_parsing']
    
    @property
    def preserve_whitespace(self) -> bool:
        """Get preserve whitespace setting."""
        return self._config['preserve_whitespace']
    
    @property
    def debug_mode(self) -> bool:
        """Get debug mode setting."""
        return self._config['debug_mode']


class EncodingDetector:
    """Handles encoding detection and conversion."""
    
    def __init__(self, config: ParserConfig):
        self.config = config
    
    def detect_encoding(self, content: bytes) -> str:
        """Detect encoding of byte content."""
        if not self.config.auto_detect_encoding:
            return self.config.encoding
        
        try:
            # First try declared encoding in HTML
            declared_encoding = self._extract_declared_encoding(content[:1024])
            if declared_encoding:
                return declared_encoding
            
            # Use chardet for detection
            detection = chardet.detect(content)
            if detection and detection['encoding']:
                confidence = detection.get('confidence', 0)
                if confidence > 0.7:  # High confidence threshold
                    return detection['encoding']
            
            # Fallback to configured encoding
            return self.config.encoding
            
        except Exception as e:
            if self.config.debug_mode:
                raise EncodingError(f"Encoding detection failed: {e}")
            return self.config.encoding
    
    def _extract_declared_encoding(self, content: bytes) -> Optional[str]:
        """Extract encoding from HTML meta tags."""
        try:
            # Convert to string for regex search (use latin-1 for byte compatibility)
            content_str = content.decode('latin-1', errors='ignore').lower()
            
            # Look for charset in meta tags
            import re
            
            # Pattern for <meta charset="...">
            charset_pattern = r'<meta[^>]+charset[=\s]*["\']?([^"\'>\s]+)'
            match = re.search(charset_pattern, content_str)
            if match:
                return match.group(1)
            
            # Pattern for <meta http-equiv="content-type" content="...charset=...">
            content_type_pattern = r'<meta[^>]+http-equiv[=\s]*["\']?content-type["\']?[^>]+content[=\s]*["\'][^"\']*charset=([^"\';\s]+)'
            match = re.search(content_type_pattern, content_str)
            if match:
                return match.group(1)
            
            return None
            
        except Exception:
            return None
    
    def convert_to_unicode(self, content: Union[str, bytes]) -> str:
        """Convert content to unicode string."""
        if isinstance(content, str):
            return content
        
        if not isinstance(content, bytes):
            raise EncodingError(f"Content must be str or bytes, got {type(content)}")
        
        try:
            encoding = self.detect_encoding(content)
            return content.decode(encoding, errors='replace')
        except Exception as e:
            raise EncodingError(f"Failed to convert content to unicode: {e}")
    
    def get_declared_encoding(self, content: Union[str, bytes]) -> Optional[str]:
        """Get the encoding declared in HTML content."""
        if isinstance(content, str):
            content = content.encode('utf-8')
        
        return self._extract_declared_encoding(content)


class ParserFeatures:
    """Manages parser feature flags and capabilities."""
    
    AVAILABLE_FEATURES = {
        'css_selectors': True,
        'tree_modification': True,
        'text_processing': True,
        'encoding_detection': True,
        'performance_optimization': True,
        'memory_management': True,
        'thread_safety': False,  # Not yet implemented
        'streaming_parser': False,  # Future feature
        'async_support': False,  # Future feature
    }
    
    def __init__(self, config: ParserConfig):
        self.config = config
        self._enabled_features = self.AVAILABLE_FEATURES.copy()
    
    def is_enabled(self, feature: str) -> bool:
        """Check if a feature is enabled."""
        return self._enabled_features.get(feature, False)
    
    def enable_feature(self, feature: str) -> None:
        """Enable a parser feature."""
        if feature not in self.AVAILABLE_FEATURES:
            raise ConfigurationError(f"Unknown feature: {feature}")
        
        if not self.AVAILABLE_FEATURES[feature]:
            raise ConfigurationError(f"Feature {feature} is not available in this version")
        
        self._enabled_features[feature] = True
    
    def disable_feature(self, feature: str) -> None:
        """Disable a parser feature."""
        if feature not in self.AVAILABLE_FEATURES:
            raise ConfigurationError(f"Unknown feature: {feature}")
        
        self._enabled_features[feature] = False
    
    def get_enabled_features(self) -> List[str]:
        """Get list of enabled features."""
        return [feature for feature, enabled in self._enabled_features.items() if enabled]
    
    def get_available_features(self) -> List[str]:
        """Get list of available features."""
        return [feature for feature, available in self.AVAILABLE_FEATURES.items() if available]


class PerformanceSettings:
    """Performance-related configuration settings."""
    
    def __init__(self, config: ParserConfig):
        self.config = config
    
    @property
    def enable_caching(self) -> bool:
        """Whether to enable internal caching."""
        return self.config.get('enable_caching', True)
    
    @property
    def cache_size_limit(self) -> int:
        """Maximum number of items in cache."""
        return self.config.get('cache_size_limit', 100)
    
    @property
    def memory_limit_mb(self) -> float:
        """Memory limit in megabytes."""
        return self.config.get('memory_limit_mb', 500)
    
    @property
    def timeout_seconds(self) -> float:
        """Parsing timeout in seconds."""
        return self.config.get('timeout_seconds', 30)
    
    @property
    def max_tree_depth(self) -> int:
        """Maximum allowed tree depth."""
        return self.config.get('max_tree_depth', 1000)
    
    def should_use_optimization(self, optimization_type: str) -> bool:
        """Check if specific optimization should be used."""
        optimizations = {
            'selector_caching': self.enable_caching,
            'memory_pooling': self.memory_limit_mb > 100,
            'lazy_evaluation': not self.config.get('strict_parsing', False),
        }
        
        return optimizations.get(optimization_type, False)


# Global default configuration instance
default_config = ParserConfig()


def get_default_config() -> ParserConfig:
    """Get the default parser configuration."""
    return default_config


def set_default_config(**kwargs) -> None:
    """Update the default parser configuration."""
    global default_config
    default_config.update(**kwargs)


def create_custom_config(**kwargs) -> ParserConfig:
    """Create a custom parser configuration."""
    return ParserConfig(**kwargs)
