"""Core Validators

This module contains common validation functions and Django validators
used throughout the Adtlas project.
"""

import re
import os
import mimetypes
from typing import Any, List, Optional, Union
from urllib.parse import urlparse

from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _
from django.core.validators import RegexValidator, EmailValidator


# ============================================================================
# Custom Validator Classes
# ============================================================================

@deconstructible
class PhoneNumberValidator:
    """Validator for phone numbers."""
    
    message = _('Enter a valid phone number.')
    code = 'invalid_phone'
    
    def __init__(self, message=None, code=None):
        if message is not None:
            self.message = message
        if code is not None:
            self.code = code
    
    def __call__(self, value):
        if not self.is_valid_phone(value):
            raise ValidationError(self.message, code=self.code)
    
    def is_valid_phone(self, phone):
        """Check if phone number is valid.
        
        Args:
            phone: Phone number string
            
        Returns:
            bool: True if valid, False otherwise
        """
        if not phone:
            return False
        
        # Remove all non-digit characters
        digits_only = re.sub(r'\D', '', phone)
        
        # Check length (7-15 digits)
        if len(digits_only) < 7 or len(digits_only) > 15:
            return False
        
        # Basic pattern matching
        patterns = [
            r'^\+?1?[2-9]\d{2}[2-9]\d{2}\d{4}$',  # US format
            r'^\+?\d{7,15}$',  # International format
        ]
        
        for pattern in patterns:
            if re.match(pattern, phone):
                return True
        
        return False


@deconstructible
class PasswordStrengthValidator:
    """Validator for password strength."""
    
    def __init__(self, min_length=8, require_uppercase=True, require_lowercase=True,
                 require_digits=True, require_special=True):
        self.min_length = min_length
        self.require_uppercase = require_uppercase
        self.require_lowercase = require_lowercase
        self.require_digits = require_digits
        self.require_special = require_special
    
    def __call__(self, value):
        errors = []
        
        if len(value) < self.min_length:
            errors.append(_(f'Password must be at least {self.min_length} characters long.'))
        
        if self.require_uppercase and not re.search(r'[A-Z]', value):
            errors.append(_('Password must contain at least one uppercase letter.'))
        
        if self.require_lowercase and not re.search(r'[a-z]', value):
            errors.append(_('Password must contain at least one lowercase letter.'))
        
        if self.require_digits and not re.search(r'\d', value):
            errors.append(_('Password must contain at least one digit.'))
        
        if self.require_special and not re.search(r'[!@#$%^&*(),.?":{}|<>]', value):
            errors.append(_('Password must contain at least one special character.'))
        
        if errors:
            raise ValidationError(errors)
    
    def get_help_text(self):
        """Return help text for password requirements."""
        requirements = []
        
        requirements.append(f'At least {self.min_length} characters')
        
        if self.require_uppercase:
            requirements.append('one uppercase letter')
        
        if self.require_lowercase:
            requirements.append('one lowercase letter')
        
        if self.require_digits:
            requirements.append('one digit')
        
        if self.require_special:
            requirements.append('one special character')
        
        return f"Password must contain: {', '.join(requirements)}."


@deconstructible
class FileExtensionValidator:
    """Validator for file extensions."""
    
    message = _('File extension "%(extension)s" is not allowed. Allowed extensions are: %(allowed_extensions)s.')
    code = 'invalid_extension'
    
    def __init__(self, allowed_extensions, message=None, code=None):
        self.allowed_extensions = [ext.lower() for ext in allowed_extensions]
        if message is not None:
            self.message = message
        if code is not None:
            self.code = code
    
    def __call__(self, value):
        extension = os.path.splitext(value.name)[1][1:].lower()
        
        if extension not in self.allowed_extensions:
            raise ValidationError(
                self.message,
                code=self.code,
                params={
                    'extension': extension,
                    'allowed_extensions': ', '.join(self.allowed_extensions)
                }
            )


@deconstructible
class FileSizeValidator:
    """Validator for file size."""
    
    message = _('File size %(size)s exceeds the maximum allowed size of %(max_size)s.')
    code = 'file_too_large'
    
    def __init__(self, max_size, message=None, code=None):
        self.max_size = max_size
        if message is not None:
            self.message = message
        if code is not None:
            self.code = code
    
    def __call__(self, value):
        if value.size > self.max_size:
            raise ValidationError(
                self.message,
                code=self.code,
                params={
                    'size': self.format_size(value.size),
                    'max_size': self.format_size(self.max_size)
                }
            )
    
    def format_size(self, size):
        """Format file size in human readable format."""
        for unit in ['B', 'KB', 'MB', 'GB']:
            if size < 1024.0:
                return f"{size:.1f} {unit}"
            size /= 1024.0
        return f"{size:.1f} TB"


