"""Creative Management Utility Functions

Utility functions for creative asset processing and management.
Provides file processing, validation, optimization, and helper functions.

Function Categories:
- File Processing: Upload, conversion, optimization
- Validation: File validation and compliance checking
- Metadata: File metadata extraction
- Image Processing: Image manipulation and optimization
- Video Processing: Video processing and compression
- Audio Processing: Audio file handling
- File Management: File operations and cleanup
- Format Conversion: File format conversions
- Performance: Performance optimization utilities

Functions:
- validate_creative_file: Validate uploaded creative files
- get_file_metadata: Extract file metadata
- generate_thumbnail: Generate file thumbnails
- optimize_image: Optimize image files
- compress_video: Compress video files
- convert_format: Convert file formats
- calculate_file_hash: Calculate file checksums
- get_file_dimensions: Get image/video dimensions
- extract_audio_metadata: Extract audio metadata
- validate_file_size: Validate file size limits

Integrations:
- PIL/Pillow for image processing
- FFmpeg for video/audio processing
- External APIs for content analysis
- Cloud storage services
- File validation libraries
"""

import os
import hashlib
import mimetypes
import subprocess
import json
import logging
from datetime import datetime
from typing import Dict, List, Optional, Tuple, Union

from PIL import Image, ImageOps, ImageFilter
from PIL.ExifTags import TAGS
import magic
from django.core.files.uploadedfile import UploadedFile
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.text import slugify
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile

logger = logging.getLogger(__name__)


# File Validation Functions
def validate_creative_file(file_path: str) -> Dict[str, Union[bool, List[str]]]:
    """
    Validate creative file for compliance and technical requirements.
    
    Args:
        file_path (str): Path to the file to validate
        
    Returns:
        dict: Validation results with is_valid flag and error list
    """
    validation_result = {
        'is_valid': True,
        'errors': [],
        'warnings': [],
        'file_info': {}
    }
    
    try:
        if not os.path.exists(file_path):
            validation_result['is_valid'] = False
            validation_result['errors'].append('File does not exist')
            return validation_result
        
        # Get basic file info
        file_size = os.path.getsize(file_path)
        file_ext = os.path.splitext(file_path)[1].lower()
        
        validation_result['file_info'] = {
            'size': file_size,
            'extension': file_ext,
            'mime_type': get_mime_type(file_path)
        }
        
        # Validate file size (50MB limit)
        if file_size > 50 * 1024 * 1024:
            validation_result['errors'].append('File size exceeds 50MB limit')
            validation_result['is_valid'] = False
        
        # Validate file extension
        allowed_extensions = [
            '.jpg', '.jpeg', '.png', '.gif', '.webp',  # Images
            '.mp4', '.mov', '.avi', '.webm',           # Videos
            '.mp3', '.wav', '.aac', '.ogg',            # Audio
            '.html', '.htm'                            # HTML
        ]
        
        if file_ext not in allowed_extensions:
            validation_result['errors'].append(f'File extension {file_ext} not allowed')
            validation_result['is_valid'] = False
        
        # Validate MIME type
        mime_type = validation_result['file_info']['mime_type']
        if not is_valid_mime_type(mime_type, file_ext):
            validation_result['errors'].append(f'MIME type {mime_type} does not match file extension')
            validation_result['is_valid'] = False
        
        # File-specific validation
        if file_ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp']:
            image_validation = validate_image_file(file_path)
            validation_result['errors'].extend(image_validation['errors'])
            validation_result['warnings'].extend(image_validation['warnings'])
            if not image_validation['is_valid']:
                validation_result['is_valid'] = False
        
        elif file_ext in ['.mp4', '.mov', '.avi', '.webm']:
            video_validation = validate_video_file(file_path)
            validation_result['errors'].extend(video_validation['errors'])
            validation_result['warnings'].extend(video_validation['warnings'])
            if not video_validation['is_valid']:
                validation_result['is_valid'] = False
        
        elif file_ext in ['.mp3', '.wav', '.aac', '.ogg']:
            audio_validation = validate_audio_file(file_path)
            validation_result['errors'].extend(audio_validation['errors'])
            validation_result['warnings'].extend(audio_validation['warnings'])
            if not audio_validation['is_valid']:
                validation_result['is_valid'] = False
        
        # Security validation
        security_validation = validate_file_security(file_path)
        validation_result['errors'].extend(security_validation['errors'])
        validation_result['warnings'].extend(security_validation['warnings'])
        if not security_validation['is_valid']:
            validation_result['is_valid'] = False
        
        logger.info(f"File validation completed: {file_path} - Valid: {validation_result['is_valid']}")
        return validation_result
        
    except Exception as exc:
        logger.error(f"Error validating file {file_path}: {str(exc)}")
        validation_result['is_valid'] = False
        validation_result['errors'].append(f'Validation error: {str(exc)}')
        return validation_result


