"""Django signals for adspots app.

This module provides signal handlers for:
- Automatic file processing when adspots are created/updated
- File cleanup when adspots are deleted
- Status change notifications
- Audit logging
"""

import os
from django.db.models.signals import post_save, post_delete, pre_delete
from django.dispatch import receiver
from django.core.files.storage import default_storage
from django.utils import timezone
from django.contrib.auth import get_user_model

from .models import Adspot, Avail, Window, AdspotInAvail, Pending
from .tasks import process_adspot_file, generate_adspot_thumbnails

User = get_user_model()


@receiver(post_save, sender=Adspot)
def adspot_post_save(sender, instance, created, **kwargs):
    """Handle adspot creation and updates.
    
    Triggers:
    - File processing for new adspots with files
    - Reprocessing when file is updated
    - Thumbnail generation for video files
    """
    # Only process if adspot has a file and is not already processed
    if instance.original_file and not instance.is_processed:
        # Check if this is a new file or file was updated
        should_process = False
        
        if created:
            # New adspot with file
            should_process = True
        else:
            # Check if file was updated by comparing with previous state
            try:
                old_instance = Adspot.objects.get(pk=instance.pk)
                if (old_instance.original_file != instance.original_file or 
                    instance.processing_status == 'pending'):
                    should_process = True
            except Adspot.DoesNotExist:
                should_process = True
        
        if should_process:
            # Reset processing status
            if instance.processing_status != 'pending':
                instance.processing_status = 'pending'
                instance.error_message = None
                instance.is_processed = False
                # Use update to avoid triggering signal again
                Adspot.objects.filter(pk=instance.pk).update(
                    processing_status='pending',
                    error_message=None,
                    is_processed=False,
                    updated_at=timezone.now()
                )
            
            # Trigger async processing
            process_adspot_file.delay(instance.id)
            
            # Generate thumbnails for video files
            if instance.format in ['mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv']:
                generate_adspot_thumbnails.delay(instance.id)


@receiver(pre_delete, sender=Adspot)
def adspot_pre_delete(sender, instance, **kwargs):
    """Handle adspot deletion - clean up files."""
    # Store file paths for cleanup
    files_to_delete = []
    
    if instance.original_file:
        files_to_delete.append(instance.original_file.name)
    
    if instance.encoded_file:
        files_to_delete.append(instance.encoded_file.name)
    
    # Store in instance for post_delete signal
    instance._files_to_delete = files_to_delete


@receiver(post_delete, sender=Adspot)
def adspot_post_delete(sender, instance, **kwargs):
    """Clean up files after adspot deletion."""
    # Delete files from storage
    if hasattr(instance, '_files_to_delete'):
        for file_name in instance._files_to_delete:
            try:
                if default_storage.exists(file_name):
                    default_storage.delete(file_name)
            except Exception as e:
                # Log error but don't raise exception
                import logging
                logger = logging.getLogger(__name__)
                logger.error(f"Error deleting file {file_name}: {str(e)}")
    
    # Clean up directory if empty
    try:
        import os
        from django.conf import settings
        
        media_root = getattr(settings, 'MEDIA_ROOT', None)
        if media_root:
            adspot_dir = os.path.join(media_root, 'adspots', str(instance.id))
            if os.path.exists(adspot_dir) and not os.listdir(adspot_dir):
                os.rmdir(adspot_dir)
    except Exception:
        pass  # Ignore cleanup errors


@receiver(post_save, sender=Avail)
def avail_post_save(sender, instance, created, **kwargs):
    """Handle avail creation and updates.
    
    Triggers:
    - Validation of time slots
    - Notification to relevant users
    """
    if created:
        # Log avail creation
        import logging
        logger = logging.getLogger(__name__)
        logger.info(
            f"New avail created: {instance.name} for channel {instance.channel.name} "
            f"from {instance.start_time} to {instance.end_time}"
        )
        
        # Validate that avail doesn't overlap with existing ones
        overlapping_avails = Avail.objects.filter(
            channel=instance.channel,
            start_time__lt=instance.end_time,
            end_time__gt=instance.start_time
        ).exclude(id=instance.id)
        
        if overlapping_avails.exists():
            logger.warning(
                f"Avail {instance.id} overlaps with existing avails: "
                f"{list(overlapping_avails.values_list('id', flat=True))}"
            )


