"""Channels Signals

This module contains Django signal handlers for the channels app.
It manages automated tasks, logging, notifications, and data integrity
for channel-related operations.

Signal Handlers:
    - Channel signals: Creation, updates, deletion
    - ChannelZone signals: Zone management
    - ChannelCodec signals: Codec configuration
    - Jingle signals: Audio file processing
    - DayTime signals: Time slot management

Features:
- Automated logging of all operations
- Email notifications for important events
- File processing and validation
- Cache invalidation
- Data integrity checks
- Audit trail maintenance
"""

import os
import logging
from django.db.models.signals import (
    post_save, post_delete, pre_save, pre_delete, m2m_changed
)
from django.dispatch import receiver
from django.core.mail import send_mail
from django.conf import settings
from django.core.cache import cache
from django.utils import timezone
from django.contrib.auth import get_user_model
from django.template.loader import render_to_string
from django.utils.html import strip_tags

from .models import Channel, ChannelZone, ChannelCodec, Jingle, DayTime
from apps.core.utils import get_current_user

# Get logger for channels app
logger = logging.getLogger('channels')
User = get_user_model()


# ============================================================================
# Channel Signals
# ============================================================================

@receiver(pre_save, sender=Channel)
def channel_pre_save(sender, instance, **kwargs):
    """Handle channel pre-save operations.
    
    - Validate channel configuration
    - Set default values
    - Check for duplicates
    """
    try:
        # Set default values if not provided
        if not instance.channel_code:
            instance.channel_code = instance.name.upper().replace(' ', '_')
        
        # Validate unique channel code
        if Channel.objects.filter(
            channel_code=instance.channel_code
        ).exclude(pk=instance.pk).exists():
            logger.warning(
                f"Duplicate channel code detected: {instance.channel_code}"
            )
        
        # Set audit fields
        current_user = get_current_user()
        if current_user:
            if not instance.pk:  # New instance
                instance.created_by = current_user
            instance.updated_by = current_user
        
        logger.info(
            f"Channel pre-save: {instance.name} ({instance.channel_code})"
        )
        
    except Exception as e:
        logger.error(f"Error in channel pre-save: {str(e)}")


@receiver(post_save, sender=Channel)
def channel_post_save(sender, instance, created, **kwargs):
    """Handle channel post-save operations.
    
    - Send notifications
    - Create default configurations
    - Update cache
    - Log operations
    """
    try:
        if created:
            # Log channel creation
            logger.info(
                f"New channel created: {instance.name} (ID: {instance.pk})"
            )
            
            # Create default codec configuration
            if not instance.codecs.exists():
                ChannelCodec.objects.create(
                    channel=instance,
                    quality_level='hd',
                    video_codec=instance.default_codec or 'h264',
                    audio_codec=instance.audio_codec or 'aac',
                    bitrate=5000,
                    resolution=instance.resolution or '1920x1080',
                    is_default=True,
                    status='active'
                )
                logger.info(f"Default codec created for channel: {instance.name}")
            
            # Send notification email for new channel
            if hasattr(settings, 'CHANNEL_NOTIFICATION_EMAILS'):
                send_channel_notification(
                    instance, 
                    'created',
                    settings.CHANNEL_NOTIFICATION_EMAILS
                )
        else:
            # Log channel update
            logger.info(
                f"Channel updated: {instance.name} (ID: {instance.pk})"
            )
        
        # Invalidate related cache
        cache_keys = [
            f'channel_{instance.pk}',
            f'channel_list_{instance.provider}',
            'active_channels',
            'channel_stats'
        ]
        cache.delete_many(cache_keys)
        
    except Exception as e:
        logger.error(f"Error in channel post-save: {str(e)}")


@receiver(pre_delete, sender=Channel)
def channel_pre_delete(sender, instance, **kwargs):
    """Handle channel pre-delete operations.
    
    - Archive related data
    - Check dependencies
    - Log deletion attempt
    """
    try:
        logger.warning(
            f"Channel deletion initiated: {instance.name} (ID: {instance.pk})"
        )
        
        # Check for active campaigns or content
        # This would check related models in other apps
        # For now, we'll just log the deletion
        
        # Archive jingle files before deletion
        for jingle in instance.jingles.all():
            if jingle.audio_file:
                # Move file to archive location
                archive_jingle_file(jingle)
        
    except Exception as e:
        logger.error(f"Error in channel pre-delete: {str(e)}")


