from django.db.models.signals import post_save, pre_delete, post_delete
from django.dispatch import receiver
from django.core.cache import cache
from django.utils.text import slugify
from .models import Channel, ChannelZone, EPGEntry, Show
import logging

# Set up logging for signal handlers
logger = logging.getLogger(__name__)


@receiver(post_save, sender=Channel)
def channel_post_save(sender, instance, created, **kwargs):
    """
    Signal handler for Channel model post-save events.
    
    Handles automatic slug generation and cache invalidation
    when channels are created or updated.
    
    Args:
        sender: The Channel model class
        instance: The Channel instance being saved
        created: Boolean indicating if this is a new instance
        **kwargs: Additional keyword arguments
    """
    # Auto-generate slug if not provided
    if not instance.slug and instance.name:
        base_slug = slugify(instance.name)
        slug = base_slug
        counter = 1
        
        # Ensure slug uniqueness
        while Channel.objects.filter(slug=slug).exclude(pk=instance.pk).exists():
            slug = f"{base_slug}-{counter}"
            counter += 1
        
        # Update slug without triggering another save signal
        Channel.objects.filter(pk=instance.pk).update(slug=slug)
    
    # Clear relevant caches
    cache_keys = [
        'channels_list',
        f'channel_detail_{instance.pk}',
        'channels_stats',
    ]
    cache.delete_many(cache_keys)
    
    # Log the action
    action = 'created' if created else 'updated'
    logger.info(f'Channel "{instance.name}" (ID: {instance.pk}) was {action}')


@receiver(post_delete, sender=Channel)
def channel_post_delete(sender, instance, **kwargs):
    """
    Signal handler for Channel model post-delete events.
    
    Handles cleanup and cache invalidation when channels are deleted.
    
    Args:
        sender: The Channel model class
        instance: The Channel instance being deleted
        **kwargs: Additional keyword arguments
    """
    # Clear relevant caches
    cache_keys = [
        'channels_list',
        f'channel_detail_{instance.pk}',
        'channels_stats',
    ]
    cache.delete_many(cache_keys)
    
    # Log the action
    logger.info(f'Channel "{instance.name}" (ID: {instance.pk}) was deleted')


@receiver(post_save, sender=Show)
def show_post_save(sender, instance, created, **kwargs):
    """
    Signal handler for Show model post-save events.
    
    Handles automatic slug generation when shows are created or updated.
    
    Args:
        sender: The Show model class
        instance: The Show instance being saved
        created: Boolean indicating if this is a new instance
        **kwargs: Additional keyword arguments
    """
    # Auto-generate slug if not provided
    if not instance.slug and instance.title:
        base_slug = slugify(instance.title)
        slug = base_slug
        counter = 1
        
        # Ensure slug uniqueness
        while Show.objects.filter(slug=slug).exclude(pk=instance.pk).exists():
            slug = f"{base_slug}-{counter}"
            counter += 1
        
        # Update slug without triggering another save signal
        Show.objects.filter(pk=instance.pk).update(slug=slug)
    
    # Clear relevant caches
    cache_keys = [
        'shows_list',
        f'show_detail_{instance.pk}',
        'shows_stats',
    ]
    cache.delete_many(cache_keys)
    
    # Log the action
    action = 'created' if created else 'updated'
    logger.info(f'Show "{instance.title}" (ID: {instance.pk}) was {action}')


@receiver(post_save, sender=ChannelZone)
def channel_zone_post_save(sender, instance, created, **kwargs):
    """
    Signal handler for ChannelZone model post-save events.
    
    Handles cache invalidation and validation when channel-zone
    configurations are created or updated.
    
    Args:
        sender: The ChannelZone model class
        instance: The ChannelZone instance being saved
        created: Boolean indicating if this is a new instance
        **kwargs: Additional keyword arguments
    """
    # Clear relevant caches
    cache_keys = [
        f'channel_detail_{instance.channel.pk}',
        f'channel_zones_{instance.channel.pk}',
        'channels_stats',
    ]
    cache.delete_many(cache_keys)
    
    # Log the action
    action = 'created' if created else 'updated'
    logger.info(
        f'Channel-Zone configuration for "{instance.channel.name}" '
        f'in "{instance.zone.name}" was {action}'
    )


@receiver(post_delete, sender=ChannelZone)
def channel_zone_post_delete(sender, instance, **kwargs):
    """
    Signal handler for ChannelZone model post-delete events.
    
    Handles cache invalidation when channel-zone configurations are deleted.
    
    Args:
        sender: The ChannelZone model class
        instance: The ChannelZone instance being deleted
        **kwargs: Additional keyword arguments
    """
    # Clear relevant caches
    cache_keys = [
        f'channel_detail_{instance.channel.pk}',
        f'channel_zones_{instance.channel.pk}',
        'channels_stats',
    ]
    cache.delete_many(cache_keys)
    
    # Log the action
    logger.info(
        f'Channel-Zone configuration for "{instance.channel.name}" '
        f'in "{instance.zone.name}" was deleted'
    )


