#!/usr/bin/env python3
"""
Security Validator for Tree-sitter Analyzer

Provides unified security validation framework inspired by code-index-mcp's
ValidationHelper but enhanced for tree-sitter analyzer's requirements.
"""

import os
import re
from pathlib import Path
from typing import Optional, Tuple

from ..exceptions import SecurityError
from ..utils import log_debug, log_warning
from .boundary_manager import ProjectBoundaryManager
from .regex_checker import RegexSafetyChecker


class SecurityValidator:
    """
    Unified security validation framework.
    
    This class provides comprehensive security validation for file paths,
    regex patterns, and other user inputs to prevent security vulnerabilities.
    
    Features:
    - Multi-layer path traversal protection
    - Project boundary enforcement
    - ReDoS attack prevention
    - Input sanitization
    """

    def __init__(self, project_root: Optional[str] = None) -> None:
        """
        Initialize security validator.
        
        Args:
            project_root: Optional project root directory for boundary checks
        """
        self.boundary_manager = (
            ProjectBoundaryManager(project_root) if project_root else None
        )
        self.regex_checker = RegexSafetyChecker()
        
        log_debug(f"SecurityValidator initialized with project_root: {project_root}")

    def validate_file_path(
        self, file_path: str, base_path: Optional[str] = None
    ) -> Tuple[bool, str]:
        """
        Validate file path with comprehensive security checks.
        
        Implements multi-layer defense against path traversal attacks
        and ensures file access stays within project boundaries.
        
        Args:
            file_path: File path to validate
            base_path: Optional base path for relative path validation
            
        Returns:
            Tuple of (is_valid, error_message)
            
        Example:
            >>> validator = SecurityValidator("/project/root")
            >>> is_valid, error = validator.validate_file_path("src/main.py")
            >>> assert is_valid
        """
        try:
            # Layer 1: Basic input validation
            if not file_path or not isinstance(file_path, str):
                return False, "File path must be a non-empty string"
            
            # Layer 2: Null byte injection check
            if "\x00" in file_path:
                log_warning(f"Null byte detected in file path: {file_path}")
                return False, "File path contains null bytes"
            
            # Layer 3: Windows drive letter check (before absolute path check)
            if len(file_path) > 1 and file_path[1] == ":":
                return False, "Windows drive letters are not allowed"

            # Layer 4: Absolute path rejection
            if os.path.isabs(file_path):
                return False, "Absolute file paths are not allowed"
            
            # Layer 5: Path normalization and traversal check
            norm_path = os.path.normpath(file_path)
            if "..\\" in norm_path or "../" in norm_path or norm_path.startswith(".."):
                log_warning(f"Path traversal attempt detected: {file_path}")
                return False, "Directory traversal not allowed"
            
            # Layer 6: Project boundary validation
            if self.boundary_manager and base_path:
                if not self.boundary_manager.is_within_project(
                    os.path.join(base_path, norm_path)
                ):
                    return False, "Access denied. File path must be within project directory"
            
            # Layer 7: Symbolic link check (if file exists)
            if base_path:
                full_path = os.path.join(base_path, norm_path)
                if os.path.exists(full_path) and os.path.islink(full_path):
                    log_warning(f"Symbolic link detected: {full_path}")
                    return False, "Symbolic links are not allowed"
            
            log_debug(f"File path validation passed: {file_path}")
            return True, ""
            
        except Exception as e:
            log_warning(f"File path validation error: {e}")
            return False, f"Validation error: {str(e)}"

    def validate_directory_path(
        self, dir_path: str, must_exist: bool = True
    ) -> Tuple[bool, str]:
        """
        Validate directory path for security and existence.
        
        Args:
            dir_path: Directory path to validate
            must_exist: Whether directory must exist
            
        Returns:
            Tuple of (is_valid, error_message)
        """
        try:
            # Basic validation using file path validator
            is_valid, error = self.validate_file_path(dir_path)
            if not is_valid:
                return False, error
            
            # Check if path exists and is directory
            if must_exist:
                if not os.path.exists(dir_path):
                    return False, f"Directory does not exist: {dir_path}"
                
                if not os.path.isdir(dir_path):
                    return False, f"Path is not a directory: {dir_path}"
            
            log_debug(f"Directory path validation passed: {dir_path}")
            return True, ""
            
        except Exception as e:
            log_warning(f"Directory path validation error: {e}")
            return False, f"Validation error: {str(e)}"

    def validate_regex_pattern(self, pattern: str) -> Tuple[bool, str]:
        """
        Validate regex pattern for ReDoS attack prevention.
        
        Args:
            pattern: Regex pattern to validate
            
        Returns:
            Tuple of (is_valid, error_message)
        """
        return self.regex_checker.validate_pattern(pattern)

    def sanitize_input(self, user_input: str, max_length: int = 1000) -> str:
        """
        Sanitize user input by removing dangerous characters.
        
        Args:
            user_input: Input string to sanitize
            max_length: Maximum allowed length
            
        Returns:
            Sanitized input string
            
        Raises:
            SecurityError: If input is too long or contains dangerous content
        """
        if not isinstance(user_input, str):
            raise SecurityError("Input must be a string")
        
        if len(user_input) > max_length:
            raise SecurityError(f"Input too long: {len(user_input)} > {max_length}")
        
        # Remove null bytes and control characters
        sanitized = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', '', user_input)
        
        # Log if sanitization occurred
        if sanitized != user_input:
            log_warning("Input sanitization performed")
        
        return sanitized

    def validate_glob_pattern(self, pattern: str) -> Tuple[bool, str]:
        """
        Validate glob pattern for safe file matching.
        
        Args:
            pattern: Glob pattern to validate
            
        Returns:
            Tuple of (is_valid, error_message)
        """
        try:
            # Basic input validation
            if not pattern or not isinstance(pattern, str):
                return False, "Pattern must be a non-empty string"
            
            # Check for dangerous patterns
            dangerous_patterns = [
                "..",  # Path traversal
                "//",  # Double slashes
                "\\\\",  # Double backslashes
            ]
            
            for dangerous in dangerous_patterns:
                if dangerous in pattern:
                    return False, f"Dangerous pattern detected: {dangerous}"
            
            # Validate length
            if len(pattern) > 500:
                return False, "Pattern too long"
            
            log_debug(f"Glob pattern validation passed: {pattern}")
            return True, ""
            
        except Exception as e:
            log_warning(f"Glob pattern validation error: {e}")
            return False, f"Validation error: {str(e)}"