def validate_image_file(file_path: str) -> Dict[str, Union[bool, List[str]]]:
    """
    Validate image file.
    
    Args:
        file_path (str): Path to image file
        
    Returns:
        dict: Image validation results
    """
    result = {'is_valid': True, 'errors': [], 'warnings': []}
    
    try:
        with Image.open(file_path) as img:
            # Check if image can be opened
            img.verify()
            
            # Reopen for further checks (verify() closes the image)
            with Image.open(file_path) as img:
                width, height = img.size
                
                # Check dimensions
                if width > 4096 or height > 4096:
                    result['warnings'].append(f'Large image dimensions: {width}x{height}')
                
                if width < 100 or height < 100:
                    result['warnings'].append(f'Small image dimensions: {width}x{height}')
                
                # Check color mode
                if img.mode not in ['RGB', 'RGBA', 'L', 'P']:
                    result['warnings'].append(f'Unusual color mode: {img.mode}')
                
                # Check for animation (GIF)
                if hasattr(img, 'is_animated') and img.is_animated:
                    if getattr(img, 'n_frames', 1) > 100:
                        result['warnings'].append('GIF has many frames, may be large')
        
    except Exception as exc:
        result['is_valid'] = False
        result['errors'].append(f'Invalid image file: {str(exc)}')
    
    return result


def validate_video_file(file_path: str) -> Dict[str, Union[bool, List[str]]]:
    """
    Validate video file using FFprobe.
    
    Args:
        file_path (str): Path to video file
        
    Returns:
        dict: Video validation results
    """
    result = {'is_valid': True, 'errors': [], 'warnings': []}
    
    try:
        # Use FFprobe to get video information
        cmd = [
            'ffprobe',
            '-v', 'quiet',
            '-print_format', 'json',
            '-show_format',
            '-show_streams',
            file_path
        ]
        
        process = subprocess.run(cmd, capture_output=True, text=True)
        
        if process.returncode != 0:
            result['is_valid'] = False
            result['errors'].append('Invalid video file or FFprobe error')
            return result
        
        video_info = json.loads(process.stdout)
        
        # Check duration
        duration = float(video_info.get('format', {}).get('duration', 0))
        if duration > 300:  # 5 minutes
            result['warnings'].append(f'Long video duration: {duration:.1f} seconds')
        
        # Check video streams
        video_streams = [s for s in video_info.get('streams', []) if s.get('codec_type') == 'video']
        if not video_streams:
            result['is_valid'] = False
            result['errors'].append('No video stream found')
        else:
            video_stream = video_streams[0]
            width = int(video_stream.get('width', 0))
            height = int(video_stream.get('height', 0))
            
            if width > 1920 or height > 1080:
                result['warnings'].append(f'High resolution video: {width}x{height}')
        
        # Check bitrate
        bitrate = int(video_info.get('format', {}).get('bit_rate', 0))
        if bitrate > 10000000:  # 10 Mbps
            result['warnings'].append(f'High bitrate: {bitrate} bps')
        
    except subprocess.TimeoutExpired:
        result['is_valid'] = False
        result['errors'].append('Video validation timeout')
    except json.JSONDecodeError:
        result['is_valid'] = False
        result['errors'].append('Error parsing video information')
    except Exception as exc:
        result['is_valid'] = False
        result['errors'].append(f'Video validation error: {str(exc)}')
    
    return result


