"""
Custom validators for enhanced input validation and security.
"""

import re
import os
import urllib.parse
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.utils.translation import gettext_lazy as _


def validate_hls_url(value):
    """
    Validate HLS stream URL format and security.
    
    Args:
        value (str): URL to validate
        
    Raises:
        ValidationError: If URL is invalid or insecure
    """
    # Basic URL validation
    url_validator = URLValidator()
    try:
        url_validator(value)
    except ValidationError:
        raise ValidationError(_('Invalid URL format.'))
    
    # Parse URL components
    parsed = urllib.parse.urlparse(value)
    
    # Security checks
    if parsed.scheme not in ['http', 'https']:
        raise ValidationError(_('Only HTTP and HTTPS protocols are allowed.'))
    
    # Check for localhost/private IPs in production
    if parsed.hostname:
        if parsed.hostname.lower() in ['localhost', '127.0.0.1', '0.0.0.0']:
            # Allow in development, but warn
            pass
        elif parsed.hostname.startswith('192.168.') or parsed.hostname.startswith('10.'):
            # Private network ranges
            pass
    
    # Validate HLS file extension
    if not (value.endswith('.m3u8') or value.endswith('.m3u')):
        raise ValidationError(_('URL must point to an HLS playlist file (.m3u8 or .m3u).'))
    
    # Check for suspicious patterns
    suspicious_patterns = [
        r'[<>"\']',  # HTML/script injection
        r'javascript:',  # JavaScript protocol
        r'data:',  # Data URLs
        r'file:',  # File protocol
    ]
    
    for pattern in suspicious_patterns:
        if re.search(pattern, value, re.IGNORECASE):
            raise ValidationError(_('URL contains potentially malicious content.'))


def validate_file_path(value):
    """
    Validate file path for security and format.
    
    Args:
        value (str): File path to validate
        
    Raises:
        ValidationError: If path is invalid or insecure
    """
    if not value:
        raise ValidationError(_('File path cannot be empty.'))
    
    # Security checks for path traversal
    if '..' in value:
        raise ValidationError(_('Path traversal attempts are not allowed.'))
    
    if value.startswith('/'):
        # Absolute path - check if it's in allowed directories
        allowed_prefixes = [
            '/tmp/',
            '/var/tmp/',
            '/opt/streams/',
            '/data/streams/',
            '/app/media/',
        ]
        
        if not any(value.startswith(prefix) for prefix in allowed_prefixes):
            raise ValidationError(_('Absolute paths must be in allowed directories.'))
    
    # Check for suspicious characters
    suspicious_chars = ['<', '>', '"', "'", '&', '|', ';', '`', '$']
    if any(char in value for char in suspicious_chars):
        raise ValidationError(_('File path contains invalid characters.'))
    
    # Validate length
    if len(value) > 500:
        raise ValidationError(_('File path is too long (max 500 characters).'))


def validate_directory_path(value):
    """
    Validate directory path for security and format.
    
    Args:
        value (str): Directory path to validate
        
    Raises:
        ValidationError: If path is invalid or insecure
    """
    validate_file_path(value)  # Use file path validation as base
    
    # Additional directory-specific checks
    if value.endswith('/'):
        value = value.rstrip('/')
    
    # Check if path looks like a directory
    if '.' in os.path.basename(value):
        # Might be a file, not a directory
        raise ValidationError(_('Path appears to be a file, not a directory.'))