@deconstructible
class ImageValidator:
    """Validator for image files."""
    
    def __init__(self, max_width=None, max_height=None, min_width=None, min_height=None):
        self.max_width = max_width
        self.max_height = max_height
        self.min_width = min_width
        self.min_height = min_height
    
    def __call__(self, value):
        try:
            from PIL import Image
            
            # Open and get image dimensions
            image = Image.open(value)
            width, height = image.size
            
            errors = []
            
            if self.max_width and width > self.max_width:
                errors.append(_(f'Image width {width}px exceeds maximum allowed width of {self.max_width}px.'))
            
            if self.max_height and height > self.max_height:
                errors.append(_(f'Image height {height}px exceeds maximum allowed height of {self.max_height}px.'))
            
            if self.min_width and width < self.min_width:
                errors.append(_(f'Image width {width}px is below minimum required width of {self.min_width}px.'))
            
            if self.min_height and height < self.min_height:
                errors.append(_(f'Image height {height}px is below minimum required height of {self.min_height}px.'))
            
            if errors:
                raise ValidationError(errors)
                
        except ImportError:
            # PIL not available, skip image validation
            pass
        except Exception as e:
            raise ValidationError(_(f'Invalid image file: {str(e)}'))


@deconstructible
class URLValidator:
    """Enhanced URL validator."""
    
    message = _('Enter a valid URL.')
    code = 'invalid_url'
    
    def __init__(self, schemes=None, message=None, code=None):
        self.schemes = schemes or ['http', 'https']
        if message is not None:
            self.message = message
        if code is not None:
            self.code = code
    
    def __call__(self, value):
        try:
            parsed = urlparse(value)
            
            if not parsed.scheme:
                raise ValidationError(self.message, code=self.code)
            
            if parsed.scheme not in self.schemes:
                raise ValidationError(
                    _(f'URL scheme "{parsed.scheme}" is not allowed. Allowed schemes: {self.schemes}'),
                    code=self.code
                )
            
            if not parsed.netloc:
                raise ValidationError(self.message, code=self.code)
                
        except Exception:
            raise ValidationError(self.message, code=self.code)


# ============================================================================
# Regex Validators
# ============================================================================

# Username validator
username_validator = RegexValidator(
    regex=r'^[a-zA-Z0-9_.-]+$',
    message=_('Username can only contain letters, numbers, dots, hyphens, and underscores.'),
    code='invalid_username'
)

# Alphanumeric validator
alphanumeric_validator = RegexValidator(
    regex=r'^[a-zA-Z0-9]+$',
    message=_('This field can only contain letters and numbers.'),
    code='invalid_alphanumeric'
)

# Slug validator
slug_validator = RegexValidator(
    regex=r'^[-a-zA-Z0-9_]+$',
    message=_('This field can only contain letters, numbers, hyphens, and underscores.'),
    code='invalid_slug'
)

# Hex color validator
hex_color_validator = RegexValidator(
    regex=r'^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$',
    message=_('Enter a valid hex color code (e.g., #FF0000 or #F00).'),
    code='invalid_hex_color'
)

# IP address validator
ip_address_validator = RegexValidator(
    regex=r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$',
    message=_('Enter a valid IP address.'),
    code='invalid_ip'
)

# Credit card validator
credit_card_validator = RegexValidator(
    regex=r'^[0-9]{13,19}$',
    message=_('Enter a valid credit card number.'),
    code='invalid_credit_card'
)


# ============================================================================
# Validation Functions
# ============================================================================

def validate_email_domain(email: str, allowed_domains: List[str]) -> bool:
    """Validate email domain against allowed domains.
    
    Args:
        email: Email address to validate
        allowed_domains: List of allowed domains
        
    Returns:
        bool: True if domain is allowed, False otherwise
    """
    if not email or '@' not in email:
        return False
    
    domain = email.split('@')[1].lower()
    return domain in [d.lower() for d in allowed_domains]


def validate_password_strength(password: str) -> dict:
    """Validate password strength and return detailed feedback.
    
    Args:
        password: Password to validate
        
    Returns:
        dict: Validation results with score and feedback
    """
    if not password:
        return {
            'score': 0,
            'strength': 'Very Weak',
            'feedback': ['Password is required']
        }
    
    score = 0
    feedback = []
    
    # Length check
    if len(password) >= 8:
        score += 1
    else:
        feedback.append('Use at least 8 characters')
    
    if len(password) >= 12:
        score += 1
    
    # Character variety checks
    if re.search(r'[a-z]', password):
        score += 1
    else:
        feedback.append('Include lowercase letters')
    
    if re.search(r'[A-Z]', password):
        score += 1
    else:
        feedback.append('Include uppercase letters')
    
    if re.search(r'\d', password):
        score += 1
    else:
        feedback.append('Include numbers')
    
    if re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
        score += 1
    else:
        feedback.append('Include special characters')
    
    # Common patterns check
    common_patterns = [
        r'123456', r'password', r'qwerty', r'abc123',
        r'admin', r'letmein', r'welcome', r'monkey'
    ]
    
    for pattern in common_patterns:
        if pattern in password.lower():
            score -= 1
            feedback.append('Avoid common passwords')
            break
    
    # Determine strength
    if score <= 1:
        strength = 'Very Weak'
    elif score <= 2:
        strength = 'Weak'
    elif score <= 3:
        strength = 'Fair'
    elif score <= 4:
        strength = 'Good'
    else:
        strength = 'Strong'
    
    return {
        'score': max(0, score),
        'strength': strength,
        'feedback': feedback
    }


