"""
Security validation module for LogicPwn Exploit Engine.
Provides secure validation and sanitization for session state updates,
template variables, and expression evaluation.
"""

import logging
import re
from dataclasses import dataclass
from typing import Any

logger = logging.getLogger(__name__)


@dataclass
class SecurityValidationResult:
    """Result of security validation."""

    is_valid: bool
    sanitized_data: dict[str, Any]
    violations: list[str]
    risk_level: str  # LOW, MEDIUM, HIGH, CRITICAL


class SecurityValidator:
    """Comprehensive security validator for exploit engine operations."""

    # Maximum sizes to prevent DoS attacks
    MAX_STRING_LENGTH = 10000
    MAX_DICT_DEPTH = 10
    MAX_DICT_SIZE = 1000
    MAX_LIST_LENGTH = 1000

    # Critical patterns that should cause complete rejection
    CRITICAL_PATTERNS = [
        r"__[a-zA-Z_]+__",  # Python dunder methods
        r"eval\s*\(",  # eval() calls
        r"exec\s*\(",  # exec() calls
        r"import\s+",  # import statements
        r"from\s+\w+\s+import",  # from...import statements
        r"subprocess",  # subprocess module
        r"os\.",  # os module calls
        r"sys\.",  # sys module calls
        r"open\s*\(",  # file operations
        r"compile\s*\(",  # code compilation
        r"globals\s*\(",  # globals access
        r"locals\s*\(",  # locals access
        r"vars\s*\(",  # vars access
        r"dir\s*\(",  # dir access
        r"getattr\s*\(",  # dynamic attribute access
        r"setattr\s*\(",  # dynamic attribute setting
        r"delattr\s*\(",  # dynamic attribute deletion
        r"hasattr\s*\(",  # attribute checking
        r"callable\s*\(",  # callable checking
        r"javascript:",  # JavaScript protocol
        r"vbscript:",  # VBScript protocol
        r"data:",  # Data protocol
        r"file:",  # File protocol
    ]

    # Patterns that should be sanitized but not rejected entirely
    SANITIZABLE_PATTERNS = [
        r"<script",  # XSS attempts - can be sanitized
        r"<.*?>",  # HTML tags - can be sanitized
    ]

    # Safe characters for template variable names
    SAFE_TEMPLATE_VAR_PATTERN = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")

    # Safe JSONPath patterns
    SAFE_JSONPATH_PATTERN = re.compile(r"^[\w\.\[\]\'\"]+$")

    def __init__(self):
        """Initialize the security validator."""
        self.compiled_critical_patterns = [
            re.compile(pattern, re.IGNORECASE) for pattern in self.CRITICAL_PATTERNS
        ]
        self.compiled_sanitizable_patterns = [
            re.compile(pattern, re.IGNORECASE) for pattern in self.SANITIZABLE_PATTERNS
        ]

    def validate_session_state_update(
        self, current_state: dict[str, Any], update_data: dict[str, Any]
    ) -> SecurityValidationResult:
        """
        Validate and sanitize session state updates to prevent injection attacks.

        Args:
            current_state: Current session state
            update_data: Data to be merged into session state

        Returns:
            SecurityValidationResult with validation status and sanitized data
        """
        violations = []
        sanitized_data = {}
        risk_level = "LOW"

        # Check for dangerous patterns in keys and values
        for key, value in update_data.items():
            # Validate key
            key_violations = self._validate_key(key)
            if key_violations:
                violations.extend(key_violations)
                risk_level = "HIGH"
                continue

            # Validate and sanitize value
            sanitized_value, value_violations, value_risk = (
                self._validate_and_sanitize_value(value)
            )

            if value_violations:
                violations.extend([f"Key '{key}': {v}" for v in value_violations])
                if value_risk == "CRITICAL":
                    risk_level = "CRITICAL"
                elif value_risk == "HIGH" and risk_level not in ["CRITICAL"]:
                    risk_level = "HIGH"
                elif value_risk == "MEDIUM" and risk_level not in ["CRITICAL", "HIGH"]:
                    risk_level = "MEDIUM"

            # Only add safe data
            if not value_violations or value_risk in ["LOW", "MEDIUM"]:
                sanitized_data[key] = sanitized_value

        # Check total size constraints
        if len(sanitized_data) > self.MAX_DICT_SIZE:
            violations.append(
                f"Session state size exceeds maximum ({self.MAX_DICT_SIZE} keys)"
            )
            risk_level = "HIGH"

        is_valid = risk_level in ["LOW", "MEDIUM"] and len(violations) == 0

        return SecurityValidationResult(
            is_valid=is_valid,
            sanitized_data=sanitized_data,
            violations=violations,
            risk_level=risk_level,
        )

    def validate_template_variable(
        self, var_name: str, var_value: Any
    ) -> tuple[bool, str]:
        """
        Validate template variable name and value for safe substitution.

        Args:
            var_name: Variable name to validate
            var_value: Variable value to validate

        Returns:
            Tuple of (is_valid, reason)
        """
        # Validate variable name against safe pattern
        if not self.SAFE_TEMPLATE_VAR_PATTERN.match(var_name):
            return False, f"Invalid template variable name: {var_name}"

        # Check for dangerous patterns in variable name
        for pattern in self.compiled_critical_patterns:
            if pattern.search(var_name):
                return (
                    False,
                    f"Dangerous pattern detected in template variable name: {var_name}",
                )

        # Additional check for dunder methods in variable names
        if var_name.startswith("__") and var_name.endswith("__"):
            return (
                False,
                f"Dangerous dunder method in template variable name: {var_name}",
            )

        # Validate variable value
        if isinstance(var_value, str):
            if len(var_value) > self.MAX_STRING_LENGTH:
                return (
                    False,
                    f"Template variable value too long: {len(var_value)} chars",
                )

            # Check for critical patterns in value
            for pattern in self.compiled_critical_patterns:
                if pattern.search(var_value):
                    return (
                        False,
                        f"Dangerous pattern detected in template variable: {var_name}",
                    )

        return True, ""

    def sanitize_template_variables(self, variables: dict[str, Any]) -> dict[str, Any]:
        """
        Sanitize template variables for safe substitution.

        Args:
            variables: Dictionary of template variables

        Returns:
            Sanitized variables dictionary
        """
        sanitized = {}

        for name, value in variables.items():
            # Check if variable name is safe (reject dangerous names entirely)
            if name.startswith("__") and name.endswith("__"):
                logger.warning(f"Skipping unsafe template variable name: {name}")
                continue

            # Check for critical patterns in name
            name_has_critical_pattern = any(
                pattern.search(name) for pattern in self.compiled_critical_patterns
            )
            if name_has_critical_pattern:
                logger.warning(
                    f"Skipping template variable with dangerous pattern in name: {name}"
                )
                continue

            # Check if name matches safe pattern
            if not self.SAFE_TEMPLATE_VAR_PATTERN.match(name):
                logger.warning(
                    f"Skipping template variable with invalid name format: {name}"
                )
                continue

            # Sanitize the value
            if isinstance(value, str):
                # Check for critical patterns in value (reject completely)
                value_has_critical_pattern = any(
                    pattern.search(value) for pattern in self.compiled_critical_patterns
                )
                if value_has_critical_pattern:
                    logger.warning(
                        f"Skipping unsafe template variable: Dangerous pattern detected in template variable: {name}"
                    )
                    continue

                # Check for sanitizable patterns and clean them
                sanitized_value = str(value)
                for pattern in self.compiled_sanitizable_patterns:
                    sanitized_value = pattern.sub("", sanitized_value)

                # Remove potentially dangerous characters but keep the variable
                sanitized_value = re.sub(r'[<>&"\']', "", sanitized_value)
                sanitized[name] = sanitized_value
            else:
                sanitized[name] = value

        return sanitized

    def validate_jsonpath_expression(self, expression: str) -> tuple[bool, str]:
        """
        Validate JSONPath expression for safe evaluation.

        Args:
            expression: JSONPath expression to validate

        Returns:
            Tuple of (is_valid, reason)
        """
        if not expression or not expression.strip():
            return False, "Empty JSONPath expression"

        # Basic length check
        if len(expression) > 500:
            return False, "JSONPath expression too long"

        # Check for dangerous patterns
        for pattern in self.compiled_critical_patterns:
            if pattern.search(expression):
                return False, f"Dangerous pattern detected in JSONPath: {expression}"

        # Validate against safe pattern
        if not self.SAFE_JSONPATH_PATTERN.match(
            expression.replace("$", "").replace("@", "")
        ):
            return False, f"Invalid characters in JSONPath: {expression}"

        return True, ""

    def validate_python_expression(self, expression: str) -> tuple[bool, str]:
        """
        Validate Python expression for safe evaluation.

        Args:
            expression: Python expression to validate

        Returns:
            Tuple of (is_valid, reason)
        """
        if not expression or not expression.strip():
            return False, "Empty Python expression"

        # Basic length check
        if len(expression) > 1000:
            return False, "Python expression too long"

        # Check for dangerous patterns
        for pattern in self.compiled_critical_patterns:
            if pattern.search(expression):
                return (
                    False,
                    f"Dangerous pattern detected in Python expression: {expression}",
                )

        # Additional checks for Python-specific dangers
        dangerous_keywords = ["import", "exec", "eval", "compile", "open", "__"]
        for keyword in dangerous_keywords:
            if keyword in expression:
                return False, f"Dangerous keyword '{keyword}' in Python expression"

        return True, ""

    def _validate_key(self, key: str) -> list[str]:
        """Validate a dictionary key."""
        violations = []

        if not isinstance(key, str):
            violations.append(f"Non-string key: {type(key)}")
            return violations

        if len(key) > 100:
            violations.append(f"Key too long: {len(key)} chars")

        # Check for dangerous patterns in key
        for pattern in self.compiled_critical_patterns:
            if pattern.search(key):
                violations.append(f"Dangerous pattern in key: {key}")
                break

        return violations

    def _validate_and_sanitize_value(
        self, value: Any, depth: int = 0
    ) -> tuple[Any, list[str], str]:
        """Validate and sanitize a value recursively."""
        violations = []
        risk_level = "LOW"

        if depth > self.MAX_DICT_DEPTH:
            return None, ["Maximum nesting depth exceeded"], "HIGH"

        if isinstance(value, str):
            if len(value) > self.MAX_STRING_LENGTH:
                violations.append(f"String too long: {len(value)} chars")
                risk_level = "MEDIUM"
                # Truncate but keep some data
                value = value[: self.MAX_STRING_LENGTH] + "...[TRUNCATED]"

            # Check for critical patterns in value (complete rejection)
            for pattern in self.compiled_critical_patterns:
                if pattern.search(value):
                    violations.append(f"Critical pattern in string value")
                    risk_level = "HIGH"
                    # Sanitize by removing dangerous content
                    value = pattern.sub("[SANITIZED]", value)

            # Check for sanitizable patterns (sanitize but keep)
            for pattern in self.compiled_sanitizable_patterns:
                if pattern.search(value):
                    violations.append(f"Sanitizable pattern in string value")
                    if risk_level == "LOW":
                        risk_level = "MEDIUM"
                    # Remove the dangerous content but keep the variable
                    value = pattern.sub("", value)

        elif isinstance(value, dict):
            if len(value) > self.MAX_DICT_SIZE:
                violations.append(f"Dictionary too large: {len(value)} keys")
                risk_level = "MEDIUM"

            sanitized_dict = {}
            for k, v in value.items():
                key_violations = self._validate_key(k)
                if key_violations:
                    violations.extend(key_violations)
                    continue

                sanitized_v, v_violations, v_risk = self._validate_and_sanitize_value(
                    v, depth + 1
                )
                violations.extend(v_violations)

                if v_risk == "CRITICAL":
                    risk_level = "CRITICAL"
                elif v_risk == "HIGH" and risk_level not in ["CRITICAL"]:
                    risk_level = "HIGH"

                sanitized_dict[k] = sanitized_v

            value = sanitized_dict

        elif isinstance(value, list):
            if len(value) > self.MAX_LIST_LENGTH:
                violations.append(f"List too long: {len(value)} items")
                risk_level = "MEDIUM"
                value = value[: self.MAX_LIST_LENGTH]

            sanitized_list = []
            for item in value:
                sanitized_item, item_violations, item_risk = (
                    self._validate_and_sanitize_value(item, depth + 1)
                )
                violations.extend(item_violations)

                if item_risk == "CRITICAL":
                    risk_level = "CRITICAL"
                elif item_risk == "HIGH" and risk_level not in ["CRITICAL"]:
                    risk_level = "HIGH"

                sanitized_list.append(sanitized_item)

            value = sanitized_list

        return value, violations, risk_level


# Global security validator instance
security_validator = SecurityValidator()