def validate_json_configuration(value):
    """
    Validate JSON configuration for notification channels.
    
    Args:
        value (dict): Configuration dictionary to validate
        
    Raises:
        ValidationError: If configuration is invalid
    """
    if not isinstance(value, dict):
        raise ValidationError(_('Configuration must be a valid JSON object.'))
    
    # Check for suspicious keys or values
    def check_suspicious_content(obj, path=""):
        if isinstance(obj, dict):
            for key, val in obj.items():
                # Check key names
                if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', key):
                    raise ValidationError(_(f'Invalid configuration key: {key}'))
                
                check_suspicious_content(val, f"{path}.{key}")
        
        elif isinstance(obj, str):
            # Check for script injection
            if re.search(r'<script|javascript:|data:|file:', obj, re.IGNORECASE):
                raise ValidationError(_(f'Suspicious content in configuration: {path}'))
            
            # Check for command injection
            if re.search(r'[;&|`$]', obj):
                raise ValidationError(_(f'Potentially dangerous characters in: {path}'))
    
    check_suspicious_content(value)


def validate_telegram_config(config):
    """
    Validate Telegram notification configuration.
    
    Args:
        config (dict): Telegram configuration
        
    Raises:
        ValidationError: If configuration is invalid
    """
    required_fields = ['bot_token', 'chat_id']
    
    for field in required_fields:
        if field not in config:
            raise ValidationError(_(f'Missing required field: {field}'))
        
        if not config[field]:
            raise ValidationError(_(f'Field {field} cannot be empty'))
    
    # Validate bot token format
    bot_token = config['bot_token']
    if not re.match(r'^\d+:[A-Za-z0-9_-]+$', bot_token):
        raise ValidationError(_('Invalid Telegram bot token format.'))
    
    # Validate chat ID
    chat_id = str(config['chat_id'])
    if not re.match(r'^-?\d+$', chat_id):
        raise ValidationError(_('Invalid Telegram chat ID format.'))


def validate_email_config(config):
    """
    Validate email notification configuration.
    
    Args:
        config (dict): Email configuration
        
    Raises:
        ValidationError: If configuration is invalid
    """
    required_fields = ['smtp_host', 'smtp_port', 'username', 'password']
    
    for field in required_fields:
        if field not in config:
            raise ValidationError(_(f'Missing required field: {field}'))
        
        if not config[field]:
            raise ValidationError(_(f'Field {field} cannot be empty'))
    
    # Validate SMTP host
    smtp_host = config['smtp_host']
    if not re.match(r'^[a-zA-Z0-9.-]+$', smtp_host):
        raise ValidationError(_('Invalid SMTP host format.'))
    
    # Validate SMTP port
    try:
        port = int(config['smtp_port'])
        if not (1 <= port <= 65535):
            raise ValidationError(_('SMTP port must be between 1 and 65535.'))
    except (ValueError, TypeError):
        raise ValidationError(_('SMTP port must be a valid integer.'))
    
    # Validate email format
    from django.core.validators import EmailValidator
    email_validator = EmailValidator()
    try:
        email_validator(config['username'])
    except ValidationError:
        raise ValidationError(_('Invalid email address format.'))


def validate_webhook_config(config):
    """
    Validate webhook notification configuration.
    
    Args:
        config (dict): Webhook configuration
        
    Raises:
        ValidationError: If configuration is invalid
    """
    required_fields = ['url', 'method']
    
    for field in required_fields:
        if field not in config:
            raise ValidationError(_(f'Missing required field: {field}'))
        
        if not config[field]:
            raise ValidationError(_(f'Field {field} cannot be empty'))
    
    # Validate URL
    url_validator = URLValidator()
    try:
        url_validator(config['url'])
    except ValidationError:
        raise ValidationError(_('Invalid webhook URL format.'))
    
    # Validate HTTP method
    allowed_methods = ['GET', 'POST', 'PUT', 'PATCH']
    if config['method'].upper() not in allowed_methods:
        raise ValidationError(_(f'HTTP method must be one of: {", ".join(allowed_methods)}'))
    
    # Validate headers if present
    if 'headers' in config:
        headers = config['headers']
        if not isinstance(headers, dict):
            raise ValidationError(_('Headers must be a valid JSON object.'))
        
        for header_name, header_value in headers.items():
            if not isinstance(header_name, str) or not isinstance(header_value, str):
                raise ValidationError(_('Header names and values must be strings.'))
            
            # Check for suspicious headers
            if header_name.lower() in ['host', 'content-length']:
                raise ValidationError(_(f'Header {header_name} is not allowed.'))


