"""Celery tasks for adspots app.

This module provides asynchronous tasks for:
- File processing and encoding
- Media analysis and metadata extraction
- VAST URL validation
- Cleanup operations
"""

import os
import hashlib
import subprocess
import tempfile
from datetime import datetime, timedelta
from django.conf import settings
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.utils import timezone
from celery import shared_task
from celery.utils.log import get_task_logger

logger = get_task_logger(__name__)


@shared_task(bind=True, max_retries=3)
def process_adspot_file(self, adspot_id):
    """Process adspot file: extract metadata, encode, and validate.
    
    Args:
        adspot_id (int): ID of the adspot to process
    
    Returns:
        dict: Processing results
    """
    from .models import Adspot
    
    try:
        adspot = Adspot.objects.get(id=adspot_id)
        logger.info(f"Starting processing for adspot {adspot_id}: {adspot.name}")
        
        # Update status to processing
        adspot.processing_status = 'processing'
        adspot.error_message = None
        adspot.save()
        
        if not adspot.original_file:
            raise ValueError("No original file found")
        
        # Extract metadata
        metadata = extract_media_metadata(adspot.original_file.path)
        
        # Update adspot with metadata
        adspot.duration = metadata.get('duration')
        adspot.format = metadata.get('format')
        adspot.resolution = metadata.get('resolution')
        adspot.bitrate = metadata.get('bitrate')
        adspot.frame_rate = metadata.get('frame_rate')
        adspot.audio_codec = metadata.get('audio_codec')
        adspot.video_codec = metadata.get('video_codec')
        
        # Encode file if needed
        if should_encode_file(metadata):
            encoded_file_path = encode_media_file(
                adspot.original_file.path,
                adspot_id
            )
            
            if encoded_file_path:
                # Save encoded file
                with open(encoded_file_path, 'rb') as f:
                    encoded_content = ContentFile(f.read())
                    encoded_filename = f"encoded_{adspot.id}_{adspot.filename}"
                    adspot.encoded_file.save(encoded_filename, encoded_content)
                
                # Clean up temporary file
                os.remove(encoded_file_path)
        
        # Validate VAST URL if provided
        if adspot.vast_url:
            vast_validation = validate_vast_url(adspot.vast_url)
            if not vast_validation['valid']:
                logger.warning(
                    f"VAST URL validation failed for adspot {adspot_id}: "
                    f"{vast_validation['error']}"
                )
        
        # Mark as processed
        adspot.is_processed = True
        adspot.processing_status = 'completed'
        adspot.save()
        
        logger.info(f"Successfully processed adspot {adspot_id}")
        
        return {
            'success': True,
            'adspot_id': adspot_id,
            'metadata': metadata,
            'message': 'File processed successfully'
        }
        
    except Adspot.DoesNotExist:
        error_msg = f"Adspot with ID {adspot_id} not found"
        logger.error(error_msg)
        return {'success': False, 'error': error_msg}
        
    except Exception as exc:
        error_msg = f"Error processing adspot {adspot_id}: {str(exc)}"
        logger.error(error_msg)
        
        # Update adspot with error
        try:
            adspot = Adspot.objects.get(id=adspot_id)
            adspot.processing_status = 'failed'
            adspot.error_message = str(exc)
            adspot.save()
        except Adspot.DoesNotExist:
            pass
        
        # Retry if not max retries reached
        if self.request.retries < self.max_retries:
            logger.info(f"Retrying processing for adspot {adspot_id} (attempt {self.request.retries + 1})")
            raise self.retry(countdown=60 * (2 ** self.request.retries), exc=exc)
        
        return {'success': False, 'error': error_msg}


