"""Signals for Jingles app."""

import logging
from typing import Any

from django.db.models.signals import post_save, pre_save, post_delete, m2m_changed
from django.dispatch import receiver, Signal
from django.core.cache import cache
from django.utils import timezone
from django.contrib.auth.signals import user_logged_in, user_logged_out

from .models import (
    Jingle,
    JinglePlaylist,
    JinglePlaylistItem,
    JingleSchedule,
    JingleCategory,
    JingleType,
)
from .tasks import (
    process_jingle_upload,
    update_jingle_play_count,
    generate_playlist_from_schedule,
    cleanup_expired_jingles,
)

logger = logging.getLogger(__name__)

# Custom signals
jingle_played = Signal()
jingle_uploaded = Signal()
jingle_approved = Signal()
jingle_rejected = Signal()
playlist_updated = Signal()
schedule_activated = Signal()
schedule_deactivated = Signal()


@receiver(post_save, sender=Jingle)
def handle_jingle_post_save(sender, instance, created, **kwargs):
    """Handle Jingle post-save signal."""
    try:
        if created:
            logger.info(f"New jingle created: {instance.name} (ID: {instance.id})")
            
            # Trigger upload processing if file is present
            if instance.file:
                process_jingle_upload.delay(
                    jingle_id=instance.id,
                    file_path=instance.file.path
                )
            
            # Send custom signal
            jingle_uploaded.send(
                sender=sender,
                jingle=instance,
                created_by=instance.uploaded_by
            )
        
        else:
            # Handle status changes
            if hasattr(instance, '_original_status'):
                old_status = instance._original_status
                new_status = instance.status
                
                if old_status != new_status:
                    logger.info(
                        f"Jingle {instance.id} status changed: {old_status} -> {new_status}"
                    )
                    
                    if new_status == 'approved':
                        jingle_approved.send(
                            sender=sender,
                            jingle=instance,
                            approved_by=instance.approved_by
                        )
                    elif new_status == 'rejected':
                        jingle_rejected.send(
                            sender=sender,
                            jingle=instance,
                            rejection_reason=instance.rejection_reason
                        )
        
        # Invalidate related caches
        _invalidate_jingle_caches(instance)
        
    except Exception as e:
        logger.error(f"Error in jingle post_save signal: {str(e)}")


@receiver(pre_save, sender=Jingle)
def handle_jingle_pre_save(sender, instance, **kwargs):
    """Handle Jingle pre-save signal."""
    try:
        # Store original status for comparison in post_save
        if instance.pk:
            try:
                original = Jingle.objects.get(pk=instance.pk)
                instance._original_status = original.status
                instance._original_is_active = original.is_active
            except Jingle.DoesNotExist:
                pass
        
        # Auto-set approval timestamp
        if instance.status == 'approved' and not instance.approved_at:
            instance.approved_at = timezone.now()
        
        # Validate file format consistency
        if instance.file:
            file_extension = instance.file.name.split('.')[-1].lower()
            if instance.file_format and instance.file_format != file_extension:
                instance.file_format = file_extension
        
    except Exception as e:
        logger.error(f"Error in jingle pre_save signal: {str(e)}")


@receiver(post_delete, sender=Jingle)
def handle_jingle_post_delete(sender, instance, **kwargs):
    """Handle Jingle post-delete signal."""
    try:
        logger.info(f"Jingle deleted: {instance.name} (ID: {instance.id})")
        
        # Clean up file if it exists
        if instance.file:
            try:
                instance.file.delete(save=False)
            except Exception as e:
                logger.error(f"Error deleting jingle file: {str(e)}")
        
        # Invalidate caches
        _invalidate_jingle_caches(instance)
        
    except Exception as e:
        logger.error(f"Error in jingle post_delete signal: {str(e)}")


@receiver(post_save, sender=JinglePlaylist)
def handle_playlist_post_save(sender, instance, created, **kwargs):
    """Handle JinglePlaylist post-save signal."""
    try:
        if created:
            logger.info(f"New playlist created: {instance.name} (ID: {instance.id})")
        
        # Invalidate playlist caches
        _invalidate_playlist_caches(instance)
        
        # Send custom signal
        playlist_updated.send(
            sender=sender,
            playlist=instance,
            created=created
        )
        
    except Exception as e:
        logger.error(f"Error in playlist post_save signal: {str(e)}")