def validate_template_content(value):
    """
    Validate notification template content for security.
    
    Args:
        value (str): Template content to validate
        
    Raises:
        ValidationError: If template contains unsafe content
    """
    if not value or not value.strip():
        raise ValidationError(_('Template content cannot be empty.'))
    
    # Check for script injection
    if re.search(r'<script|javascript:|data:|file:', value, re.IGNORECASE):
        raise ValidationError(_('Template contains potentially malicious content.'))
    
    # Check for command injection patterns
    if re.search(r'[;&|`$]', value):
        raise ValidationError(_('Template contains potentially dangerous characters.'))
    
    # Validate template variables
    template_vars = re.findall(r'\{\{([^}]+)\}\}', value)
    for var in template_vars:
        var = var.strip()
        if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var):
            raise ValidationError(_(f'Invalid template variable: {var}'))
    
    # Check length
    if len(value) > 5000:
        raise ValidationError(_('Template content is too long (max 5000 characters).'))


def validate_confidence_score(value):
    """
    Validate confidence score range.
    
    Args:
        value (float): Confidence score to validate
        
    Raises:
        ValidationError: If score is out of valid range
    """
    if not isinstance(value, (int, float)):
        raise ValidationError(_('Confidence score must be a number.'))
    
    if not (0.0 <= value <= 1.0):
        raise ValidationError(_('Confidence score must be between 0.0 and 1.0.'))


def validate_similarity_threshold(value):
    """
    Validate similarity threshold range.
    
    Args:
        value (float): Similarity threshold to validate
        
    Raises:
        ValidationError: If threshold is out of valid range
    """
    if not isinstance(value, (int, float)):
        raise ValidationError(_('Similarity threshold must be a number.'))
    
    if not (0.0 <= value <= 1.0):
        raise ValidationError(_('Similarity threshold must be between 0.0 and 1.0.'))


def validate_positive_integer(value):
    """
    Validate positive integer values.
    
    Args:
        value (int): Integer to validate
        
    Raises:
        ValidationError: If value is not a positive integer
    """
    if not isinstance(value, int):
        raise ValidationError(_('Value must be an integer.'))
    
    if value <= 0:
        raise ValidationError(_('Value must be a positive integer.'))


def validate_segment_duration(value):
    """
    Validate HLS segment duration.
    
    Args:
        value (int): Segment duration in seconds
        
    Raises:
        ValidationError: If duration is invalid
    """
    validate_positive_integer(value)
    
    if not (1 <= value <= 60):
        raise ValidationError(_('Segment duration must be between 1 and 60 seconds.'))


def validate_max_segments(value):
    """
    Validate maximum segments count.
    
    Args:
        value (int): Maximum segments count
        
    Raises:
        ValidationError: If count is invalid
    """
    validate_positive_integer(value)
    
    if not (1 <= value <= 100):
        raise ValidationError(_('Maximum segments must be between 1 and 100.'))


def validate_retry_attempts(value):
    """
    Validate retry attempts count.
    
    Args:
        value (int): Retry attempts count
        
    Raises:
        ValidationError: If count is invalid
    """
    validate_positive_integer(value)
    
    if not (1 <= value <= 10):
        raise ValidationError(_('Retry attempts must be between 1 and 10.'))


def validate_video_dimensions(width, height):
    """
    Validate video dimensions.
    
    Args:
        width (int): Video width
        height (int): Video height
        
    Raises:
        ValidationError: If dimensions are invalid
    """
    validate_positive_integer(width)
    validate_positive_integer(height)
    
    # Common video resolutions validation
    min_width, max_width = 320, 7680  # From 320p to 8K
    min_height, max_height = 240, 4320
    
    if not (min_width <= width <= max_width):
        raise ValidationError(_(f'Video width must be between {min_width} and {max_width} pixels.'))
    
    if not (min_height <= height <= max_height):
        raise ValidationError(_(f'Video height must be between {min_height} and {max_height} pixels.'))
    
    # Check aspect ratio reasonableness
    aspect_ratio = width / height
    if not (0.5 <= aspect_ratio <= 4.0):
        raise ValidationError(_('Video aspect ratio must be between 0.5 and 4.0.'))


