"""
Image validation utilities for upload operations.

This module provides comprehensive image validation including file size,
type checking, and integrity verification using PIL. It's designed to work
with Django REST Framework serializers and provides user-friendly error messages.
"""

import io
from typing import List, Tuple, BinaryIO

from PIL import Image
from rest_framework import serializers


class ImageValidator:
    """
    Comprehensive image validator with configurable constraints.
    
    This validator provides multiple validation checks for uploaded images:
    - File size validation
    - MIME type validation  
    - Image integrity verification using PIL
    - Dimension constraints (optional)
    
    The validator is designed to be reusable across different upload contexts
    and provides clear, user-friendly error messages for validation failures.
    """
    
    def __init__(
        self,
        max_size_bytes: int = 5 * 1024 * 1024,  # 5MB default
        allowed_types: List[str] = None,
        max_dimensions: Tuple[int, int] = None
    ):
        """
        Initialize image validator with constraints.
        
        Args:
            max_size_bytes: Maximum file size in bytes (default: 5MB)
            allowed_types: List of allowed MIME types (default: JPG, PNG, WebP)
            max_dimensions: Optional tuple of (max_width, max_height)
        """
        self.max_size_bytes = max_size_bytes
        self.allowed_types = allowed_types or [
            'image/jpeg',
            'image/jpg', 
            'image/png',
            'image/webp'
        ]
        self.max_dimensions = max_dimensions
    
    def validate(self, file: BinaryIO) -> None:
        """
        Perform comprehensive validation on an uploaded image file.
        
        This method runs all validation checks and raises ValidationError
        if any check fails. The file pointer is preserved during validation.
        
        Args:
            file: Binary file object to validate
            
        Raises:
            serializers.ValidationError: If validation fails with user-friendly message
            
        Example:
            >>> validator = ImageValidator(max_size_bytes=2*1024*1024)  # 2MB
            >>> validator.validate(uploaded_file)  # Raises ValidationError if invalid
        """
        # Store original file position
        original_position = file.tell()
        
        try:
            # Run all validation checks
            self._validate_size(file)
            self._validate_type(file)
            self._validate_integrity(file)
            
            if self.max_dimensions:
                self._validate_dimensions(file)
                
        finally:
            # Always restore file pointer to original position
            file.seek(original_position)
    
    def _validate_size(self, file: BinaryIO) -> None:
        """
        Validate file size against maximum allowed size.
        
        Args:
            file: Binary file object to validate
            
        Raises:
            serializers.ValidationError: If file exceeds maximum size
        """
        # Get file size by seeking to end
        file.seek(0, 2)  # Seek to end
        file_size = file.tell()
        file.seek(0)  # Reset to beginning
        
        if file_size > self.max_size_bytes:
            max_size_mb = self.max_size_bytes / (1024 * 1024)
            actual_size_mb = file_size / (1024 * 1024)
            
            raise serializers.ValidationError(
                f"Image file too large. Maximum size is {max_size_mb:.1f}MB, "
                f"but uploaded file is {actual_size_mb:.1f}MB."
            )
    
    def _validate_type(self, file: BinaryIO) -> None:
        """
        Validate file MIME type against allowed types.
        
        Args:
            file: Binary file object to validate
            
        Raises:
            serializers.ValidationError: If file type is not allowed
        """
        # Get content type from file object if available
        content_type = getattr(file, 'content_type', None)
        
        if not content_type:
            # Try to determine from file name
            file_name = getattr(file, 'name', '')
            if file_name.lower().endswith(('.jpg', '.jpeg')):
                content_type = 'image/jpeg'
            elif file_name.lower().endswith('.png'):
                content_type = 'image/png'
            elif file_name.lower().endswith('.webp'):
                content_type = 'image/webp'
            else:
                raise serializers.ValidationError(
                    "Unable to determine image type. Please upload a valid image file."
                )
        
        if content_type not in self.allowed_types:
            allowed_formats = []
            for mime_type in self.allowed_types:
                if mime_type == 'image/jpeg' or mime_type == 'image/jpg':
                    allowed_formats.append('JPEG')
                elif mime_type == 'image/png':
                    allowed_formats.append('PNG')
                elif mime_type == 'image/webp':
                    allowed_formats.append('WebP')
            
            raise serializers.ValidationError(
                f"Invalid image format. Allowed formats: {', '.join(allowed_formats)}. "
                f"Uploaded file type: {content_type}."
            )
    
    def _validate_integrity(self, file: BinaryIO) -> None:
        """
        Validate image integrity using PIL verification.
        
        This method uses PIL to verify that the file is a valid image
        and can be opened without corruption.
        
        Args:
            file: Binary file object to validate
            
        Raises:
            serializers.ValidationError: If image is corrupted or invalid
        """
        file.seek(0)
        
        try:
            # Create PIL Image object
            with Image.open(file) as img:
                # Verify image integrity
                img.verify()
                
        except Exception as e:
            raise serializers.ValidationError(
                f"Invalid or corrupted image file. Please upload a valid image. "
                f"Error: {str(e)}"
            )
    
    def _validate_dimensions(self, file: BinaryIO) -> None:
        """
        Validate image dimensions against maximum allowed dimensions.
        
        Args:
            file: Binary file object to validate
            
        Raises:
            serializers.ValidationError: If image dimensions exceed limits
        """
        if not self.max_dimensions:
            return
        
        file.seek(0)
        
        try:
            with Image.open(file) as img:
                width, height = img.size
                max_width, max_height = self.max_dimensions
                
                if width > max_width or height > max_height:
                    raise serializers.ValidationError(
                        f"Image dimensions too large. Maximum size is {max_width}x{max_height} pixels, "
                        f"but uploaded image is {width}x{height} pixels."
                    )
                    
        except serializers.ValidationError:
            # Re-raise validation errors
            raise
        except Exception as e:
            raise serializers.ValidationError(
                f"Unable to read image dimensions. Please upload a valid image file. "
                f"Error: {str(e)}"
            )
    
    def get_image_info(self, file: BinaryIO) -> dict:
        """
        Get basic information about an image file.
        
        This method extracts basic metadata from an image file without
        performing validation. Useful for logging or debugging.
        
        Args:
            file: Binary file object to analyze
            
        Returns:
            dict: Image information including size, dimensions, format
        """
        original_position = file.tell()
        
        try:
            # Get file size
            file.seek(0, 2)
            file_size = file.tell()
            file.seek(0)
            
            info = {
                'size_bytes': file_size,
                'size_mb': round(file_size / (1024 * 1024), 2)
            }
            
            # Try to get image dimensions and format
            try:
                with Image.open(file) as img:
                    info.update({
                        'width': img.width,
                        'height': img.height,
                        'format': img.format,
                        'mode': img.mode
                    })
            except Exception:
                info.update({
                    'width': None,
                    'height': None,
                    'format': 'unknown',
                    'mode': 'unknown'
                })
            
            return info
            
        finally:
            file.seek(original_position)