@receiver(post_save, sender=JinglePlaylistItem)
def handle_playlist_item_post_save(sender, instance, created, **kwargs):
    """Handle JinglePlaylistItem post-save signal."""
    try:
        if created:
            logger.info(
                f"New playlist item created: {instance.jingle.name} "
                f"in {instance.playlist.name}"
            )
        
        # Update playlist's updated_at timestamp
        instance.playlist.save(update_fields=['updated_at'])
        
        # Invalidate related caches
        _invalidate_playlist_caches(instance.playlist)
        
    except Exception as e:
        logger.error(f"Error in playlist item post_save signal: {str(e)}")


@receiver(post_delete, sender=JinglePlaylistItem)
def handle_playlist_item_post_delete(sender, instance, **kwargs):
    """Handle JinglePlaylistItem post-delete signal."""
    try:
        logger.info(
            f"Playlist item deleted: {instance.jingle.name} "
            f"from {instance.playlist.name}"
        )
        
        # Update playlist's updated_at timestamp
        try:
            instance.playlist.save(update_fields=['updated_at'])
        except:
            pass  # Playlist might be deleted too
        
        # Invalidate related caches
        _invalidate_playlist_caches(instance.playlist)
        
    except Exception as e:
        logger.error(f"Error in playlist item post_delete signal: {str(e)}")


@receiver(post_save, sender=JingleSchedule)
def handle_schedule_post_save(sender, instance, created, **kwargs):
    """Handle JingleSchedule post-save signal."""
    try:
        if created:
            logger.info(f"New schedule created: {instance.name} (ID: {instance.id})")
        
        # Handle activation/deactivation
        if hasattr(instance, '_original_is_active'):
            old_active = instance._original_is_active
            new_active = instance.is_active
            
            if old_active != new_active:
                if new_active:
                    logger.info(f"Schedule activated: {instance.name}")
                    schedule_activated.send(
                        sender=sender,
                        schedule=instance
                    )
                    
                    # Generate playlist if auto-generation is enabled
                    if instance.auto_generate_playlist:
                        generate_playlist_from_schedule.delay(instance.id)
                        
                else:
                    logger.info(f"Schedule deactivated: {instance.name}")
                    schedule_deactivated.send(
                        sender=sender,
                        schedule=instance
                    )
        
        # Invalidate schedule caches
        _invalidate_schedule_caches(instance)
        
    except Exception as e:
        logger.error(f"Error in schedule post_save signal: {str(e)}")


@receiver(pre_save, sender=JingleSchedule)
def handle_schedule_pre_save(sender, instance, **kwargs):
    """Handle JingleSchedule pre-save signal."""
    try:
        # Store original is_active for comparison in post_save
        if instance.pk:
            try:
                original = JingleSchedule.objects.get(pk=instance.pk)
                instance._original_is_active = original.is_active
            except JingleSchedule.DoesNotExist:
                pass
        
        # Validate schedule configuration
        if instance.schedule_type == 'interval' and not instance.interval_minutes:
            instance.interval_minutes = 60  # Default to 1 hour
        
    except Exception as e:
        logger.error(f"Error in schedule pre_save signal: {str(e)}")


@receiver(m2m_changed, sender=JingleSchedule.jingle_categories.through)
def handle_schedule_categories_changed(sender, instance, action, pk_set, **kwargs):
    """Handle changes to schedule's jingle categories."""
    try:
        if action in ['post_add', 'post_remove', 'post_clear']:
            logger.info(f"Schedule categories changed for: {instance.name}")
            
            # Regenerate playlist if auto-generation is enabled
            if instance.is_active and instance.auto_generate_playlist:
                generate_playlist_from_schedule.delay(instance.id)
            
            # Invalidate caches
            _invalidate_schedule_caches(instance)
            
    except Exception as e:
        logger.error(f"Error in schedule categories changed signal: {str(e)}")


@receiver(m2m_changed, sender=JingleSchedule.jingle_types.through)
def handle_schedule_types_changed(sender, instance, action, pk_set, **kwargs):
    """Handle changes to schedule's jingle types."""
    try:
        if action in ['post_add', 'post_remove', 'post_clear']:
            logger.info(f"Schedule types changed for: {instance.name}")
            
            # Regenerate playlist if auto-generation is enabled
            if instance.is_active and instance.auto_generate_playlist:
                generate_playlist_from_schedule.delay(instance.id)
            
            # Invalidate caches
            _invalidate_schedule_caches(instance)
            
    except Exception as e:
        logger.error(f"Error in schedule types changed signal: {str(e)}")


@receiver(post_save, sender=JingleCategory)
def handle_category_post_save(sender, instance, created, **kwargs):
    """Handle JingleCategory post-save signal."""
    try:
        if created:
            logger.info(f"New jingle category created: {instance.name}")
        
        # Invalidate category-related caches
        cache.delete_many([
            'jingle_categories_active',
            f'category_jingles_{instance.id}',
            'jingle_stats_by_category',
        ])
        
    except Exception as e:
        logger.error(f"Error in category post_save signal: {str(e)}")