def extract_media_metadata(file_path):
    """Extract metadata from media file using ffprobe.
    
    Args:
        file_path (str): Path to the media file
    
    Returns:
        dict: Extracted metadata
    """
    metadata = {
        'duration': None,
        'format': None,
        'resolution': None,
        'bitrate': None,
        'frame_rate': None,
        'audio_codec': None,
        'video_codec': None,
    }
    
    try:
        # Use ffprobe to extract metadata
        cmd = [
            'ffprobe',
            '-v', 'quiet',
            '-print_format', 'json',
            '-show_format',
            '-show_streams',
            file_path
        ]
        
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=30
        )
        
        if result.returncode == 0:
            import json
            data = json.loads(result.stdout)
            
            # Extract format information
            if 'format' in data:
                format_info = data['format']
                
                # Duration
                if 'duration' in format_info:
                    duration_seconds = float(format_info['duration'])
                    hours = int(duration_seconds // 3600)
                    minutes = int((duration_seconds % 3600) // 60)
                    seconds = int(duration_seconds % 60)
                    metadata['duration'] = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
                
                # Format
                if 'format_name' in format_info:
                    format_name = format_info['format_name'].split(',')[0]
                    metadata['format'] = format_name
                
                # Bitrate
                if 'bit_rate' in format_info:
                    metadata['bitrate'] = int(float(format_info['bit_rate']) / 1000)  # Convert to kbps
            
            # Extract stream information
            if 'streams' in data:
                for stream in data['streams']:
                    if stream.get('codec_type') == 'video':
                        # Video codec
                        metadata['video_codec'] = stream.get('codec_name')
                        
                        # Resolution
                        width = stream.get('width')
                        height = stream.get('height')
                        if width and height:
                            metadata['resolution'] = f"{width}x{height}"
                        
                        # Frame rate
                        if 'r_frame_rate' in stream:
                            frame_rate_str = stream['r_frame_rate']
                            if '/' in frame_rate_str:
                                num, den = map(int, frame_rate_str.split('/'))
                                if den != 0:
                                    metadata['frame_rate'] = round(num / den, 2)
                    
                    elif stream.get('codec_type') == 'audio':
                        # Audio codec
                        metadata['audio_codec'] = stream.get('codec_name')
        
        else:
            logger.warning(f"ffprobe failed for {file_path}: {result.stderr}")
    
    except subprocess.TimeoutExpired:
        logger.error(f"ffprobe timeout for {file_path}")
    except Exception as e:
        logger.error(f"Error extracting metadata from {file_path}: {str(e)}")
    
    return metadata


def should_encode_file(metadata):
    """Determine if file should be encoded based on metadata.
    
    Args:
        metadata (dict): File metadata
    
    Returns:
        bool: True if file should be encoded
    """
    # Define encoding criteria
    target_formats = ['mp4', 'webm']
    max_bitrate = 5000  # kbps
    target_resolutions = ['1920x1080', '1280x720', '854x480']
    
    # Check if format needs encoding
    current_format = metadata.get('format', '').lower()
    if current_format not in target_formats:
        return True
    
    # Check if bitrate is too high
    current_bitrate = metadata.get('bitrate')
    if current_bitrate and current_bitrate > max_bitrate:
        return True
    
    # Check if resolution needs adjustment
    current_resolution = metadata.get('resolution')
    if current_resolution and current_resolution not in target_resolutions:
        # Check if resolution is higher than target
        try:
            width, height = map(int, current_resolution.split('x'))
            if width > 1920 or height > 1080:
                return True
        except (ValueError, AttributeError):
            pass
    
    return False


def encode_media_file(input_path, adspot_id):
    """Encode media file to target format.
    
    Args:
        input_path (str): Path to input file
        adspot_id (int): Adspot ID for naming
    
    Returns:
        str: Path to encoded file or None if failed
    """
    try:
        # Create temporary output file
        with tempfile.NamedTemporaryFile(
            suffix='.mp4',
            delete=False,
            prefix=f'adspot_{adspot_id}_'
        ) as temp_file:
            output_path = temp_file.name
        
        # FFmpeg encoding command
        cmd = [
            'ffmpeg',
            '-i', input_path,
            '-c:v', 'libx264',
            '-preset', 'medium',
            '-crf', '23',
            '-maxrate', '4000k',
            '-bufsize', '8000k',
            '-vf', 'scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2',
            '-c:a', 'aac',
            '-b:a', '128k',
            '-movflags', '+faststart',
            '-y',  # Overwrite output file
            output_path
        ]
        
        logger.info(f"Starting encoding for adspot {adspot_id}")
        
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=1800  # 30 minutes timeout
        )
        
        if result.returncode == 0:
            logger.info(f"Successfully encoded adspot {adspot_id}")
            return output_path
        else:
            logger.error(f"Encoding failed for adspot {adspot_id}: {result.stderr}")
            # Clean up failed output file
            if os.path.exists(output_path):
                os.remove(output_path)
            return None
    
    except subprocess.TimeoutExpired:
        logger.error(f"Encoding timeout for adspot {adspot_id}")
        if os.path.exists(output_path):
            os.remove(output_path)
        return None
    except Exception as e:
        logger.error(f"Error encoding adspot {adspot_id}: {str(e)}")
        return None


def validate_vast_url(vast_url):
    """Validate VAST URL by making a request.
    
    Args:
        vast_url (str): VAST URL to validate
    
    Returns:
        dict: Validation result
    """
    try:
        import requests
        from xml.etree import ElementTree as ET
        
        # Make request to VAST URL
        response = requests.get(vast_url, timeout=10)
        response.raise_for_status()
        
        # Try to parse as XML
        try:
            root = ET.fromstring(response.content)
            
            # Basic VAST validation
            if root.tag.lower() in ['vast', 'videoadservingtemplate']:
                return {
                    'valid': True,
                    'message': 'VAST URL is valid'
                }
            else:
                return {
                    'valid': False,
                    'error': 'Response is not a valid VAST document'
                }
        
        except ET.ParseError as e:
            return {
                'valid': False,
                'error': f'Invalid XML format: {str(e)}'
            }
    
    except requests.RequestException as e:
        return {
            'valid': False,
            'error': f'Request failed: {str(e)}'
        }
    except Exception as e:
        return {
            'valid': False,
            'error': f'Validation error: {str(e)}'
        }


@shared_task
def cleanup_old_files():
    """Clean up old temporary and processed files.
    
    This task should be run periodically to clean up:
    - Failed processing files
    - Old temporary files
    - Orphaned encoded files
    """
    from .models import Adspot
    
    logger.info("Starting cleanup of old files")
    
    cleaned_count = 0
    
    try:
        # Clean up failed processing files older than 7 days
        cutoff_date = timezone.now() - timedelta(days=7)
        failed_adspots = Adspot.objects.filter(
            processing_status='failed',
            updated_at__lt=cutoff_date
        )
        
        for adspot in failed_adspots:
            try:
                # Remove encoded file if exists
                if adspot.encoded_file:
                    default_storage.delete(adspot.encoded_file.name)
                    adspot.encoded_file = None
                    adspot.save()
                    cleaned_count += 1
                    logger.info(f"Cleaned up failed adspot {adspot.id}")
            except Exception as e:
                logger.error(f"Error cleaning up adspot {adspot.id}: {str(e)}")
        
        # Clean up orphaned files in media directory
        media_root = getattr(settings, 'MEDIA_ROOT', None)
        if media_root:
            adspots_dir = os.path.join(media_root, 'adspots')
            if os.path.exists(adspots_dir):
                # Get all adspot IDs from database
                existing_ids = set(
                    Adspot.objects.values_list('id', flat=True)
                )
                
                # Check each directory in adspots folder
                for item in os.listdir(adspots_dir):
                    item_path = os.path.join(adspots_dir, item)
                    if os.path.isdir(item_path):
                        try:
                            adspot_id = int(item)
                            if adspot_id not in existing_ids:
                                # Remove orphaned directory
                                import shutil
                                shutil.rmtree(item_path)
                                cleaned_count += 1
                                logger.info(f"Removed orphaned directory: {item_path}")
                        except ValueError:
                            # Not a valid adspot ID directory
                            continue
                        except Exception as e:
                            logger.error(f"Error removing directory {item_path}: {str(e)}")
        
        logger.info(f"Cleanup completed. Cleaned {cleaned_count} items.")
        
        return {
            'success': True,
            'cleaned_count': cleaned_count,
            'message': f'Cleaned up {cleaned_count} items'
        }
    
    except Exception as e:
        error_msg = f"Error during cleanup: {str(e)}"
        logger.error(error_msg)
        return {
            'success': False,
            'error': error_msg
        }


@shared_task
def reprocess_failed_adspots():
    """Reprocess adspots that failed processing.
    
    This task can be run to retry processing of failed adspots.
    """
    from .models import Adspot
    
    logger.info("Starting reprocessing of failed adspots")
    
    # Get failed adspots that haven't been retried recently
    cutoff_date = timezone.now() - timedelta(hours=1)
    failed_adspots = Adspot.objects.filter(
        processing_status='failed',
        updated_at__lt=cutoff_date
    )[:10]  # Limit to 10 at a time
    
    reprocessed_count = 0
    
    for adspot in failed_adspots:
        try:
            # Reset status and trigger reprocessing
            adspot.processing_status = 'pending'
            adspot.error_message = None
            adspot.save()
            
            # Trigger processing task
            process_adspot_file.delay(adspot.id)
            reprocessed_count += 1
            
            logger.info(f"Queued reprocessing for adspot {adspot.id}")
        
        except Exception as e:
            logger.error(f"Error queuing reprocessing for adspot {adspot.id}: {str(e)}")
    
    logger.info(f"Queued {reprocessed_count} adspots for reprocessing")
    
    return {
        'success': True,
        'reprocessed_count': reprocessed_count,
        'message': f'Queued {reprocessed_count} adspots for reprocessing'
    }


@shared_task
def generate_adspot_thumbnails(adspot_id):
    """Generate thumbnails for video adspots.
    
    Args:
        adspot_id (int): ID of the adspot
    
    Returns:
        dict: Generation result
    """
    from .models import Adspot
    
    try:
        adspot = Adspot.objects.get(id=adspot_id)
        
        if not adspot.original_file:
            return {'success': False, 'error': 'No original file found'}
        
        # Check if it's a video file
        video_formats = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv']
        if adspot.format not in video_formats:
            return {'success': False, 'error': 'Not a video file'}
        
        # Generate thumbnail using ffmpeg
        with tempfile.NamedTemporaryFile(
            suffix='.jpg',
            delete=False,
            prefix=f'thumb_{adspot_id}_'
        ) as temp_file:
            thumbnail_path = temp_file.name
        
        cmd = [
            'ffmpeg',
            '-i', adspot.original_file.path,
            '-ss', '00:00:01',  # Take screenshot at 1 second
            '-vframes', '1',
            '-vf', 'scale=320:240:force_original_aspect_ratio=decrease,pad=320:240:(ow-iw)/2:(oh-ih)/2',
            '-y',
            thumbnail_path
        ]
        
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            timeout=30
        )
        
        if result.returncode == 0 and os.path.exists(thumbnail_path):
            # Save thumbnail (this would require adding a thumbnail field to the model)
            # For now, just clean up
            os.remove(thumbnail_path)
            
            logger.info(f"Generated thumbnail for adspot {adspot_id}")
            return {
                'success': True,
                'message': 'Thumbnail generated successfully'
            }
        else:
            if os.path.exists(thumbnail_path):
                os.remove(thumbnail_path)
            return {
                'success': False,
                'error': f'Thumbnail generation failed: {result.stderr}'
            }
    
    except Adspot.DoesNotExist:
        return {'success': False, 'error': 'Adspot not found'}
    except Exception as e:
        return {'success': False, 'error': str(e)}