@receiver(post_save, sender=Window)
def window_post_save(sender, instance, created, **kwargs):
    """Handle window creation and updates."""
    if created:
        import logging
        logger = logging.getLogger(__name__)
        logger.info(
            f"New window created: {instance.name} "
            f"({instance.start_time} - {instance.end_time})"
        )


@receiver(post_save, sender=AdspotInAvail)
def adspot_in_avail_post_save(sender, instance, created, **kwargs):
    """Handle adspot placement in avail.
    
    Triggers:
    - Validation of placement constraints
    - Update of avail utilization
    - Campaign performance tracking
    """
    if created:
        import logging
        logger = logging.getLogger(__name__)
        logger.info(
            f"Adspot {instance.adspot.name} placed in avail {instance.avail.name} "
            f"at position {instance.position}"
        )
        
        # Update avail utilization
        try:
            avail = instance.avail
            total_duration = sum(
                AdspotInAvail.objects.filter(avail=avail).values_list(
                    'adspot__duration_seconds', flat=True
                )
            )
            
            # Calculate utilization percentage
            if avail.duration_seconds > 0:
                utilization = (total_duration / avail.duration_seconds) * 100
                
                # Update avail if utilization changed significantly
                if abs(avail.utilization - utilization) > 1:  # 1% threshold
                    Avail.objects.filter(pk=avail.pk).update(
                        utilization=utilization,
                        updated_at=timezone.now()
                    )
                    
                    if utilization > 100:
                        logger.warning(
                            f"Avail {avail.id} is over-utilized: {utilization:.1f}%"
                        )
        
        except Exception as e:
            logger.error(f"Error updating avail utilization: {str(e)}")
        
        # Update campaign performance if applicable
        try:
            if hasattr(instance.adspot, 'campaign') and instance.adspot.campaign:
                campaign = instance.adspot.campaign
                
                # Increment scheduled impressions
                if hasattr(campaign, 'current_impressions'):
                    campaign.current_impressions += 1
                    campaign.save(update_fields=['current_impressions', 'updated_at'])
        
        except Exception as e:
            logger.error(f"Error updating campaign performance: {str(e)}")


@receiver(post_delete, sender=AdspotInAvail)
def adspot_in_avail_post_delete(sender, instance, **kwargs):
    """Handle adspot removal from avail.
    
    Updates:
    - Avail utilization
    - Campaign performance
    """
    import logging
    logger = logging.getLogger(__name__)
    
    logger.info(
        f"Adspot {instance.adspot.name} removed from avail {instance.avail.name}"
    )
    
    # Update avail utilization
    try:
        avail = instance.avail
        remaining_adspots = AdspotInAvail.objects.filter(avail=avail)
        
        if remaining_adspots.exists():
            total_duration = sum(
                remaining_adspots.values_list('adspot__duration_seconds', flat=True)
            )
            utilization = (total_duration / avail.duration_seconds) * 100 if avail.duration_seconds > 0 else 0
        else:
            utilization = 0
        
        Avail.objects.filter(pk=avail.pk).update(
            utilization=utilization,
            updated_at=timezone.now()
        )
    
    except Exception as e:
        logger.error(f"Error updating avail utilization after removal: {str(e)}")
    
    # Update campaign performance
    try:
        if hasattr(instance.adspot, 'campaign') and instance.adspot.campaign:
            campaign = instance.adspot.campaign
            
            if hasattr(campaign, 'current_impressions') and campaign.current_impressions > 0:
                campaign.current_impressions -= 1
                campaign.save(update_fields=['current_impressions', 'updated_at'])
    
    except Exception as e:
        logger.error(f"Error updating campaign performance after removal: {str(e)}")


