from __future__ import annotations

import os
from io import BytesIO
from uuid import uuid4
from typing import BinaryIO

from django.core.files.base import ContentFile
from PIL import Image, ImageOps, UnidentifiedImageError
from rest_framework import serializers

from ..storage import StorageFactory


MAX_REVIEW_IMAGE_BYTES = 5 * 1024 * 1024
MAX_REVIEW_IMAGE_DIMENSION = (1600, 1600)
ALLOWED_REVIEW_IMAGE_TYPES = {
    "image/jpeg",
    "image/png",
    "image/webp",
}


def validate_review_image_file(uploaded_file):
    """
    Validate review image file (legacy function for backward compatibility).
    
    This function provides backward compatibility for existing code that uses
    the original validation logic. For new code, consider using ImageValidator
    from common.validators.image_validator for more flexible validation.
    
    Args:
        uploaded_file: Django uploaded file object
        
    Returns:
        The validated uploaded file
        
    Raises:
        serializers.ValidationError: If validation fails
    """
    if not uploaded_file:
        return uploaded_file

    if uploaded_file.size > MAX_REVIEW_IMAGE_BYTES:
        raise serializers.ValidationError("Review image must be 5MB or smaller.")

    content_type = getattr(uploaded_file, "content_type", None)
    if content_type and content_type not in ALLOWED_REVIEW_IMAGE_TYPES:
        raise serializers.ValidationError(
            "Only JPG, PNG, or WebP images are allowed for reviews."
        )

    current_position = uploaded_file.tell() if hasattr(uploaded_file, "tell") else None

    try:
        uploaded_file.seek(0)
        with Image.open(uploaded_file) as image:
            image.verify()
    except (UnidentifiedImageError, OSError, ValueError):
        raise serializers.ValidationError("Uploaded file is not a valid image.")
    finally:
        if current_position is not None:
            uploaded_file.seek(current_position)

    return uploaded_file


def compress_review_image(uploaded_file, upload_dir: str):
    """
    Compress review image for filesystem storage (legacy function for backward compatibility).
    
    This function provides backward compatibility for existing code that uses
    filesystem storage with PIL compression. When using Cloudinary storage,
    compression is handled automatically by Cloudinary's transformations.
    
    Args:
        uploaded_file: Django uploaded file object
        upload_dir: Directory path for organizing files
        
    Returns:
        ContentFile: Compressed image file ready for filesystem storage
        
    Raises:
        serializers.ValidationError: If compression fails or file is too large
    """
    validate_review_image_file(uploaded_file)

    original_name = getattr(uploaded_file, "name", "review-image")
    file_root, _ = os.path.splitext(original_name)

    uploaded_file.seek(0)
    with Image.open(uploaded_file) as image:
        image = ImageOps.exif_transpose(image)
        image.thumbnail(MAX_REVIEW_IMAGE_DIMENSION, Image.Resampling.LANCZOS)

        has_alpha = image.mode in ("RGBA", "LA") or (
            image.mode == "P" and "transparency" in image.info
        )

        buffer = BytesIO()

        if has_alpha:
            image = image.convert("RGBA")
            image.save(buffer, format="WEBP", quality=82, method=6)
            extension = "webp"
        else:
            image = image.convert("RGB")
            image.save(
                buffer,
                format="JPEG",
                quality=82,
                optimize=True,
                progressive=True,
            )
            extension = "jpg"

    compressed_bytes = buffer.getvalue()
    if len(compressed_bytes) > MAX_REVIEW_IMAGE_BYTES:
        raise serializers.ValidationError(
            "Compressed review image is still larger than 5MB. Please choose a smaller image."
        )

    prefix = os.path.basename(upload_dir.rstrip("/")) if upload_dir else ""
    safe_root = prefix or file_root.strip().replace(" ", "-") or "review-image"
    file_name = f"{safe_root}-{uuid4().hex[:8]}.{extension}"
    return ContentFile(compressed_bytes, name=file_name)


def upload_review_image(uploaded_file: BinaryIO, path: str) -> str:
    """
    Upload review image using the configured storage backend.
    
    This function provides a unified interface for uploading review images
    that works with both filesystem and Cloudinary storage backends. It
    automatically handles compression for filesystem storage and uses
    Cloudinary's transformations for cloud storage.
    
    The storage backend is determined by the REVIEW_IMAGE_STORAGE_BACKEND
    Django setting:
    - 'filesystem': Uses local storage with PIL compression
    - 'cloudinary': Uses Cloudinary with automatic optimization
    
    Args:
        uploaded_file: Binary file object to upload
        path: Logical path for organization (e.g., "reviews/property")
        
    Returns:
        str: Storage identifier (file path for filesystem, public_id for Cloudinary)
        
    Raises:
        serializers.ValidationError: If upload fails or validation fails
        
    Example:
        >>> identifier = upload_review_image(request.FILES['image'], "reviews/property")
        >>> # For filesystem: "review_images/reviews/property/image-abc123.jpg"
        >>> # For Cloudinary: "panjabiz/reviews/property/review-def456-1704067200"
    """
    # Get the configured storage backend
    storage = StorageFactory.get_storage_backend()
    
    # For filesystem storage, we need to compress the image first
    if StorageFactory.is_filesystem_backend():
        # Use existing compression logic for filesystem storage
        compressed_file = compress_review_image(uploaded_file, path)
        
        # Upload the compressed file
        return storage.upload(compressed_file, path)
    
    else:
        # For Cloudinary and other cloud storage, validate but don't compress
        # (compression/optimization is handled by the cloud service)
        validate_review_image_file(uploaded_file)
        
        # Upload directly - cloud storage handles optimization
        return storage.upload(uploaded_file, path)