def validate_audio_file(file_path: str) -> Dict[str, Union[bool, List[str]]]:
    """
    Validate audio file.
    
    Args:
        file_path (str): Path to audio file
        
    Returns:
        dict: Audio validation results
    """
    result = {'is_valid': True, 'errors': [], 'warnings': []}
    
    try:
        # Use FFprobe to get audio information
        cmd = [
            'ffprobe',
            '-v', 'quiet',
            '-print_format', 'json',
            '-show_format',
            '-show_streams',
            file_path
        ]
        
        process = subprocess.run(cmd, capture_output=True, text=True)
        
        if process.returncode != 0:
            result['is_valid'] = False
            result['errors'].append('Invalid audio file or FFprobe error')
            return result
        
        audio_info = json.loads(process.stdout)
        
        # Check duration
        duration = float(audio_info.get('format', {}).get('duration', 0))
        if duration > 600:  # 10 minutes
            result['warnings'].append(f'Long audio duration: {duration:.1f} seconds')
        
        # Check audio streams
        audio_streams = [s for s in audio_info.get('streams', []) if s.get('codec_type') == 'audio']
        if not audio_streams:
            result['is_valid'] = False
            result['errors'].append('No audio stream found')
        
    except Exception as exc:
        result['is_valid'] = False
        result['errors'].append(f'Audio validation error: {str(exc)}')
    
    return result


def validate_file_security(file_path: str) -> Dict[str, Union[bool, List[str]]]:
    """
    Validate file for security issues.
    
    Args:
        file_path (str): Path to file
        
    Returns:
        dict: Security validation results
    """
    result = {'is_valid': True, 'errors': [], 'warnings': []}
    
    try:
        # Check for executable files
        if os.access(file_path, os.X_OK):
            result['is_valid'] = False
            result['errors'].append('File has executable permissions')
        
        # Check file content for suspicious patterns
        with open(file_path, 'rb') as f:
            content = f.read(1024)  # Read first 1KB
            
            # Check for script tags in non-HTML files
            file_ext = os.path.splitext(file_path)[1].lower()
            if file_ext not in ['.html', '.htm']:
                if b'<script' in content.lower() or b'javascript:' in content.lower():
                    result['is_valid'] = False
                    result['errors'].append('Suspicious script content detected')
        
    except Exception as exc:
        result['warnings'].append(f'Security validation warning: {str(exc)}')
    
    return result


# File Metadata Functions
def get_file_metadata(file_path: str) -> Dict[str, Union[str, int, float, Dict]]:
    """
    Extract comprehensive metadata from file.
    
    Args:
        file_path (str): Path to file
        
    Returns:
        dict: File metadata
    """
    metadata = {
        'file_path': file_path,
        'file_name': os.path.basename(file_path),
        'file_size': 0,
        'file_extension': '',
        'mime_type': '',
        'created_at': None,
        'modified_at': None,
        'checksum': ''
    }
    
    try:
        if not os.path.exists(file_path):
            return metadata
        
        # Basic file info
        stat = os.stat(file_path)
        metadata.update({
            'file_size': stat.st_size,
            'file_extension': os.path.splitext(file_path)[1].lower(),
            'mime_type': get_mime_type(file_path),
            'created_at': datetime.fromtimestamp(stat.st_ctime).isoformat(),
            'modified_at': datetime.fromtimestamp(stat.st_mtime).isoformat(),
            'checksum': calculate_file_hash(file_path)
        })
        
        # File-specific metadata
        file_ext = metadata['file_extension']
        
        if file_ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp']:
            metadata.update(get_image_metadata(file_path))
        elif file_ext in ['.mp4', '.mov', '.avi', '.webm']:
            metadata.update(get_video_metadata(file_path))
        elif file_ext in ['.mp3', '.wav', '.aac', '.ogg']:
            metadata.update(get_audio_metadata(file_path))
        
        logger.info(f"Metadata extracted for file: {file_path}")
        return metadata
        
    except Exception as exc:
        logger.error(f"Error extracting metadata from {file_path}: {str(exc)}")
        metadata['error'] = str(exc)
        return metadata


