"""
Signal-based hook system for FSM state transitions.

This module defines custom Django signals emitted during state machine
transitions and provides utilities for connecting signal handlers.
"""

import logging
from collections.abc import Callable

from django.db import models
from django.dispatch import Signal, receiver

logger = logging.getLogger(__name__)


# Custom signals for state machine transitions

pre_state_transition = Signal()
"""
Signal sent before a state transition occurs.

Arguments:
    sender: The model class of the transitioning instance.
    instance: The model instance undergoing transition.
    source: The source state value.
    target: The target state value.
    user: The user initiating the transition (if available).
    context: TransitionContext with full transition metadata.
"""

post_state_transition = Signal()
"""
Signal sent after a successful state transition.

Arguments:
    sender: The model class of the transitioning instance.
    instance: The model instance that transitioned.
    source: The source state value.
    target: The target state value.
    user: The user who initiated the transition.
    context: TransitionContext with full transition metadata.
"""

state_transition_failed = Signal()
"""
Signal sent when a state transition fails.

Arguments:
    sender: The model class of the transitioning instance.
    instance: The model instance that failed to transition.
    source: The source state value.
    target: The intended target state value.
    user: The user who initiated the transition.
    exception: The exception that caused the failure.
    context: TransitionContext with full transition metadata.
"""


class TransitionSignalHandler:
    """
    Utility class for managing transition signal handlers.

    Provides a cleaner interface for connecting and disconnecting
    signal handlers filtered by model class and transition states.
    """

    def __init__(self):
        self._handlers: dict[str, list[Callable]] = {}

    def register(
        self,
        model_class: type[models.Model],
        source: str,
        target: str,
        handler: Callable,
        stage: str = "post",
    ) -> None:
        """
        Register a handler for a specific transition.

        Args:
            model_class: The model class to handle transitions for.
            source: Source state (use '*' for any).
            target: Target state (use '*' for any).
            handler: The handler function to call.
            stage: 'pre', 'post', or 'error'.
        """
        key = self._make_key(model_class, source, target, stage)
        if key not in self._handlers:
            self._handlers[key] = []
        self._handlers[key].append(handler)

        # Connect to appropriate signal
        signal = self._get_signal(stage)
        self._connect_signal(signal, model_class, source, target, handler)

        logger.debug(f"Registered {stage} transition handler for " f"{model_class.__name__}: {source} → {target}")

    def unregister(
        self,
        model_class: type[models.Model],
        source: str,
        target: str,
        handler: Callable,
        stage: str = "post",
    ) -> None:
        """Unregister a previously registered handler."""
        key = self._make_key(model_class, source, target, stage)
        if key in self._handlers and handler in self._handlers[key]:
            self._handlers[key].remove(handler)

        signal = self._get_signal(stage)
        signal.disconnect(handler, sender=model_class)

    def _make_key(
        self,
        model_class: type[models.Model],
        source: str,
        target: str,
        stage: str,
    ) -> str:
        """Create a unique key for handler registration."""
        return f"{model_class.__name__}:{source}:{target}:{stage}"

    def _get_signal(self, stage: str) -> Signal:
        """Get the signal for a given stage."""
        if stage == "pre":
            return pre_state_transition
        elif stage == "error":
            return state_transition_failed
        return post_state_transition

    def _connect_signal(
        self,
        signal: Signal,
        model_class: type[models.Model],
        source: str,
        target: str,
        handler: Callable,
    ) -> None:
        """Connect a filtered handler to the signal."""

        def filtered_handler(sender, **kwargs):
            # Check if this is the right model
            if sender != model_class:
                return

            # Check source state
            signal_source = kwargs.get("source", "")
            if source != "*" and str(signal_source) != source:
                return

            # Check target state
            signal_target = kwargs.get("target", "")
            if target != "*" and str(signal_target) != target:
                return

            # Call the handler
            return handler(**kwargs)

        signal.connect(filtered_handler, sender=model_class, weak=False)


# Global signal handler instance
transition_signal_handler = TransitionSignalHandler()


