"""Input sanitization and secure configuration handling for trax project.

This module provides comprehensive input sanitization to prevent various
security vulnerabilities including SQL injection, XSS, command injection,
and configuration validation.
"""

import re
import os
from typing import Any, Dict, Optional, Union
from pathlib import Path

from src.logging import get_logger

logger = get_logger(__name__)


class InputSanitizationError(Exception):
    """Raised when input sanitization fails due to security concerns."""
    pass


class ConfigValidationError(Exception):
    """Raised when configuration validation fails."""
    pass


def sanitize_sql_input(input_str: Optional[str]) -> str:
    """Sanitize input to prevent SQL injection attacks.
    
    Args:
        input_str: Input string to sanitize
        
    Returns:
        Sanitized string safe for SQL operations
        
    Raises:
        InputSanitizationError: If critical SQL injection attempt detected
    """
    if input_str is None:
        return ""
    
    input_str = str(input_str)
    
    # Check for critical attacks that should raise errors
    critical_patterns = [
        r"DROP\s+DATABASE",
        r"SHUTDOWN",
        r"xp_cmdshell",
        r"EXEC\s+xp_cmdshell",
    ]
    
    for pattern in critical_patterns:
        if re.search(pattern, input_str, re.IGNORECASE):
            logger.warning(f"Critical SQL injection attempt detected: {input_str}")
            raise InputSanitizationError(f"Critical SQL injection attempt: {pattern}")
    
    # Remove or escape dangerous SQL patterns
    dangerous_patterns = [
        (r"DROP\s+", ""),
        (r"INSERT\s+", ""),
        (r"UPDATE\s+", ""),
        (r"DELETE\s+", ""),
        (r"CREATE\s+", ""),
        (r"ALTER\s+", ""),
        (r";", ""),
        (r"--", ""),
        (r"/\*", ""),
        (r"\*/", ""),
        (r"UNION\s+", ""),
        (r"OR\s+'1'='1", ""),
        (r"OR\s+1=1", ""),
    ]
    
    sanitized = input_str
    for pattern, replacement in dangerous_patterns:
        sanitized = re.sub(pattern, replacement, sanitized, flags=re.IGNORECASE)
    
    # Limit length to prevent buffer overflow
    if len(sanitized) > 1000:
        sanitized = sanitized[:1000]
    
    return sanitized


def sanitize_html_input(input_str: Optional[str]) -> str:
    """Sanitize input to prevent XSS attacks.
    
    Args:
        input_str: Input string to sanitize
        
    Returns:
        Sanitized string safe for HTML output
    """
    if input_str is None:
        return ""
    
    input_str = str(input_str)
    
    # Remove dangerous HTML tags and attributes
    dangerous_patterns = [
        (r"<script[^>]*>.*?</script>", "", re.IGNORECASE | re.DOTALL),
        (r"<iframe[^>]*>.*?</iframe>", "", re.IGNORECASE | re.DOTALL),
        (r"<object[^>]*>.*?</object>", "", re.IGNORECASE | re.DOTALL),
        (r"<embed[^>]*>", "", re.IGNORECASE),
        (r"javascript:", "", re.IGNORECASE),
        (r"on\w+\s*=", "", re.IGNORECASE),
        (r"vbscript:", "", re.IGNORECASE),
        (r"data:", "", re.IGNORECASE),
        # Remove dangerous attributes from safe tags
        (r"<(\w+)[^>]*\s+on\w+\s*=[^>]*>", r"<\1>", re.IGNORECASE),
        (r"<(\w+)[^>]*\s+href\s*=\s*['\"]javascript:", r"<\1>", re.IGNORECASE),
    ]
    
    sanitized = input_str
    for pattern, replacement, flags in dangerous_patterns:
        sanitized = re.sub(pattern, replacement, sanitized, flags=flags)
    
    return sanitized


def sanitize_command_input(input_str: Optional[str]) -> str:
    """Sanitize input to prevent command injection attacks.
    
    Args:
        input_str: Input string to sanitize
        
    Returns:
        Sanitized string safe for command execution
    """
    if input_str is None:
        return ""
    
    input_str = str(input_str)
    
    # Remove command injection patterns
    dangerous_patterns = [
        (r";\s*", ""),
        (r"&&\s*", ""),
        (r"\|\s*", ""),
        (r"`.*?`", ""),
        (r"\$\(.*?\)", ""),
        (r"rm\s+-rf", ""),
        (r"cat\s+/etc", ""),
        (r"wget\s+", ""),
        (r"curl\s+", ""),
    ]
    
    sanitized = input_str
    for pattern, replacement in dangerous_patterns:
        sanitized = re.sub(pattern, replacement, sanitized, flags=re.IGNORECASE)
    
    return sanitized


def sanitize_file_path(input_str: Optional[str]) -> str:
    """Sanitize file path to prevent directory traversal attacks.
    
    Args:
        input_str: Input string to sanitize
        
    Returns:
        Sanitized file path
    """
    if input_str is None:
        return ""
    
    input_str = str(input_str)
    
    # Remove dangerous path patterns
    dangerous_patterns = [
        (r"\.\./", ""),
        (r"\.\.\\", ""),
        (r"/etc/", ""),
        (r"/root/", ""),
        (r"/var/", ""),
        (r"C:\\Windows\\System32", ""),
        (r"~/.ssh", ""),
        (r"~\\\.ssh", ""),
        (r"\.ssh", ""),
    ]
    
    sanitized = input_str
    for pattern, replacement in dangerous_patterns:
        sanitized = re.sub(pattern, replacement, sanitized, flags=re.IGNORECASE)
    
    return sanitized.strip()