def get_image_metadata(file_path: str) -> Dict[str, Union[str, int, Dict]]:
    """
    Extract image-specific metadata.
    
    Args:
        file_path (str): Path to image file
        
    Returns:
        dict: Image metadata
    """
    metadata = {}
    
    try:
        with Image.open(file_path) as img:
            width, height = img.size
            metadata.update({
                'dimensions': f'{width}x{height}',
                'width': width,
                'height': height,
                'color_mode': img.mode,
                'format': img.format
            })
            
            # Extract EXIF data
            if hasattr(img, '_getexif') and img._getexif():
                exif_data = {}
                for tag_id, value in img._getexif().items():
                    tag = TAGS.get(tag_id, tag_id)
                    exif_data[tag] = str(value)
                metadata['exif'] = exif_data
            
            # Check for animation
            if hasattr(img, 'is_animated'):
                metadata['is_animated'] = img.is_animated
                if img.is_animated:
                    metadata['frame_count'] = getattr(img, 'n_frames', 1)
    
    except Exception as exc:
        logger.error(f"Error extracting image metadata: {str(exc)}")
        metadata['error'] = str(exc)
    
    return metadata


def get_video_metadata(file_path: str) -> Dict[str, Union[str, int, float, Dict]]:
    """
    Extract video-specific metadata using FFprobe.
    
    Args:
        file_path (str): Path to video file
        
    Returns:
        dict: Video metadata
    """
    metadata = {}
    
    try:
        cmd = [
            'ffprobe',
            '-v', 'quiet',
            '-print_format', 'json',
            '-show_format',
            '-show_streams',
            file_path
        ]
        
        process = subprocess.run(cmd, capture_output=True, text=True)
        
        if process.returncode == 0:
            video_info = json.loads(process.stdout)
            
            # Format information
            format_info = video_info.get('format', {})
            metadata.update({
                'duration': float(format_info.get('duration', 0)),
                'bitrate': int(format_info.get('bit_rate', 0)),
                'format_name': format_info.get('format_name', '')
            })
            
            # Video stream information
            video_streams = [s for s in video_info.get('streams', []) if s.get('codec_type') == 'video']
            if video_streams:
                video_stream = video_streams[0]
                width = int(video_stream.get('width', 0))
                height = int(video_stream.get('height', 0))
                
                metadata.update({
                    'dimensions': f'{width}x{height}',
                    'width': width,
                    'height': height,
                    'codec': video_stream.get('codec_name', ''),
                    'fps': eval(video_stream.get('r_frame_rate', '0/1'))
                })
            
            # Audio stream information
            audio_streams = [s for s in video_info.get('streams', []) if s.get('codec_type') == 'audio']
            if audio_streams:
                audio_stream = audio_streams[0]
                metadata.update({
                    'audio_codec': audio_stream.get('codec_name', ''),
                    'sample_rate': int(audio_stream.get('sample_rate', 0)),
                    'channels': int(audio_stream.get('channels', 0))
                })
    
    except Exception as exc:
        logger.error(f"Error extracting video metadata: {str(exc)}")
        metadata['error'] = str(exc)
    
    return metadata


def get_audio_metadata(file_path: str) -> Dict[str, Union[str, int, float]]:
    """
    Extract audio-specific metadata.
    
    Args:
        file_path (str): Path to audio file
        
    Returns:
        dict: Audio metadata
    """
    metadata = {}
    
    try:
        cmd = [
            'ffprobe',
            '-v', 'quiet',
            '-print_format', 'json',
            '-show_format',
            '-show_streams',
            file_path
        ]
        
        process = subprocess.run(cmd, capture_output=True, text=True)
        
        if process.returncode == 0:
            audio_info = json.loads(process.stdout)
            
            # Format information
            format_info = audio_info.get('format', {})
            metadata.update({
                'duration': float(format_info.get('duration', 0)),
                'bitrate': int(format_info.get('bit_rate', 0))
            })
            
            # Audio stream information
            audio_streams = [s for s in audio_info.get('streams', []) if s.get('codec_type') == 'audio']
            if audio_streams:
                audio_stream = audio_streams[0]
                metadata.update({
                    'codec': audio_stream.get('codec_name', ''),
                    'sample_rate': int(audio_stream.get('sample_rate', 0)),
                    'channels': int(audio_stream.get('channels', 0))
                })
    
    except Exception as exc:
        logger.error(f"Error extracting audio metadata: {str(exc)}")
        metadata['error'] = str(exc)
    
    return metadata