@receiver(post_save, sender=Pending)
def pending_post_save(sender, instance, created, **kwargs):
    """Handle pending creative status changes.
    
    Triggers:
    - Notifications when status changes
    - Automatic processing when approved
    """
    if not created:
        # Check if status changed
        try:
            old_instance = sender.objects.get(pk=instance.pk)
            if old_instance.status != instance.status:
                import logging
                logger = logging.getLogger(__name__)
                
                logger.info(
                    f"Pending creative {instance.id} status changed from "
                    f"{old_instance.status} to {instance.status}"
                )
                
                # If approved, trigger processing
                if instance.status == 'approved' and instance.creative_file:
                    # Create adspot from approved pending creative
                    try:
                        adspot = Adspot.objects.create(
                            name=instance.name or f"Approved Creative {instance.id}",
                            description=instance.description,
                            original_file=instance.creative_file,
                            advertiser=instance.advertiser,
                            campaign=getattr(instance, 'campaign', None),
                            created_by=instance.created_by
                        )
                        
                        logger.info(
                            f"Created adspot {adspot.id} from approved pending creative {instance.id}"
                        )
                        
                        # Update pending with reference to created adspot
                        instance.notes = f"Approved and converted to Adspot ID: {adspot.id}"
                        instance.save(update_fields=['notes', 'updated_at'])
                    
                    except Exception as e:
                        logger.error(
                            f"Error creating adspot from pending creative {instance.id}: {str(e)}"
                        )
                        
                        # Update pending with error
                        instance.status = 'rejected'
                        instance.notes = f"Error during approval: {str(e)}"
                        instance.save(update_fields=['status', 'notes', 'updated_at'])
        
        except sender.DoesNotExist:
            pass  # Old instance not found, probably a new record
        except Exception as e:
            import logging
            logger = logging.getLogger(__name__)
            logger.error(f"Error in pending_post_save signal: {str(e)}")


@receiver(pre_delete, sender=Pending)
def pending_pre_delete(sender, instance, **kwargs):
    """Handle pending creative deletion - clean up files."""
    if instance.creative_file:
        instance._file_to_delete = instance.creative_file.name


@receiver(post_delete, sender=Pending)
def pending_post_delete(sender, instance, **kwargs):
    """Clean up files after pending creative deletion."""
    if hasattr(instance, '_file_to_delete'):
        try:
            if default_storage.exists(instance._file_to_delete):
                default_storage.delete(instance._file_to_delete)
        except Exception as e:
            import logging
            logger = logging.getLogger(__name__)
            logger.error(f"Error deleting pending file {instance._file_to_delete}: {str(e)}")


# Custom signal for adspot processing completion
from django.dispatch import Signal

adspot_processing_completed = Signal()
adspot_processing_failed = Signal()


@receiver(adspot_processing_completed)
def handle_adspot_processing_completed(sender, adspot_id, **kwargs):
    """Handle successful adspot processing completion."""
    import logging
    logger = logging.getLogger(__name__)
    
    try:
        adspot = Adspot.objects.get(id=adspot_id)
        logger.info(f"Adspot {adspot_id} processing completed successfully")
        
        # Send notification to relevant users
        # This could be extended to send emails, push notifications, etc.
        
        # Update any related campaigns
        if hasattr(adspot, 'campaign') and adspot.campaign:
            campaign = adspot.campaign
            # Update campaign status or metrics as needed
            
    except Adspot.DoesNotExist:
        logger.error(f"Adspot {adspot_id} not found for processing completion")
    except Exception as e:
        logger.error(f"Error handling adspot processing completion: {str(e)}")


@receiver(adspot_processing_failed)
def handle_adspot_processing_failed(sender, adspot_id, error_message, **kwargs):
    """Handle failed adspot processing."""
    import logging
    logger = logging.getLogger(__name__)
    
    try:
        adspot = Adspot.objects.get(id=adspot_id)
        logger.error(f"Adspot {adspot_id} processing failed: {error_message}")
        
        # Send notification to relevant users about the failure
        # This could be extended to send emails, alerts, etc.
        
        # Update adspot status
        adspot.processing_status = 'failed'
        adspot.error_message = error_message
        adspot.save(update_fields=['processing_status', 'error_message', 'updated_at'])
        
    except Adspot.DoesNotExist:
        logger.error(f"Adspot {adspot_id} not found for processing failure")
    except Exception as e:
        logger.error(f"Error handling adspot processing failure: {str(e)}")