# Generated by Copilot
# Maintained by Dinesh Kumar C (https://github.com/Dinesh210805/blender-mcp)
# Forked from original work by Siddharth Ahuja (https://github.com/ahujasid/blender-mcp)

"""Validation utilities for Blender MCP."""

import re
import os
from pathlib import Path
from typing import Any, Dict, List, Optional, Union
from urllib.parse import urlparse

from ..exceptions import ValidationError, SecurityError


def validate_object_name(name: str) -> str:
    """Validate and sanitize object name."""
    if not name or not isinstance(name, str):
        raise ValidationError("Object name must be a non-empty string")
    
    # Remove potentially dangerous characters
    sanitized = re.sub(r'[^\w\s\-\.]', '', name.strip())
    
    if not sanitized:
        raise ValidationError("Object name contains only invalid characters")
    
    if len(sanitized) > 63:  # Blender object name limit
        sanitized = sanitized[:63]
    
    return sanitized


def validate_file_path(path: Union[str, Path], must_exist: bool = False) -> Path:
    """Validate file path for security and existence."""
    if not path:
        raise ValidationError("File path cannot be empty")
    
    path_obj = Path(path).resolve()
    
    # Check for path traversal attempts
    if ".." in str(path_obj):
        raise SecurityError("Path traversal detected in file path")
    
    # Check if path is within allowed directories (basic check)
    if path_obj.is_absolute():
        # For absolute paths, ensure they're not in sensitive system directories
        sensitive_dirs = ["/etc", "/sys", "/proc", "C:\\Windows", "C:\\System32"]
        for sensitive_dir in sensitive_dirs:
            if str(path_obj).lower().startswith(sensitive_dir.lower()):
                raise SecurityError(f"Access to sensitive directory not allowed: {sensitive_dir}")
    
    if must_exist and not path_obj.exists():
        raise ValidationError(f"File does not exist: {path_obj}")
    
    return path_obj


def validate_url(url: str) -> str:
    """Validate URL format."""
    if not url or not isinstance(url, str):
        raise ValidationError("URL must be a non-empty string")
    
    parsed = urlparse(url)
    if not all([parsed.scheme, parsed.netloc]):
        raise ValidationError("Invalid URL format")
    
    if parsed.scheme not in ['http', 'https']:
        raise ValidationError("Only HTTP and HTTPS URLs are allowed")
    
    return url


def validate_code_safety(code: str) -> bool:
    """Basic safety checks for code execution."""
    if not isinstance(code, str):
        raise ValidationError("Code must be a string")
    
    if len(code) > 10000:  # Arbitrary limit
        raise SecurityError("Code is too long (max 10000 characters)")
    
    # Check for dangerous patterns
    dangerous_patterns = [
        r'import\s+os',
        r'import\s+subprocess',
        r'import\s+sys',
        r'exec\s*\(',
        r'eval\s*\(',
        r'__import__',
        r'open\s*\(',
        r'file\s*\(',
        r'input\s*\(',
        r'raw_input\s*\(',
        r'compile\s*\(',
        r'globals\s*\(',
        r'locals\s*\(',
        r'vars\s*\(',
        r'dir\s*\(',
        r'getattr',
        r'setattr',
        r'delattr',
        r'hasattr',
    ]
    
    for pattern in dangerous_patterns:
        if re.search(pattern, code, re.IGNORECASE):
            raise SecurityError(f"Potentially dangerous code pattern detected: {pattern}")
    
    return True


def validate_asset_params(asset_id: str, asset_type: str, resolution: str) -> Dict[str, str]:
    """Validate asset download parameters."""
    # Validate asset_id
    if not asset_id or not isinstance(asset_id, str):
        raise ValidationError("Asset ID must be a non-empty string")
    
    # Sanitize asset_id (allow alphanumeric, hyphens, underscores)
    if not re.match(r'^[a-zA-Z0-9_\-]+$', asset_id):
        raise ValidationError("Asset ID contains invalid characters")
    
    # Validate asset_type
    valid_asset_types = ['hdris', 'textures', 'models', 'all']
    if asset_type not in valid_asset_types:
        raise ValidationError(f"Asset type must be one of: {', '.join(valid_asset_types)}")
    
    # Validate resolution
    valid_resolutions = ['1k', '2k', '4k', '8k', '16k']
    if resolution not in valid_resolutions:
        raise ValidationError(f"Resolution must be one of: {', '.join(valid_resolutions)}")
    
    return {
        'asset_id': asset_id,
        'asset_type': asset_type,
        'resolution': resolution
    }


def validate_numeric_range(value: Union[int, float], min_val: float, max_val: float, param_name: str) -> Union[int, float]:
    """Validate numeric value is within range."""
    if not isinstance(value, (int, float)):
        raise ValidationError(f"{param_name} must be a number")
    
    if value < min_val or value > max_val:
        raise ValidationError(f"{param_name} must be between {min_val} and {max_val}")
    
    return value


def validate_bbox_condition(bbox: Optional[List[float]]) -> Optional[List[int]]:
    """Validate and process bounding box condition."""
    if bbox is None:
        return None
    
    if not isinstance(bbox, list) or len(bbox) != 3:
        raise ValidationError("Bounding box condition must be a list of 3 numbers")
    
    for i, val in enumerate(bbox):
        if not isinstance(val, (int, float)) or val <= 0:
            raise ValidationError(f"Bounding box value at index {i} must be a positive number")
    
    # Normalize to percentages
    max_val = max(bbox)
    return [int(float(val) / max_val * 100) for val in bbox]


def sanitize_filename(filename: str) -> str:
    """Sanitize filename for safe file operations."""
    if not filename:
        raise ValidationError("Filename cannot be empty")
    
    # Remove path separators and dangerous characters
    sanitized = re.sub(r'[<>:"/\\|?*]', '', filename)
    
    # Remove leading/trailing dots and spaces
    sanitized = sanitized.strip('. ')
    
    if not sanitized:
        raise ValidationError("Filename contains only invalid characters")
    
    # Limit length
    if len(sanitized) > 255:
        name_part, ext = os.path.splitext(sanitized)
        sanitized = name_part[:255-len(ext)] + ext
    
    return sanitized