# File Processing Functions
def optimize_image(file_path: str, quality: int = 85, max_width: int = 1920, max_height: int = 1080) -> Optional[str]:
    """
    Optimize image file for web delivery.
    
    Args:
        file_path (str): Path to image file
        quality (int): JPEG quality (1-100)
        max_width (int): Maximum width
        max_height (int): Maximum height
        
    Returns:
        str: Path to optimized image or None if no optimization needed
    """
    try:
        with Image.open(file_path) as img:
            original_size = os.path.getsize(file_path)
            width, height = img.size
            
            # Check if optimization is needed
            needs_resize = width > max_width or height > max_height
            needs_compression = original_size > 1024 * 1024  # 1MB
            
            if not needs_resize and not needs_compression:
                return None
            
            # Convert to RGB if necessary
            if img.mode in ('RGBA', 'LA', 'P'):
                img = img.convert('RGB')
            
            # Resize if needed
            if needs_resize:
                img.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)
            
            # Save optimized image
            optimized_path = f"/tmp/optimized_{hashlib.md5(file_path.encode()).hexdigest()}.jpg"
            
            # Apply additional optimizations
            img = ImageOps.exif_transpose(img)  # Fix orientation
            
            img.save(optimized_path, 'JPEG', quality=quality, optimize=True)
            
            optimized_size = os.path.getsize(optimized_path)
            compression_ratio = (original_size - optimized_size) / original_size * 100
            
            logger.info(f"Image optimized: {compression_ratio:.1f}% size reduction")
            return optimized_path
            
    except Exception as exc:
        logger.error(f"Error optimizing image: {str(exc)}")
        return None


def compress_video(file_path: str, target_bitrate: str = '2M', max_width: int = 1920) -> Optional[str]:
    """
    Compress video file using FFmpeg.
    
    Args:
        file_path (str): Path to video file
        target_bitrate (str): Target bitrate (e.g., '2M')
        max_width (int): Maximum width
        
    Returns:
        str: Path to compressed video or None if no compression needed
    """
    try:
        # Get video info first
        metadata = get_video_metadata(file_path)
        
        if 'error' in metadata:
            return None
        
        original_size = os.path.getsize(file_path)
        width = metadata.get('width', 0)
        
        # Check if compression is needed
        needs_compression = original_size > 10 * 1024 * 1024  # 10MB
        needs_resize = width > max_width
        
        if not needs_compression and not needs_resize:
            return None
        
        compressed_path = f"/tmp/compressed_{hashlib.md5(file_path.encode()).hexdigest()}.mp4"
        
        # Build FFmpeg command
        cmd = [
            'ffmpeg',
            '-i', file_path,
            '-c:v', 'libx264',
            '-b:v', target_bitrate,
            '-c:a', 'aac',
            '-b:a', '128k',
            '-movflags', '+faststart',
            '-y',  # Overwrite output file
            compressed_path
        ]
        
        # Add resize filter if needed
        if needs_resize:
            cmd.insert(-2, '-vf')
            cmd.insert(-2, f'scale={max_width}:-2')
        
        process = subprocess.run(cmd, capture_output=True, text=True)
        
        if process.returncode == 0 and os.path.exists(compressed_path):
            compressed_size = os.path.getsize(compressed_path)
            compression_ratio = (original_size - compressed_size) / original_size * 100
            
            logger.info(f"Video compressed: {compression_ratio:.1f}% size reduction")
            return compressed_path
        else:
            logger.error(f"FFmpeg error: {process.stderr}")
            return None
            
    except Exception as exc:
        logger.error(f"Error compressing video: {str(exc)}")
        return None


def generate_thumbnail(file_path: str, size: Tuple[int, int] = (300, 300)) -> Optional[str]:
    """
    Generate thumbnail for any supported file type.
    
    Args:
        file_path (str): Path to file
        size (tuple): Thumbnail size (width, height)
        
    Returns:
        str: Path to generated thumbnail or None if failed
    """
    try:
        file_ext = os.path.splitext(file_path)[1].lower()
        
        if file_ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp']:
            return generate_image_thumbnail(file_path, size)
        elif file_ext in ['.mp4', '.mov', '.avi', '.webm']:
            return generate_video_thumbnail(file_path)
        else:
            # Generate generic file icon thumbnail
            return generate_generic_thumbnail(file_ext, size)
            
    except Exception as exc:
        logger.error(f"Error generating thumbnail: {str(exc)}")
        return None


