"""
Signals for the schedule app.

Handles:
1. Auto-attaching plugins from GlobalEventPlugin rules when events are created
2. Rescheduling Celery tasks when events are modified (time/duration changes)
3. Scheduling/cancelling Celery tasks when EventPlugins are created/deleted/modified
4. Cancelling tasks when Events are deleted or cancelled
"""
import logging
from django.db.models.signals import post_save, pre_save, post_delete, pre_delete
from django.dispatch import receiver

logger = logging.getLogger(__name__)


@receiver(post_save, sender='schedule.Event')
def auto_attach_global_plugins(sender, instance, created, **kwargs):
    """
    When a new event is created, automatically attach all active GlobalEventPlugin rules.
    """
    if not created:
        return

    from .models import GlobalEventPlugin, EventPlugin

    # Get all active global rules
    global_rules = GlobalEventPlugin.objects.filter(is_active=True)

    attached_count = 0
    for rule in global_rules:
        event_plugin = rule.apply_to_event(instance)
        if event_plugin:
            attached_count += 1
            logger.info(
                f"Auto-attached plugin '{rule.plugin_installation}' to event '{instance}' "
                f"via global rule (trigger={rule.trigger}, offset={rule.offset_minutes})"
            )

    if attached_count > 0:
        logger.info(f"Auto-attached {attached_count} plugins to new event '{instance}'")


@receiver(pre_save, sender='schedule.Event')
def track_event_changes(sender, instance, **kwargs):
    """
    Track changes to event timing before save.
    Store old values on the instance for post_save comparison.
    """
    if instance.pk:
        try:
            from .models import Event
            old_instance = Event.objects.get(pk=instance.pk)
            instance._old_start_time = old_instance.start_time
            instance._old_end_time = old_instance.end_time
        except sender.DoesNotExist:
            instance._old_start_time = None
            instance._old_end_time = None
    else:
        instance._old_start_time = None
        instance._old_end_time = None


@receiver(post_save, sender='schedule.Event')
def reschedule_event_plugins_on_change(sender, instance, created, **kwargs):
    """
    When an event's timing changes, update any scheduled Celery tasks for its plugins.
    This handles both time changes and duration changes (via end_time).
    """
    if created:
        # New events don't have existing tasks to reschedule
        return

    old_start = getattr(instance, '_old_start_time', None)
    old_end = getattr(instance, '_old_end_time', None)

    if old_start is None and old_end is None:
        return

    # Check if timing actually changed
    start_changed = old_start and old_start != instance.start_time
    end_changed = old_end and old_end != instance.end_time

    if not start_changed and not end_changed:
        return

    logger.info(
        f"Event '{instance}' timing changed. "
        f"Start: {old_start} -> {instance.start_time}, "
        f"End: {old_end} -> {instance.end_time}"
    )

    # Reschedule all active time-based event plugins
    reschedule_event_celery_tasks(instance, start_changed, end_changed)


def reschedule_event_celery_tasks(event, start_changed=True, end_changed=True):
    """
    Reschedule Celery tasks for an event's plugins when timing changes.

    Args:
        event: The Event instance
        start_changed: Whether start_time changed
        end_changed: Whether end_time changed
    """
    from .models import EventPlugin

    # Get all active, time-based plugins for this event
    time_based_triggers = ['before_start', 'at_start', 'after_start', 'after_end']

    plugins_to_update = event.event_plugins.filter(
        is_active=True,
        trigger__in=time_based_triggers
    )

    for event_plugin in plugins_to_update:
        # Only reschedule if the relevant time changed
        affects_start = event_plugin.trigger in ['before_start', 'at_start', 'after_start']
        affects_end = event_plugin.trigger == 'after_end'

        if (affects_start and start_changed) or (affects_end and end_changed):
            new_execution_time = event_plugin.get_execution_time()
            if new_execution_time:
                logger.info(
                    f"Rescheduling plugin '{event_plugin.plugin_installation}' for event '{event}' "
                    f"to new execution time: {new_execution_time}"
                )
                # TODO: Integrate with Celery beat to reschedule the actual task
                # For now, we log the intent. The actual Celery integration
                # will be handled by the task execution system.
                schedule_event_plugin_task(event_plugin, new_execution_time)