@receiver(post_save, sender=EPGEntry)
def epg_entry_post_save(sender, instance, created, **kwargs):
    """
    Signal handler for EPGEntry model post-save events.
    
    Handles cache invalidation and conflict detection when EPG entries
    are created or updated.
    
    Args:
        sender: The EPGEntry model class
        instance: The EPGEntry instance being saved
        created: Boolean indicating if this is a new instance
        **kwargs: Additional keyword arguments
    """
    # Clear relevant caches
    cache_keys = [
        f'epg_channel_{instance.channel.pk}',
        f'epg_show_{instance.show.pk}',
        'epg_today',
        'epg_stats',
    ]
    cache.delete_many(cache_keys)
    
    # Log the action
    action = 'created' if created else 'updated'
    logger.info(
        f'EPG entry for "{instance.show.title}" on "{instance.channel.name}" '
        f'at {instance.start_time} was {action}'
    )


@receiver(post_delete, sender=EPGEntry)
def epg_entry_post_delete(sender, instance, **kwargs):
    """
    Signal handler for EPGEntry model post-delete events.
    
    Handles cache invalidation when EPG entries are deleted.
    
    Args:
        sender: The EPGEntry model class
        instance: The EPGEntry instance being deleted
        **kwargs: Additional keyword arguments
    """
    # Clear relevant caches
    cache_keys = [
        f'epg_channel_{instance.channel.pk}',
        f'epg_show_{instance.show.pk}',
        'epg_today',
        'epg_stats',
    ]
    cache.delete_many(cache_keys)
    
    # Log the action
    logger.info(
        f'EPG entry for "{instance.show.title}" on "{instance.channel.name}" '
        f'at {instance.start_time} was deleted'
    )


@receiver(pre_delete, sender=Channel)
def channel_pre_delete(sender, instance, **kwargs):
    """
    Signal handler for Channel model pre-delete events.
    
    Handles cleanup of related data before channel deletion.
    
    Args:
        sender: The Channel model class
        instance: The Channel instance being deleted
        **kwargs: Additional keyword arguments
    """
    # Check for dependent EPG entries
    epg_count = instance.epg_entries.count()
    if epg_count > 0:
        logger.warning(
            f'Deleting channel "{instance.name}" will remove {epg_count} EPG entries'
        )
    
    # Check for dependent jingles
    jingles_count = instance.jingles.count()
    if jingles_count > 0:
        logger.warning(
            f'Deleting channel "{instance.name}" will remove {jingles_count} jingles'
        )
    
    # Check for zone configurations
    zones_count = instance.channel_zones.count()
    if zones_count > 0:
        logger.warning(
            f'Deleting channel "{instance.name}" will remove {zones_count} zone configurations'
        )


@receiver(pre_delete, sender=Show)
def show_pre_delete(sender, instance, **kwargs):
    """
    Signal handler for Show model pre-delete events.
    
    Handles cleanup of related data before show deletion.
    
    Args:
        sender: The Show model class
        instance: The Show instance being deleted
        **kwargs: Additional keyword arguments
    """
    # Check for dependent EPG entries
    epg_count = instance.epg_entries.count()
    if epg_count > 0:
        logger.warning(
            f'Deleting show "{instance.title}" will remove {epg_count} EPG entries'
        )


def invalidate_channel_cache(channel_id):
    """
    Utility function to invalidate all cache keys related to a specific channel.
    
    Args:
        channel_id: The ID of the channel whose cache should be invalidated
    """
    cache_keys = [
        f'channel_detail_{channel_id}',
        f'channel_zones_{channel_id}',
        f'epg_channel_{channel_id}',
        'channels_list',
        'channels_stats',
    ]
    cache.delete_many(cache_keys)
    logger.debug(f'Invalidated cache for channel ID: {channel_id}')


def invalidate_show_cache(show_id):
    """
    Utility function to invalidate all cache keys related to a specific show.
    
    Args:
        show_id: The ID of the show whose cache should be invalidated
    """
    cache_keys = [
        f'show_detail_{show_id}',
        f'epg_show_{show_id}',
        'shows_list',
        'shows_stats',
    ]
    cache.delete_many(cache_keys)
    logger.debug(f'Invalidated cache for show ID: {show_id}')


def invalidate_epg_cache():
    """
    Utility function to invalidate all EPG-related cache keys.
    
    This is useful when performing bulk EPG operations.
    """
    # Get all cache keys that start with 'epg_'
    cache_keys = [
        'epg_today',
        'epg_stats',
        'epg_calendar',
    ]
    
    # Add channel and show specific EPG caches
    # Note: In a production environment, you might want to use a more
    # sophisticated cache invalidation strategy
    cache.delete_many(cache_keys)
    logger.debug('Invalidated all EPG-related cache keys')