@receiver(post_delete, sender=Channel)
def channel_post_delete(sender, instance, **kwargs):
    """Handle channel post-delete operations.
    
    - Clean up files
    - Send notifications
    - Update cache
    """
    try:
        logger.warning(
            f"Channel deleted: {instance.name} (ID: {instance.pk})"
        )
        
        # Clean up logo file
        if instance.logo:
            if os.path.isfile(instance.logo.path):
                os.remove(instance.logo.path)
                logger.info(f"Logo file deleted: {instance.logo.path}")
        
        # Send deletion notification
        if hasattr(settings, 'CHANNEL_NOTIFICATION_EMAILS'):
            send_channel_notification(
                instance, 
                'deleted',
                settings.CHANNEL_NOTIFICATION_EMAILS
            )
        
        # Invalidate cache
        cache.clear()  # Clear all cache for safety
        
    except Exception as e:
        logger.error(f"Error in channel post-delete: {str(e)}")


# ============================================================================
# ChannelZone Signals
# ============================================================================

@receiver(post_save, sender=ChannelZone)
def channel_zone_post_save(sender, instance, created, **kwargs):
    """Handle channel zone post-save operations."""
    try:
        if created:
            logger.info(
                f"New zone created: {instance.zone_name} for {instance.channel.name}"
            )
            
            # If this is marked as primary, ensure no other zones are primary
            if instance.is_primary:
                ChannelZone.objects.filter(
                    channel=instance.channel,
                    is_primary=True
                ).exclude(pk=instance.pk).update(is_primary=False)
                
                logger.info(
                    f"Primary zone set: {instance.zone_name} for {instance.channel.name}"
                )
        
        # Invalidate channel cache
        cache.delete(f'channel_{instance.channel.pk}_zones')
        
    except Exception as e:
        logger.error(f"Error in channel zone post-save: {str(e)}")


@receiver(post_delete, sender=ChannelZone)
def channel_zone_post_delete(sender, instance, **kwargs):
    """Handle channel zone post-delete operations."""
    try:
        logger.info(
            f"Zone deleted: {instance.zone_name} from {instance.channel.name}"
        )
        
        # If deleted zone was primary, set another zone as primary
        if instance.is_primary:
            next_zone = ChannelZone.objects.filter(
                channel=instance.channel,
                status='active'
            ).first()
            
            if next_zone:
                next_zone.is_primary = True
                next_zone.save()
                logger.info(
                    f"New primary zone set: {next_zone.zone_name} for {instance.channel.name}"
                )
        
        # Invalidate cache
        cache.delete(f'channel_{instance.channel.pk}_zones')
        
    except Exception as e:
        logger.error(f"Error in channel zone post-delete: {str(e)}")


# ============================================================================
# ChannelCodec Signals
# ============================================================================

@receiver(post_save, sender=ChannelCodec)
def channel_codec_post_save(sender, instance, created, **kwargs):
    """Handle channel codec post-save operations."""
    try:
        if created:
            logger.info(
                f"New codec created: {instance.quality_level} for {instance.channel.name}"
            )
        
        # If this is marked as default, ensure no other codecs are default
        if instance.is_default:
            ChannelCodec.objects.filter(
                channel=instance.channel,
                is_default=True
            ).exclude(pk=instance.pk).update(is_default=False)
            
            logger.info(
                f"Default codec set: {instance.quality_level} for {instance.channel.name}"
            )
        
        # Invalidate cache
        cache.delete(f'channel_{instance.channel.pk}_codecs')
        
    except Exception as e:
        logger.error(f"Error in channel codec post-save: {str(e)}")


# ============================================================================
# Jingle Signals
# ============================================================================