def register_transition_handler(
    model_class: type[models.Model],
    source: str,
    target: str,
    handler: Callable,
    stage: str = "post",
) -> None:
    """
    Convenience function to register a transition signal handler.

    Args:
        model_class: The model class to handle transitions for.
        source: Source state (use '*' for any).
        target: Target state (use '*' for any).
        handler: The handler function to call.
        stage: 'pre', 'post', or 'error'.
    """
    transition_signal_handler.register(model_class, source, target, handler, stage)


def connect_fsm_log_signals() -> None:
    """
    Connect to django-fsm-log signals for audit logging.

    This function should be called in an AppConfig.ready() method
    to set up integration with django-fsm-log's StateLog.
    """
    try:
        from django_fsm_log.models import StateLog

        @receiver(models.signals.post_save, sender=StateLog)
        def log_state_transition(sender, instance, created, **kwargs):
            """Log state transitions from django-fsm-log."""
            if created:
                logger.info(
                    f"FSM Transition: {instance.content_type} "
                    f"({instance.object_id}): {instance.source_state} → "
                    f"{instance.state} by {instance.by}"
                )

        logger.debug("Connected to django-fsm-log signals")

    except ImportError:
        logger.debug("django-fsm-log not available, skipping signal connection")


class TransitionHandlerDecorator:
    """
    Decorator for registering transition handlers.

    Usage:
        @on_transition(EditSubmission, 'PENDING', 'APPROVED')
        def handle_approval(instance, source, target, user, **kwargs):
            # Handle the approval
            pass
    """

    def __init__(
        self,
        model_class: type[models.Model],
        source: str = "*",
        target: str = "*",
        stage: str = "post",
    ):
        """
        Initialize the decorator.

        Args:
            model_class: The model class to handle.
            source: Source state filter.
            target: Target state filter.
            stage: When to execute ('pre', 'post', 'error').
        """
        self.model_class = model_class
        self.source = source
        self.target = target
        self.stage = stage

    def __call__(self, func: Callable) -> Callable:
        """Register the decorated function as a handler."""
        register_transition_handler(
            self.model_class,
            self.source,
            self.target,
            func,
            self.stage,
        )
        return func


def on_transition(
    model_class: type[models.Model],
    source: str = "*",
    target: str = "*",
    stage: str = "post",
) -> TransitionHandlerDecorator:
    """
    Decorator factory for registering transition handlers.

    Args:
        model_class: The model class to handle.
        source: Source state filter ('*' for any).
        target: Target state filter ('*' for any).
        stage: When to execute ('pre', 'post', 'error').

    Returns:
        Decorator for registering the handler function.

    Example:
        @on_transition(EditSubmission, source='PENDING', target='APPROVED')
        def notify_user(instance, source, target, user, **kwargs):
            send_notification(instance.submitted_by, "Your submission was approved!")
    """
    return TransitionHandlerDecorator(model_class, source, target, stage)


def on_pre_transition(
    model_class: type[models.Model],
    source: str = "*",
    target: str = "*",
) -> TransitionHandlerDecorator:
    """Decorator for pre-transition handlers."""
    return on_transition(model_class, source, target, stage="pre")


def on_post_transition(
    model_class: type[models.Model],
    source: str = "*",
    target: str = "*",
) -> TransitionHandlerDecorator:
    """Decorator for post-transition handlers."""
    return on_transition(model_class, source, target, stage="post")


def on_transition_error(
    model_class: type[models.Model],
    source: str = "*",
    target: str = "*",
) -> TransitionHandlerDecorator:
    """Decorator for transition error handlers."""
    return on_transition(model_class, source, target, stage="error")


__all__ = [
    # Signals
    "pre_state_transition",
    "post_state_transition",
    "state_transition_failed",
    # Handler registration
    "TransitionSignalHandler",
    "transition_signal_handler",
    "register_transition_handler",
    "connect_fsm_log_signals",
    # Decorators
    "on_transition",
    "on_pre_transition",
    "on_post_transition",
    "on_transition_error",
]