@receiver(post_save, sender=JingleType)
def handle_type_post_save(sender, instance, created, **kwargs):
    """Handle JingleType post-save signal."""
    try:
        if created:
            logger.info(f"New jingle type created: {instance.name}")
        
        # Invalidate type-related caches
        cache.delete_many([
            'jingle_types_active',
            f'type_jingles_{instance.id}',
            'jingle_stats_by_type',
        ])
        
    except Exception as e:
        logger.error(f"Error in type post_save signal: {str(e)}")


# Custom signal handlers

@receiver(jingle_played)
def handle_jingle_played(sender, jingle_id, playlist_item_id=None, **kwargs):
    """Handle jingle played event."""
    try:
        logger.info(f"Jingle played: {jingle_id}")
        
        # Update play count asynchronously
        update_jingle_play_count.delay(
            jingle_id=jingle_id,
            playlist_item_id=playlist_item_id
        )
        
        # Invalidate play count caches
        cache.delete_many([
            f'jingle_play_count_{jingle_id}',
            'top_played_jingles',
            'jingle_play_stats',
        ])
        
    except Exception as e:
        logger.error(f"Error handling jingle played signal: {str(e)}")


@receiver(jingle_uploaded)
def handle_jingle_uploaded(sender, jingle, created_by, **kwargs):
    """Handle jingle uploaded event."""
    try:
        logger.info(f"Jingle uploaded: {jingle.name} by {created_by}")
        
        # You can add custom logic here, such as:
        # - Sending notifications to moderators
        # - Updating user upload statistics
        # - Triggering content analysis
        
    except Exception as e:
        logger.error(f"Error handling jingle uploaded signal: {str(e)}")


@receiver(jingle_approved)
def handle_jingle_approved(sender, jingle, approved_by, **kwargs):
    """Handle jingle approved event."""
    try:
        logger.info(f"Jingle approved: {jingle.name} by {approved_by}")
        
        # Invalidate approval-related caches
        cache.delete_many([
            'pending_jingles_count',
            'approved_jingles_today',
            f'user_jingles_{jingle.uploaded_by.id}',
        ])
        
    except Exception as e:
        logger.error(f"Error handling jingle approved signal: {str(e)}")


@receiver(jingle_rejected)
def handle_jingle_rejected(sender, jingle, rejection_reason, **kwargs):
    """Handle jingle rejected event."""
    try:
        logger.info(f"Jingle rejected: {jingle.name} - {rejection_reason}")
        
        # Invalidate rejection-related caches
        cache.delete_many([
            'pending_jingles_count',
            'rejected_jingles_today',
            f'user_jingles_{jingle.uploaded_by.id}',
        ])
        
    except Exception as e:
        logger.error(f"Error handling jingle rejected signal: {str(e)}")


@receiver(playlist_updated)
def handle_playlist_updated(sender, playlist, created, **kwargs):
    """Handle playlist updated event."""
    try:
        logger.info(f"Playlist updated: {playlist.name}")
        
        # Trigger schedule regeneration if needed
        active_schedules = JingleSchedule.objects.filter(
            playlist=playlist,
            is_active=True,
            auto_generate_playlist=True
        )
        
        for schedule in active_schedules:
            generate_playlist_from_schedule.delay(schedule.id)
        
    except Exception as e:
        logger.error(f"Error handling playlist updated signal: {str(e)}")


@receiver(schedule_activated)
def handle_schedule_activated(sender, schedule, **kwargs):
    """Handle schedule activated event."""
    try:
        logger.info(f"Schedule activated: {schedule.name}")
        
        # You can add custom logic here, such as:
        # - Sending notifications
        # - Starting background monitoring
        # - Updating system configurations
        
    except Exception as e:
        logger.error(f"Error handling schedule activated signal: {str(e)}")


@receiver(schedule_deactivated)
def handle_schedule_deactivated(sender, schedule, **kwargs):
    """Handle schedule deactivated event."""
    try:
        logger.info(f"Schedule deactivated: {schedule.name}")
        
        # You can add custom logic here, such as:
        # - Sending notifications
        # - Stopping background monitoring
        # - Cleaning up resources
        
    except Exception as e:
        logger.error(f"Error handling schedule deactivated signal: {str(e)}")


# User activity signals