def generate_image_thumbnail(file_path: str, size: Tuple[int, int]) -> Optional[str]:
    """
    Generate thumbnail for image file.
    
    Args:
        file_path (str): Path to image file
        size (tuple): Thumbnail size
        
    Returns:
        str: Path to generated thumbnail
    """
    try:
        with Image.open(file_path) as img:
            # Convert to RGB if necessary
            if img.mode in ('RGBA', 'LA', 'P'):
                img = img.convert('RGB')
            
            # Generate thumbnail
            img.thumbnail(size, Image.Resampling.LANCZOS)
            
            # Save thumbnail
            thumb_path = f"/tmp/thumb_{hashlib.md5(file_path.encode()).hexdigest()}.jpg"
            img.save(thumb_path, 'JPEG', quality=85)
            
            return thumb_path
            
    except Exception as exc:
        logger.error(f"Error generating image thumbnail: {str(exc)}")
        return None


def generate_video_thumbnail(file_path: str, time_offset: str = '00:00:01') -> Optional[str]:
    """
    Generate thumbnail for video file.
    
    Args:
        file_path (str): Path to video file
        time_offset (str): Time offset for thumbnail
        
    Returns:
        str: Path to generated thumbnail
    """
    try:
        thumb_path = f"/tmp/thumb_{hashlib.md5(file_path.encode()).hexdigest()}.jpg"
        
        cmd = [
            'ffmpeg',
            '-i', file_path,
            '-ss', time_offset,
            '-vframes', '1',
            '-vf', 'scale=300:300:force_original_aspect_ratio=decrease',
            '-y',
            thumb_path
        ]
        
        process = subprocess.run(cmd, capture_output=True, text=True)
        
        if process.returncode == 0 and os.path.exists(thumb_path):
            return thumb_path
        else:
            logger.error(f"FFmpeg thumbnail error: {process.stderr}")
            return None
            
    except Exception as exc:
        logger.error(f"Error generating video thumbnail: {str(exc)}")
        return None


def generate_generic_thumbnail(file_ext: str, size: Tuple[int, int]) -> Optional[str]:
    """
    Generate generic file icon thumbnail.
    
    Args:
        file_ext (str): File extension
        size (tuple): Thumbnail size
        
    Returns:
        str: Path to generated thumbnail
    """
    try:
        # Create a simple colored rectangle with file extension
        img = Image.new('RGB', size, color='#f8f9fa')
        
        # Add file extension text (would need PIL font support)
        # For now, just return a solid color
        
        thumb_path = f"/tmp/thumb_generic_{file_ext[1:]}_{size[0]}x{size[1]}.jpg"
        img.save(thumb_path, 'JPEG', quality=85)
        
        return thumb_path
        
    except Exception as exc:
        logger.error(f"Error generating generic thumbnail: {str(exc)}")
        return None


# Utility Functions
def get_mime_type(file_path: str) -> str:
    """
    Get MIME type of file.
    
    Args:
        file_path (str): Path to file
        
    Returns:
        str: MIME type
    """
    try:
        # Try python-magic first (more accurate)
        if hasattr(magic, 'from_file'):
            return magic.from_file(file_path, mime=True)
        else:
            # Fallback to mimetypes
            mime_type, _ = mimetypes.guess_type(file_path)
            return mime_type or 'application/octet-stream'
    except Exception:
        # Final fallback
        mime_type, _ = mimetypes.guess_type(file_path)
        return mime_type or 'application/octet-stream'


def is_valid_mime_type(mime_type: str, file_ext: str) -> bool:
    """
    Check if MIME type matches file extension.
    
    Args:
        mime_type (str): MIME type
        file_ext (str): File extension
        
    Returns:
        bool: True if MIME type is valid for extension
    """
    valid_combinations = {
        '.jpg': ['image/jpeg'],
        '.jpeg': ['image/jpeg'],
        '.png': ['image/png'],
        '.gif': ['image/gif'],
        '.webp': ['image/webp'],
        '.mp4': ['video/mp4'],
        '.mov': ['video/quicktime'],
        '.avi': ['video/x-msvideo'],
        '.webm': ['video/webm'],
        '.mp3': ['audio/mpeg'],
        '.wav': ['audio/wav', 'audio/x-wav'],
        '.aac': ['audio/aac'],
        '.ogg': ['audio/ogg'],
        '.html': ['text/html'],
        '.htm': ['text/html']
    }
    
    return mime_type in valid_combinations.get(file_ext, [])