def validate_video_bitrate(value):
    """
    Validate video bitrate.
    
    Args:
        value (int): Video bitrate in bps
        
    Raises:
        ValidationError: If bitrate is invalid
    """
    validate_positive_integer(value)
    
    # Reasonable bitrate range: 100kbps to 50Mbps
    min_bitrate = 100_000
    max_bitrate = 50_000_000
    
    if not (min_bitrate <= value <= max_bitrate):
        raise ValidationError(_(f'Video bitrate must be between {min_bitrate} and {max_bitrate} bps.'))


def validate_audio_bitrate(value):
    """
    Validate audio bitrate.
    
    Args:
        value (int): Audio bitrate in bps
        
    Raises:
        ValidationError: If bitrate is invalid
    """
    validate_positive_integer(value)
    
    # Reasonable audio bitrate range: 32kbps to 320kbps
    min_bitrate = 32_000
    max_bitrate = 320_000
    
    if not (min_bitrate <= value <= max_bitrate):
        raise ValidationError(_(f'Audio bitrate must be between {min_bitrate} and {max_bitrate} bps.'))


def validate_sample_rate(value):
    """
    Validate audio sample rate.
    
    Args:
        value (int): Sample rate in Hz
        
    Raises:
        ValidationError: If sample rate is invalid
    """
    validate_positive_integer(value)
    
    # Common sample rates
    valid_rates = [8000, 11025, 16000, 22050, 44100, 48000, 96000, 192000]
    
    if value not in valid_rates:
        raise ValidationError(_(f'Sample rate must be one of: {", ".join(map(str, valid_rates))} Hz.'))


def validate_audio_channels(value):
    """
    Validate audio channel count.
    
    Args:
        value (int): Number of audio channels
        
    Raises:
        ValidationError: If channel count is invalid
    """
    validate_positive_integer(value)
    
    if not (1 <= value <= 8):
        raise ValidationError(_('Audio channels must be between 1 and 8.'))


def validate_framerate(value):
    """
    Validate video framerate.
    
    Args:
        value (float): Framerate in fps
        
    Raises:
        ValidationError: If framerate is invalid
    """
    if not isinstance(value, (int, float)):
        raise ValidationError(_('Framerate must be a number.'))
    
    if not (1.0 <= value <= 120.0):
        raise ValidationError(_('Framerate must be between 1.0 and 120.0 fps.'))


def validate_slug_format(value):
    """
    Validate slug format for URL safety.
    
    Args:
        value (str): Slug to validate
        
    Raises:
        ValidationError: If slug format is invalid
    """
    if not value:
        raise ValidationError(_('Slug cannot be empty.'))
    
    # Slug should only contain lowercase letters, numbers, and hyphens
    if not re.match(r'^[a-z0-9-]+$', value):
        raise ValidationError(_('Slug can only contain lowercase letters, numbers, and hyphens.'))
    
    # Should not start or end with hyphen
    if value.startswith('-') or value.endswith('-'):
        raise ValidationError(_('Slug cannot start or end with a hyphen.'))
    
    # Should not contain consecutive hyphens
    if '--' in value:
        raise ValidationError(_('Slug cannot contain consecutive hyphens.'))
    
    # Length validation
    if not (3 <= len(value) <= 50):
        raise ValidationError(_('Slug must be between 3 and 50 characters long.'))