def validate_file_type(file, allowed_types: List[str]) -> bool:
    """Validate file type using MIME type.
    
    Args:
        file: File object to validate
        allowed_types: List of allowed MIME types
        
    Returns:
        bool: True if file type is allowed, False otherwise
    """
    if not file or not file.name:
        return False
    
    # Get MIME type
    mime_type, _ = mimetypes.guess_type(file.name)
    
    if not mime_type:
        return False
    
    return mime_type in allowed_types


def validate_json_structure(data: Any, required_fields: List[str]) -> bool:
    """Validate JSON data structure.
    
    Args:
        data: JSON data to validate
        required_fields: List of required field names
        
    Returns:
        bool: True if structure is valid, False otherwise
    """
    if not isinstance(data, dict):
        return False
    
    for field in required_fields:
        if field not in data:
            return False
    
    return True


def validate_date_range(start_date, end_date) -> bool:
    """Validate that start date is before end date.
    
    Args:
        start_date: Start date
        end_date: End date
        
    Returns:
        bool: True if range is valid, False otherwise
    """
    if not start_date or not end_date:
        return False
    
    return start_date <= end_date


def validate_positive_number(value: Union[int, float]) -> bool:
    """Validate that number is positive.
    
    Args:
        value: Number to validate
        
    Returns:
        bool: True if positive, False otherwise
    """
    try:
        return float(value) > 0
    except (ValueError, TypeError):
        return False


def validate_percentage(value: Union[int, float]) -> bool:
    """Validate that value is a valid percentage (0-100).
    
    Args:
        value: Value to validate
        
    Returns:
        bool: True if valid percentage, False otherwise
    """
    try:
        num_value = float(value)
        return 0 <= num_value <= 100
    except (ValueError, TypeError):
        return False


def validate_coordinates(latitude: float, longitude: float) -> bool:
    """Validate GPS coordinates.
    
    Args:
        latitude: Latitude value
        longitude: Longitude value
        
    Returns:
        bool: True if coordinates are valid, False otherwise
    """
    try:
        lat = float(latitude)
        lng = float(longitude)
        
        return -90 <= lat <= 90 and -180 <= lng <= 180
    except (ValueError, TypeError):
        return False


# ============================================================================
# Django Model Field Validators
# ============================================================================

def validate_no_special_chars(value):
    """Validate that value contains no special characters."""
    if re.search(r'[^a-zA-Z0-9\s]', value):
        raise ValidationError(
            _('This field cannot contain special characters.'),
            code='special_chars_not_allowed'
        )


def validate_no_profanity(value):
    """Validate that value contains no profanity."""
    # Basic profanity filter - in production, use a proper library
    profanity_words = ['spam', 'test_profanity']  # Add actual words as needed
    
    value_lower = value.lower()
    for word in profanity_words:
        if word in value_lower:
            raise ValidationError(
                _('This field contains inappropriate content.'),
                code='profanity_detected'
            )


def validate_future_date(value):
    """Validate that date is in the future."""
    from django.utils import timezone
    
    if value <= timezone.now().date():
        raise ValidationError(
            _('Date must be in the future.'),
            code='date_not_future'
        )


def validate_business_hours(value):
    """Validate that time is within business hours (9 AM - 5 PM)."""
    from datetime import time
    
    start_time = time(9, 0)  # 9:00 AM
    end_time = time(17, 0)   # 5:00 PM
    
    if not (start_time <= value <= end_time):
        raise ValidationError(
            _('Time must be within business hours (9:00 AM - 5:00 PM).'),
            code='outside_business_hours'
        )


# ============================================================================
# Validator Instances
# ============================================================================

# Common validator instances
phone_validator = PhoneNumberValidator()
password_strength_validator = PasswordStrengthValidator()
url_validator = URLValidator()

# File validators
image_file_validator = FileExtensionValidator(['jpg', 'jpeg', 'png', 'gif', 'webp'])
document_file_validator = FileExtensionValidator(['pdf', 'doc', 'docx', 'txt', 'rtf'])
video_file_validator = FileExtensionValidator(['mp4', 'avi', 'mov', 'wmv', 'flv'])
audio_file_validator = FileExtensionValidator(['mp3', 'wav', 'ogg', 'aac'])

# Size validators
small_file_validator = FileSizeValidator(5 * 1024 * 1024)  # 5MB
medium_file_validator = FileSizeValidator(25 * 1024 * 1024)  # 25MB
large_file_validator = FileSizeValidator(100 * 1024 * 1024)  # 100MB

# Image validators
profile_image_validator = ImageValidator(max_width=1000, max_height=1000, min_width=100, min_height=100)
banner_image_validator = ImageValidator(max_width=2000, max_height=800, min_width=800, min_height=200)
icon_image_validator = ImageValidator(max_width=50, max_height=50, min_width=20, min_height=20)