def calculate_file_hash(file_path: str, algorithm: str = 'md5') -> str:
    """
    Calculate file hash.
    
    Args:
        file_path (str): Path to file
        algorithm (str): Hash algorithm (md5, sha1, sha256)
        
    Returns:
        str: File hash
    """
    try:
        hash_func = getattr(hashlib, algorithm)()
        
        with open(file_path, 'rb') as f:
            for chunk in iter(lambda: f.read(4096), b''):
                hash_func.update(chunk)
        
        return hash_func.hexdigest()
        
    except Exception as exc:
        logger.error(f"Error calculating file hash: {str(exc)}")
        return ''


def get_file_dimensions(file_path: str) -> Optional[Tuple[int, int]]:
    """
    Get file dimensions for images and videos.
    
    Args:
        file_path (str): Path to file
        
    Returns:
        tuple: (width, height) or None if not applicable
    """
    try:
        file_ext = os.path.splitext(file_path)[1].lower()
        
        if file_ext in ['.jpg', '.jpeg', '.png', '.gif', '.webp']:
            with Image.open(file_path) as img:
                return img.size
        
        elif file_ext in ['.mp4', '.mov', '.avi', '.webm']:
            metadata = get_video_metadata(file_path)
            width = metadata.get('width')
            height = metadata.get('height')
            if width and height:
                return (width, height)
        
        return None
        
    except Exception as exc:
        logger.error(f"Error getting file dimensions: {str(exc)}")
        return None


def format_file_size(size_bytes: int) -> str:
    """
    Format file size in human-readable format.
    
    Args:
        size_bytes (int): Size in bytes
        
    Returns:
        str: Formatted size string
    """
    if size_bytes == 0:
        return '0 B'
    
    size_names = ['B', 'KB', 'MB', 'GB', 'TB']
    i = 0
    
    while size_bytes >= 1024 and i < len(size_names) - 1:
        size_bytes /= 1024.0
        i += 1
    
    return f'{size_bytes:.1f} {size_names[i]}'


def clean_filename(filename: str) -> str:
    """
    Clean filename for safe storage.
    
    Args:
        filename (str): Original filename
        
    Returns:
        str: Cleaned filename
    """
    # Remove path components
    filename = os.path.basename(filename)
    
    # Split name and extension
    name, ext = os.path.splitext(filename)
    
    # Clean the name part
    clean_name = slugify(name)
    
    # Ensure we have a name
    if not clean_name:
        clean_name = 'file'
    
    return f'{clean_name}{ext.lower()}'


def validate_file_size(file_size: int, max_size: int = 50 * 1024 * 1024) -> bool:
    """
    Validate file size against limit.
    
    Args:
        file_size (int): File size in bytes
        max_size (int): Maximum allowed size in bytes
        
    Returns:
        bool: True if file size is valid
    """
    return file_size <= max_size


def get_upload_path(instance, filename: str) -> str:
    """
    Generate upload path for creative files.
    
    Args:
        instance: Model instance
        filename (str): Original filename
        
    Returns:
        str: Upload path
    """
    # Clean filename
    clean_name = clean_filename(filename)
    
    # Generate path based on date and creative type
    date_path = datetime.now().strftime('%Y/%m/%d')
    creative_type = getattr(instance, 'creative_type', 'unknown')
    
    return f'creatives/{creative_type}/{date_path}/{clean_name}'


def get_thumbnail_upload_path(instance, filename: str) -> str:
    """
    Generate upload path for thumbnail files.
    
    Args:
        instance: Model instance
        filename (str): Original filename
        
    Returns:
        str: Upload path
    """
    clean_name = clean_filename(filename)
    date_path = datetime.now().strftime('%Y/%m/%d')
    
    return f'creatives/thumbnails/{date_path}/{clean_name}'