def validate_name_format(value):
    """
    Validate name format for display purposes.
    
    Args:
        value (str): Name to validate
        
    Raises:
        ValidationError: If name format is invalid
    """
    if not value or not value.strip():
        raise ValidationError(_('Name cannot be empty.'))
    
    value = value.strip()
    
    # Check for HTML/script injection
    if re.search(r'[<>]', value):
        raise ValidationError(_('Name cannot contain HTML tags.'))
    
    # Check for control characters
    if re.search(r'[\x00-\x1f\x7f]', value):
        raise ValidationError(_('Name cannot contain control characters.'))
    
    # Length validation
    if not (2 <= len(value) <= 100):
        raise ValidationError(_('Name must be between 2 and 100 characters long.'))


def validate_priority(value):
    """
    Validate priority value.
    
    Args:
        value (int): Priority value
        
    Raises:
        ValidationError: If priority is invalid
    """
    if not isinstance(value, int):
        raise ValidationError(_('Priority must be an integer.'))
    
    if not (1 <= value <= 10):
        raise ValidationError(_('Priority must be between 1 and 10.'))


def validate_event_type(value):
    """
    Validate event type format.
    
    Args:
        value (str): Event type to validate
        
    Raises:
        ValidationError: If event type is invalid
    """
    if not value:
        raise ValidationError(_('Event type cannot be empty.'))
    
    # Event type should be snake_case
    if not re.match(r'^[a-z][a-z0-9_]*$', value):
        raise ValidationError(_('Event type must be in snake_case format (lowercase letters, numbers, underscores).'))
    
    # Length validation
    if not (3 <= len(value) <= 50):
        raise ValidationError(_('Event type must be between 3 and 50 characters long.'))


def validate_image_file_path(value):
    """
    Validate image file path and format.
    
    Args:
        value (str): Image file path to validate
        
    Raises:
        ValidationError: If path or format is invalid
    """
    validate_file_path(value)  # Use base file path validation
    
    # Check file extension
    valid_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.webp']
    file_ext = os.path.splitext(value)[1].lower()
    
    if file_ext not in valid_extensions:
        raise ValidationError(_(f'Image file must have one of these extensions: {", ".join(valid_extensions)}'))


def sanitize_user_input(value):
    """
    Sanitize user input for safe storage and display.
    
    Args:
        value (str): Input to sanitize
        
    Returns:
        str: Sanitized input
    """
    if not isinstance(value, str):
        return value
    
    # Remove control characters
    value = re.sub(r'[\x00-\x1f\x7f]', '', value)
    
    # Remove excessive whitespace
    value = re.sub(r'\s+', ' ', value).strip()
    
    # Escape HTML entities
    import html
    value = html.escape(value)
    
    return value


def validate_context_data(context):
    """
    Validate context data for templates and notifications.
    
    Args:
        context (dict): Context data to validate
        
    Raises:
        ValidationError: If context data is invalid
    """
    if not isinstance(context, dict):
        raise ValidationError(_('Context must be a dictionary.'))
    
    def validate_context_value(key, value, path=""):
        current_path = f"{path}.{key}" if path else key
        
        # Validate key format
        if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', key):
            raise ValidationError(_(f'Invalid context key: {key}'))
        
        # Validate value based on type
        if isinstance(value, str):
            # String validation
            if len(value) > 1000:
                raise ValidationError(_(f'Context value too long: {current_path}'))
            
            # Check for script injection
            if re.search(r'<script|javascript:|data:', value, re.IGNORECASE):
                raise ValidationError(_(f'Suspicious content in context: {current_path}'))
        
        elif isinstance(value, dict):
            # Recursive validation for nested objects
            for nested_key, nested_value in value.items():
                validate_context_value(nested_key, nested_value, current_path)
        
        elif isinstance(value, list):
            # Validate list items
            for i, item in enumerate(value):
                if isinstance(item, (str, dict)):
                    validate_context_value(f"item_{i}", item, current_path)
        
        elif not isinstance(value, (int, float, bool, type(None))):
            raise ValidationError(_(f'Invalid context value type: {current_path}'))
    
    for key, value in context.items():
        validate_context_value(key, value)
    
    # Check total context size
    import json
    try:
        context_json = json.dumps(context)
        if len(context_json) > 10000:
            raise ValidationError(_('Context data is too large (max 10KB).'))
    except (TypeError, ValueError):
        raise ValidationError(_('Context data must be JSON serializable.'))