@receiver(pre_save, sender=Jingle)
def jingle_pre_save(sender, instance, **kwargs):
    """Handle jingle pre-save operations.
    
    - Process audio file
    - Extract metadata
    - Validate file format
    """
    try:
        # If audio file is being updated
        if instance.audio_file:
            # Extract audio metadata
            extract_audio_metadata(instance)
            
            # Validate audio format
            if not validate_audio_file(instance.audio_file):
                logger.error(
                    f"Invalid audio file format for jingle: {instance.name}"
                )
        
        # Set audit fields
        current_user = get_current_user()
        if current_user:
            if not instance.pk:
                instance.created_by = current_user
            instance.updated_by = current_user
        
    except Exception as e:
        logger.error(f"Error in jingle pre-save: {str(e)}")


@receiver(post_save, sender=Jingle)
def jingle_post_save(sender, instance, created, **kwargs):
    """Handle jingle post-save operations."""
    try:
        if created:
            logger.info(
                f"New jingle created: {instance.name} for {instance.channel.name}"
            )
            
            # Send notification for new jingle
            if hasattr(settings, 'JINGLE_NOTIFICATION_EMAILS'):
                send_jingle_notification(
                    instance,
                    'created',
                    settings.JINGLE_NOTIFICATION_EMAILS
                )
        
        # Invalidate cache
        cache.delete(f'channel_{instance.channel.pk}_jingles')
        
    except Exception as e:
        logger.error(f"Error in jingle post-save: {str(e)}")


@receiver(post_delete, sender=Jingle)
def jingle_post_delete(sender, instance, **kwargs):
    """Handle jingle post-delete operations."""
    try:
        logger.info(
            f"Jingle deleted: {instance.name} from {instance.channel.name}"
        )
        
        # Archive or delete audio file
        if instance.audio_file:
            archive_jingle_file(instance)
        
        # Invalidate cache
        cache.delete(f'channel_{instance.channel.pk}_jingles')
        
    except Exception as e:
        logger.error(f"Error in jingle post-delete: {str(e)}")


# ============================================================================
# DayTime Signals
# ============================================================================

@receiver(post_save, sender=DayTime)
def day_time_post_save(sender, instance, created, **kwargs):
    """Handle day time post-save operations."""
    try:
        if created:
            logger.info(f"New time slot created: {instance.name}")
        
        # Invalidate time slot cache
        cache.delete('day_time_slots')
        cache.delete(f'day_time_{instance.day_of_week}')
        
    except Exception as e:
        logger.error(f"Error in day time post-save: {str(e)}")


# ============================================================================
# Utility Functions
# ============================================================================

def send_channel_notification(channel, action, email_list):
    """Send email notification for channel operations.
    
    Args:
        channel: Channel instance
        action: Action performed ('created', 'updated', 'deleted')
        email_list: List of email addresses
    """
    try:
        subject = f"Channel {action.title()}: {channel.name}"
        
        context = {
            'channel': channel,
            'action': action,
            'timestamp': timezone.now()
        }
        
        html_message = render_to_string(
            'channels/emails/channel_notification.html',
            context
        )
        plain_message = strip_tags(html_message)
        
        send_mail(
            subject=subject,
            message=plain_message,
            from_email=settings.DEFAULT_FROM_EMAIL,
            recipient_list=email_list,
            html_message=html_message,
            fail_silently=False
        )
        
        logger.info(f"Channel notification sent: {action} - {channel.name}")
        
    except Exception as e:
        logger.error(f"Failed to send channel notification: {str(e)}")


def send_jingle_notification(jingle, action, email_list):
    """Send email notification for jingle operations.
    
    Args:
        jingle: Jingle instance
        action: Action performed ('created', 'updated', 'deleted')
        email_list: List of email addresses
    """
    try:
        subject = f"Jingle {action.title()}: {jingle.name}"
        
        context = {
            'jingle': jingle,
            'action': action,
            'timestamp': timezone.now()
        }
        
        html_message = render_to_string(
            'channels/emails/jingle_notification.html',
            context
        )
        plain_message = strip_tags(html_message)
        
        send_mail(
            subject=subject,
            message=plain_message,
            from_email=settings.DEFAULT_FROM_EMAIL,
            recipient_list=email_list,
            html_message=html_message,
            fail_silently=False
        )
        
        logger.info(f"Jingle notification sent: {action} - {jingle.name}")
        
    except Exception as e:
        logger.error(f"Failed to send jingle notification: {str(e)}")