def schedule_event_plugin_task(event_plugin, execution_time):
    """
    Schedule a Celery task for an event plugin at a specific time.

    This function handles creating or updating Celery beat entries
    for time-based event plugin execution.
    """
    from django.utils import timezone

    # Don't schedule tasks in the past
    if execution_time < timezone.now():
        logger.debug(
            f"Skipping scheduling for event plugin {event_plugin.id} - "
            f"execution time {execution_time} is in the past"
        )
        return

    # Get or create the Celery task entry
    # Using django-celery-beat's PeriodicTask model if available
    try:
        from django_celery_beat.models import PeriodicTask, ClockedSchedule

        # Create a clocked schedule for the specific execution time
        clocked_schedule, _ = ClockedSchedule.objects.get_or_create(
            clocked_time=execution_time
        )

        # Task name is unique per event-plugin combination
        task_name = f"event_plugin_{event_plugin.id}"

        import json

        # Create or update the periodic task
        task, created = PeriodicTask.objects.update_or_create(
            name=task_name,
            defaults={
                'task': 'schedule.tasks.execute_event_plugin',
                'clocked': clocked_schedule,
                'one_off': True,  # Run only once
                'enabled': event_plugin.is_active,
                'kwargs': json.dumps({
                    'event_plugin_id': event_plugin.id,
                    'event_id': event_plugin.event_id,
                }),
            }
        )

        action = "Created" if created else "Updated"
        logger.info(f"{action} Celery task '{task_name}' for execution at {execution_time}")

    except ImportError:
        # django-celery-beat not installed, fall back to simple delay
        logger.warning(
            "django-celery-beat not installed. "
            "Event plugin scheduling will use basic Celery delay."
        )
    except Exception as e:
        logger.error(f"Failed to schedule event plugin task: {e}")


@receiver(post_save, sender='schedule.GlobalEventPlugin')
def apply_global_plugin_to_existing_events(sender, instance, created, **kwargs):
    """
    When a new GlobalEventPlugin rule is created, apply it to all existing events
    if apply_to_existing is True.
    """
    if not created:
        return

    if not instance.is_active:
        return

    if not instance.apply_to_existing:
        logger.info(
            f"Global plugin rule '{instance}' will only apply to future events"
        )
        return

    count = instance.apply_to_all_events()
    logger.info(
        f"Applied global plugin rule '{instance}' to {count} existing events"
    )


# ============================================================================
# EventPlugin Scheduling Signals
# ============================================================================

@receiver(post_save, sender='schedule.EventPlugin')
def schedule_event_plugin_on_create(sender, instance, created, **kwargs):
    """
    When an EventPlugin is created or updated, schedule its Celery task
    if it has a time-based trigger.
    """
    # Only schedule time-based triggers
    time_based_triggers = ['before_start', 'at_start', 'after_start', 'after_end']

    if instance.trigger not in time_based_triggers:
        return

    if not instance.is_active:
        # If deactivated, cancel any existing task
        from .tasks import cancel_event_plugin_task
        cancel_event_plugin_task(instance.id)
        return

    execution_time = instance.get_execution_time()
    if execution_time:
        schedule_event_plugin_task(instance, execution_time)


@receiver(pre_save, sender='schedule.EventPlugin')
def track_event_plugin_active_change(sender, instance, **kwargs):
    """
    Track if is_active changed so we can cancel tasks when deactivated.
    """
    if instance.pk:
        try:
            from .models import EventPlugin
            old_instance = EventPlugin.objects.get(pk=instance.pk)
            instance._was_active = old_instance.is_active
        except sender.DoesNotExist:
            instance._was_active = None
    else:
        instance._was_active = None


@receiver(post_delete, sender='schedule.EventPlugin')
def cancel_event_plugin_on_delete(sender, instance, **kwargs):
    """
    When an EventPlugin is deleted, cancel its scheduled Celery task.
    """
    from .tasks import cancel_event_plugin_task
    cancel_event_plugin_task(instance.id)


# ============================================================================
# Event Deletion/Cancellation Signals
# ============================================================================

@receiver(pre_delete, sender='schedule.Event')
def cancel_event_tasks_on_delete(sender, instance, **kwargs):
    """
    When an Event is deleted, cancel all its scheduled plugin tasks.
    """
    from .tasks import cancel_event_tasks
    cancel_event_tasks(instance.id)


@receiver(pre_save, sender='schedule.Event')
def track_event_status_change(sender, instance, **kwargs):
    """
    Track status changes to detect cancellation.
    """
    if instance.pk:
        try:
            from .models import Event
            old_instance = Event.objects.get(pk=instance.pk)
            instance._old_status = old_instance.status
        except sender.DoesNotExist:
            instance._old_status = None
    else:
        instance._old_status = None


@receiver(post_save, sender='schedule.Event')
def cancel_event_tasks_on_cancel(sender, instance, created, **kwargs):
    """
    When an Event is cancelled, cancel all its scheduled plugin tasks.
    """
    if created:
        return

    from .models import Event

    old_status = getattr(instance, '_old_status', None)

    # If status changed to cancelled, cancel all tasks
    if old_status != Event.Status.CANCELED and instance.status == Event.Status.CANCELED:
        from .tasks import cancel_event_tasks
        logger.info(f"Event '{instance}' was cancelled, cancelling all plugin tasks")
        cancel_event_tasks(instance.id)