def validate_slug_format(value):
    """
    Validate slug format for URL-friendly identifiers.
    
    Args:
        value (str): Slug to validate
        
    Raises:
        ValidationError: If slug format is invalid
    """
    if not value:
        raise ValidationError(_('Slug cannot be empty.'))
    
    # Check slug format (lowercase letters, numbers, hyphens)
    if not re.match(r'^[a-z0-9-]+$', value):
        raise ValidationError(_('Slug can only contain lowercase letters, numbers, and hyphens.'))
    
    # Check length
    if len(value) > 100:
        raise ValidationError(_('Slug is too long (max 100 characters).'))
    
    # Cannot start or end with hyphen
    if value.startswith('-') or value.endswith('-'):
        raise ValidationError(_('Slug cannot start or end with a hyphen.'))
    
    # Cannot have consecutive hyphens
    if '--' in value:
        raise ValidationError(_('Slug cannot contain consecutive hyphens.'))


def validate_name_format(value):
    """
    Validate name format for human-readable names.
    
    Args:
        value (str): Name to validate
        
    Raises:
        ValidationError: If name format is invalid
    """
    if not value:
        raise ValidationError(_('Name cannot be empty.'))
    
    # Check length
    if len(value) > 100:
        raise ValidationError(_('Name is too long (max 100 characters).'))
    
    # Check for valid characters (letters, numbers, spaces, basic punctuation)
    if not re.match(r'^[a-zA-Z0-9\s\-_\.\(\)]+$', value):
        raise ValidationError(_('Name contains invalid characters.'))
    
    # Cannot be only whitespace
    if not value.strip():
        raise ValidationError(_('Name cannot be only whitespace.'))
    
    # Check for suspicious patterns
    suspicious_patterns = [
        r'<[^>]*>',  # HTML tags
        r'javascript:',  # JavaScript protocol
        r'[;&|`$]',  # Command injection characters
    ]
    
    for pattern in suspicious_patterns:
        if re.search(pattern, value, re.IGNORECASE):
            raise ValidationError(_('Name contains potentially malicious content.'))


def validate_confidence_score(value):
    """
    Validate confidence score range (0.0 to 1.0).
    
    Args:
        value (float): Confidence score to validate
        
    Raises:
        ValidationError: If score is out of range
    """
    if value is None:
        raise ValidationError(_('Confidence score cannot be None.'))
    
    try:
        score = float(value)
    except (TypeError, ValueError):
        raise ValidationError(_('Confidence score must be a number.'))
    
    if score < 0.0 or score > 1.0:
        raise ValidationError(_('Confidence score must be between 0.0 and 1.0.'))


def validate_template_content(value):
    """
    Validate template content for jingle detection.
    
    Args:
        value (str): Template content to validate
        
    Raises:
        ValidationError: If template content is invalid
    """
    if not value:
        raise ValidationError(_('Template content cannot be empty.'))
    
    # Check length
    if len(value) > 1000:
        raise ValidationError(_('Template content is too long (max 1000 characters).'))
    
    # Check for suspicious content
    suspicious_patterns = [
        r'<script[^>]*>',  # Script tags
        r'javascript:',  # JavaScript protocol
        r'data:',  # Data URLs
        r'[;&|`$]',  # Command injection
    ]
    
    for pattern in suspicious_patterns:
        if re.search(pattern, value, re.IGNORECASE):
            raise ValidationError(_('Template content contains potentially malicious content.'))
    
    # Basic content validation
    if not value.strip():
        raise ValidationError(_('Template content cannot be only whitespace.'))