def extract_audio_metadata(jingle):
    """Extract metadata from audio file.
    
    Args:
        jingle: Jingle instance with audio file
    """
    try:
        if not jingle.audio_file:
            return
        
        # This would use a library like mutagen to extract metadata
        # For now, we'll set basic properties
        
        file_path = jingle.audio_file.path
        file_size = os.path.getsize(file_path)
        
        jingle.file_size = file_size
        
        # Extract file format from extension
        file_extension = os.path.splitext(file_path)[1].lower()
        format_map = {
            '.mp3': 'mp3',
            '.wav': 'wav',
            '.aac': 'aac',
            '.ogg': 'ogg',
            '.flac': 'flac'
        }
        jingle.audio_format = format_map.get(file_extension, 'unknown')
        
        logger.info(f"Audio metadata extracted for: {jingle.name}")
        
    except Exception as e:
        logger.error(f"Failed to extract audio metadata: {str(e)}")


def validate_audio_file(audio_file):
    """Validate audio file format and properties.
    
    Args:
        audio_file: Audio file field
        
    Returns:
        bool: True if valid, False otherwise
    """
    try:
        if not audio_file:
            return False
        
        # Check file extension
        allowed_extensions = ['.mp3', '.wav', '.aac', '.ogg', '.flac']
        file_extension = os.path.splitext(audio_file.name)[1].lower()
        
        if file_extension not in allowed_extensions:
            logger.error(f"Invalid audio file extension: {file_extension}")
            return False
        
        # Check file size (max 50MB)
        max_size = 50 * 1024 * 1024  # 50MB
        if audio_file.size > max_size:
            logger.error(f"Audio file too large: {audio_file.size} bytes")
            return False
        
        return True
        
    except Exception as e:
        logger.error(f"Error validating audio file: {str(e)}")
        return False


def archive_jingle_file(jingle):
    """Archive jingle file before deletion.
    
    Args:
        jingle: Jingle instance
    """
    try:
        if not jingle.audio_file or not os.path.isfile(jingle.audio_file.path):
            return
        
        # Create archive directory if it doesn't exist
        archive_dir = os.path.join(settings.MEDIA_ROOT, 'archived_jingles')
        os.makedirs(archive_dir, exist_ok=True)
        
        # Generate archive filename with timestamp
        timestamp = timezone.now().strftime('%Y%m%d_%H%M%S')
        original_name = os.path.basename(jingle.audio_file.name)
        archive_name = f"{timestamp}_{jingle.pk}_{original_name}"
        archive_path = os.path.join(archive_dir, archive_name)
        
        # Move file to archive
        os.rename(jingle.audio_file.path, archive_path)
        
        logger.info(f"Jingle file archived: {archive_path}")
        
    except Exception as e:
        logger.error(f"Failed to archive jingle file: {str(e)}")


# ============================================================================
# Signal Connection Verification
# ============================================================================

def verify_signal_connections():
    """Verify that all signals are properly connected.
    
    This function can be called during app initialization to ensure
    all signal handlers are registered correctly.
    """
    signals_to_check = [
        (post_save, Channel, channel_post_save),
        (pre_save, Channel, channel_pre_save),
        (post_delete, Channel, channel_post_delete),
        (pre_delete, Channel, channel_pre_delete),
        (post_save, ChannelZone, channel_zone_post_save),
        (post_delete, ChannelZone, channel_zone_post_delete),
        (post_save, ChannelCodec, channel_codec_post_save),
        (pre_save, Jingle, jingle_pre_save),
        (post_save, Jingle, jingle_post_save),
        (post_delete, Jingle, jingle_post_delete),
        (post_save, DayTime, day_time_post_save),
    ]
    
    connected_signals = []
    for signal, sender, handler in signals_to_check:
        if handler in [r[1]() for r in signal._live_receivers(sender)]:
            connected_signals.append(f"{sender.__name__}.{handler.__name__}")
    
    logger.info(f"Connected signals: {', '.join(connected_signals)}")
    return connected_signals