def sanitize_config_value(
    value: Any, 
    expected_type: type, 
    min_value: Optional[Union[int, float]] = None,
    max_value: Optional[Union[int, float]] = None
) -> Any:
    """Sanitize and validate configuration values.
    
    Args:
        value: Configuration value to sanitize
        expected_type: Expected type (str, int, float, bool)
        min_value: Minimum allowed value (for numeric types)
        max_value: Maximum allowed value (for numeric types)
        
    Returns:
        Sanitized and validated value
        
    Raises:
        ConfigValidationError: If validation fails
    """
    if value is None:
        raise ConfigValidationError("Configuration value cannot be None")
    
    if expected_type == str:
        if not isinstance(value, str) or not value.strip():
            raise ConfigValidationError("String value cannot be empty")
        return value.strip()
    
    elif expected_type == int:
        try:
            int_value = int(value)
        except (ValueError, TypeError):
            raise ConfigValidationError(f"Cannot convert '{value}' to integer")
        
        if min_value is not None and int_value < min_value:
            raise ConfigValidationError(f"Value {int_value} is below minimum {min_value}")
        if max_value is not None and int_value > max_value:
            raise ConfigValidationError(f"Value {int_value} is above maximum {max_value}")
        
        return int_value
    
    elif expected_type == float:
        try:
            float_value = float(value)
        except (ValueError, TypeError):
            raise ConfigValidationError(f"Cannot convert '{value}' to float")
        
        if min_value is not None and float_value < min_value:
            raise ConfigValidationError(f"Value {float_value} is below minimum {min_value}")
        if max_value is not None and float_value > max_value:
            raise ConfigValidationError(f"Value {float_value} is above maximum {max_value}")
        
        return float_value
    
    elif expected_type == bool:
        if isinstance(value, bool):
            return value
        elif isinstance(value, str):
            if value.lower() in ('true', '1', 'yes', 'on'):
                return True
            elif value.lower() in ('false', '0', 'no', 'off'):
                return False
            else:
                raise ConfigValidationError(f"Cannot convert '{value}' to boolean")
        else:
            raise ConfigValidationError(f"Cannot convert '{value}' to boolean")
    
    else:
        raise ConfigValidationError(f"Unsupported type: {expected_type}")


def validate_config_schema(config: Dict[str, Any], schema: Dict[str, Any]) -> None:
    """Validate configuration against a JSON schema.
    
    Args:
        config: Configuration dictionary to validate
        schema: JSON schema to validate against
        
    Raises:
        ConfigValidationError: If validation fails
    """
    if not isinstance(config, dict):
        raise ConfigValidationError("Configuration must be a dictionary")
    
    # Check required fields
    required_fields = schema.get("required", [])
    for field in required_fields:
        if field not in config:
            raise ConfigValidationError(f"Required field '{field}' is missing")
    
    # Validate properties
    properties = schema.get("properties", {})
    for field_name, field_value in config.items():
        if field_name in properties:
            field_schema = properties[field_name]
            _validate_field(field_name, field_value, field_schema)


def _validate_field(field_name: str, field_value: Any, field_schema: Dict[str, Any]) -> None:
    """Validate a single configuration field.
    
    Args:
        field_name: Name of the field being validated
        field_value: Value of the field
        field_schema: Schema for the field
        
    Raises:
        ConfigValidationError: If validation fails
    """
    expected_type = field_schema.get("type")
    
    if expected_type == "string":
        if not isinstance(field_value, str):
            raise ConfigValidationError(f"Field '{field_name}' must be a string")
    
    elif expected_type == "integer":
        if not isinstance(field_value, int):
            raise ConfigValidationError(f"Field '{field_name}' must be an integer")
    
    elif expected_type == "boolean":
        if not isinstance(field_value, bool):
            raise ConfigValidationError(f"Field '{field_name}' must be a boolean")
    
    elif expected_type == "object":
        if not isinstance(field_value, dict):
            raise ConfigValidationError(f"Field '{field_name}' must be an object")
        
        # Recursively validate nested object
        nested_properties = field_schema.get("properties", {})
        for nested_field_name, nested_field_value in field_value.items():
            if nested_field_name in nested_properties:
                _validate_field(
                    f"{field_name}.{nested_field_name}",
                    nested_field_value,
                    nested_properties[nested_field_name]
                )


def sanitize_search_query(input_str: Optional[str]) -> str:
    """Sanitize search query input.
    
    Args:
        input_str: Search query to sanitize
        
    Returns:
        Sanitized search query
    """
    if input_str is None:
        return ""
    
    input_str = str(input_str)
    
    # Apply SQL sanitization to prevent injection
    sanitized = sanitize_sql_input(input_str)
    
    # Remove additional search-specific dangerous patterns
    dangerous_patterns = [
        (r"OR\s+", ""),
        (r"AND\s+", ""),
        (r"NOT\s+", ""),
    ]
    
    for pattern, replacement in dangerous_patterns:
        sanitized = re.sub(pattern, replacement, sanitized, flags=re.IGNORECASE)
    
    return sanitized


def sanitize_environment_variable(input_str: Optional[str]) -> str:
    """Sanitize environment variable names.
    
    Args:
        input_str: Environment variable name to sanitize
        
    Returns:
        Sanitized environment variable name
    """
    if input_str is None:
        return ""
    
    input_str = str(input_str)
    
    # Remove command injection patterns
    dangerous_patterns = [
        (r";\s*", ""),
        (r"&&\s*", ""),
        (r"\|\s*", ""),
        (r"`.*?`", ""),
        (r"\$\(.*?\)", ""),
    ]
    
    sanitized = input_str
    for pattern, replacement in dangerous_patterns:
        sanitized = re.sub(pattern, replacement, sanitized)
    
    return sanitized