@receiver(user_logged_in)
def handle_user_logged_in(sender, request, user, **kwargs):
    """Handle user login for jingle-related activities."""
    try:
        # Log user activity if they have jingle-related permissions
        if user.has_perm('jingles.view_jingle'):
            logger.info(f"Jingle user logged in: {user.username}")
            
            # You can add custom logic here, such as:
            # - Updating last login for jingle activities
            # - Sending welcome notifications
            # - Preparing user-specific data
        
    except Exception as e:
        logger.error(f"Error handling user login signal: {str(e)}")


@receiver(user_logged_out)
def handle_user_logged_out(sender, request, user, **kwargs):
    """Handle user logout for jingle-related activities."""
    try:
        if user and user.has_perm('jingles.view_jingle'):
            logger.info(f"Jingle user logged out: {user.username}")
            
            # You can add custom logic here, such as:
            # - Cleaning up user-specific caches
            # - Logging activity duration
            # - Saving user preferences
        
    except Exception as e:
        logger.error(f"Error handling user logout signal: {str(e)}")


# Helper functions

def _invalidate_jingle_caches(jingle: Jingle) -> None:
    """Invalidate jingle-related caches."""
    try:
        cache_keys = [
            f'jingle_{jingle.id}',
            f'jingle_metadata_{jingle.id}',
            f'channel_jingles_{jingle.channel.id}' if jingle.channel else None,
            f'category_jingles_{jingle.category.id}' if jingle.category else None,
            f'type_jingles_{jingle.jingle_type.id}' if jingle.jingle_type else None,
            'active_jingles',
            'approved_jingles',
            'pending_jingles',
            'jingle_stats',
            'top_played_jingles',
        ]
        
        # Remove None values
        cache_keys = [key for key in cache_keys if key is not None]
        cache.delete_many(cache_keys)
        
    except Exception as e:
        logger.error(f"Error invalidating jingle caches: {str(e)}")


def _invalidate_playlist_caches(playlist: JinglePlaylist) -> None:
    """Invalidate playlist-related caches."""
    try:
        cache_keys = [
            f'playlist_{playlist.id}',
            f'playlist_items_{playlist.id}',
            f'channel_playlists_{playlist.channel.id}' if playlist.channel else None,
            'active_playlists',
            'playlist_stats',
        ]
        
        # Remove None values
        cache_keys = [key for key in cache_keys if key is not None]
        cache.delete_many(cache_keys)
        
    except Exception as e:
        logger.error(f"Error invalidating playlist caches: {str(e)}")


def _invalidate_schedule_caches(schedule: JingleSchedule) -> None:
    """Invalidate schedule-related caches."""
    try:
        cache_keys = [
            f'schedule_{schedule.id}',
            f'channel_schedules_{schedule.channel.id}' if schedule.channel else None,
            f'playlist_schedules_{schedule.playlist.id}' if schedule.playlist else None,
            'active_schedules',
            'schedule_stats',
            'current_schedules',
        ]
        
        # Remove None values
        cache_keys = [key for key in cache_keys if key is not None]
        cache.delete_many(cache_keys)
        
    except Exception as e:
        logger.error(f"Error invalidating schedule caches: {str(e)}")


# Periodic cleanup signal
def setup_periodic_cleanup():
    """Setup periodic cleanup tasks."""
    try:
        # This would typically be called from the app's ready() method
        # to schedule periodic cleanup tasks
        
        # Schedule daily cleanup
        from django_celery_beat.models import PeriodicTask, CrontabSchedule
        
        # Create or get daily schedule (runs at 2 AM)
        daily_schedule, created = CrontabSchedule.objects.get_or_create(
            minute=0,
            hour=2,
            day_of_week='*',
            day_of_month='*',
            month_of_year='*',
        )
        
        # Create or update cleanup task
        PeriodicTask.objects.update_or_create(
            name='jingles_daily_cleanup',
            defaults={
                'crontab': daily_schedule,
                'task': 'apps.jingles.tasks.cleanup_expired_jingles',
                'enabled': True,
            }
        )
        
        # Create or update file cleanup task (weekly)
        weekly_schedule, created = CrontabSchedule.objects.get_or_create(
            minute=0,
            hour=3,
            day_of_week=1,  # Monday
            day_of_month='*',
            month_of_year='*',
        )
        
        PeriodicTask.objects.update_or_create(
            name='jingles_file_cleanup',
            defaults={
                'crontab': weekly_schedule,
                'task': 'apps.jingles.tasks.cleanup_old_jingle_files',
                'enabled': True,
            }
        )
        
        logger.info("Periodic cleanup tasks configured")
        
    except Exception as e:
        logger.error(f"Error setting up periodic cleanup: {str(e)}")