"""TransitionRegistry - Centralized registry for managing FSM transitions."""

import logging
from collections.abc import Callable
from dataclasses import dataclass, field
from typing import Any, Optional

from django.db import models

from apps.core.state_machine.builder import StateTransitionBuilder

logger = logging.getLogger(__name__)


@dataclass
class TransitionInfo:
    """Information about a state transition."""

    source: str
    target: str
    method_name: str
    requires_moderator: bool = False
    requires_admin_approval: bool = False
    metadata: dict[str, Any] = field(default_factory=dict)

    def __hash__(self):
        """Make TransitionInfo hashable."""
        return hash((self.source, self.target, self.method_name))


class TransitionRegistry:
    """Centralized registry for managing and looking up FSM transitions."""

    _instance: Optional["TransitionRegistry"] = None
    _transitions: dict[tuple[str, str], dict[tuple[str, str], TransitionInfo]]

    def __new__(cls):
        """Implement singleton pattern."""
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._transitions = {}
        return cls._instance

    def _get_key(self, choice_group: str, domain: str) -> tuple[str, str]:
        """Generate registry key from choice group and domain."""
        return (domain, choice_group)

    def register_transition(
        self,
        choice_group: str,
        domain: str,
        source: str,
        target: str,
        method_name: str,
        metadata: dict[str, Any] | None = None,
    ) -> TransitionInfo:
        """
        Register a transition.

        Args:
            choice_group: Choice group name
            domain: Domain namespace
            source: Source state
            target: Target state
            method_name: Name of the transition method
            metadata: Additional metadata

        Returns:
            Registered TransitionInfo
        """
        key = self._get_key(choice_group, domain)
        transition_key = (source, target)

        if key not in self._transitions:
            self._transitions[key] = {}

        meta = metadata or {}
        transition_info = TransitionInfo(
            source=source,
            target=target,
            method_name=method_name,
            requires_moderator=meta.get("requires_moderator", False),
            requires_admin_approval=meta.get("requires_admin_approval", False),
            metadata=meta,
        )

        self._transitions[key][transition_key] = transition_info
        return transition_info

    def get_transition(self, choice_group: str, domain: str, source: str, target: str) -> TransitionInfo | None:
        """
        Retrieve transition info.

        Args:
            choice_group: Choice group name
            domain: Domain namespace
            source: Source state
            target: Target state

        Returns:
            TransitionInfo or None if not found
        """
        key = self._get_key(choice_group, domain)
        transition_key = (source, target)

        if key not in self._transitions:
            return None

        return self._transitions[key].get(transition_key)

    def get_available_transitions(self, choice_group: str, domain: str, current_state: str) -> list[TransitionInfo]:
        """
        Get all valid transitions from a state.

        Args:
            choice_group: Choice group name
            domain: Domain namespace
            current_state: Current state value

        Returns:
            List of available TransitionInfo objects
        """
        key = self._get_key(choice_group, domain)

        if key not in self._transitions:
            return []

        available = []
        for (source, _target), info in self._transitions[key].items():
            if source == current_state:
                available.append(info)

        return available

    def get_transition_method_name(self, choice_group: str, domain: str, source: str, target: str) -> str | None:
        """
        Get the method name for a transition.

        Args:
            choice_group: Choice group name
            domain: Domain namespace
            source: Source state
            target: Target state

        Returns:
            Method name or None if not found
        """
        transition = self.get_transition(choice_group, domain, source, target)
        return transition.method_name if transition else None

    def validate_transition(self, choice_group: str, domain: str, source: str, target: str) -> bool:
        """
        Check if a transition is valid.

        Args:
            choice_group: Choice group name
            domain: Domain namespace
            source: Source state
            target: Target state

        Returns:
            True if transition is valid
        """
        return self.get_transition(choice_group, domain, source, target) is not None

    def build_registry_from_choices(self, choice_group: str, domain: str = "core") -> None:
        """
        Automatically populate registry from RichChoice metadata.

        Args:
            choice_group: Choice group name
            domain: Domain namespace
        """
        from apps.core.state_machine.builder import (
            determine_method_name_for_transition,
        )

        builder = StateTransitionBuilder(choice_group, domain)
        transition_graph = builder.build_transition_graph()

        for source, targets in transition_graph.items():
            source_metadata = builder.get_choice_metadata(source)

            for target in targets:
                # Use shared method name determination
                method_name = determine_method_name_for_transition(source, target)

                self.register_transition(
                    choice_group=choice_group,
                    domain=domain,
                    source=source,
                    target=target,
                    method_name=method_name,
                    metadata=source_metadata,
                )

    def clear_registry(
        self,
        choice_group: str | None = None,
        domain: str | None = None,
    ) -> None:
        """
        Clear registry entries for testing.

        Args:
            choice_group: Optional specific choice group to clear
            domain: Optional specific domain to clear
        """
        if choice_group and domain:
            key = self._get_key(choice_group, domain)
            if key in self._transitions:
                del self._transitions[key]
        else:
            self._transitions.clear()

    def export_transition_graph(self, choice_group: str, domain: str, format: str = "dict") -> Any:
        """
        Export state machine graph for visualization.

        Args:
            choice_group: Choice group name
            domain: Domain namespace
            format: Export format ('dict', 'mermaid', 'dot')

        Returns:
            Transition graph in requested format
        """
        key = self._get_key(choice_group, domain)

        if key not in self._transitions:
            return {} if format == "dict" else ""

        if format == "dict":
            graph: dict[str, list[str]] = {}
            for (source, target), _info in self._transitions[key].items():
                if source not in graph:
                    graph[source] = []
                graph[source].append(target)
            return graph

        elif format == "mermaid":
            lines = ["stateDiagram-v2"]
            for (source, target), info in self._transitions[key].items():
                lines.append(f"    {source} --> {target}: {info.method_name}")
            return "\n".join(lines)

        elif format == "dot":
            lines = ["digraph {"]
            for (source, target), info in self._transitions[key].items():
                lines.append(f'    "{source}" -> "{target}" ' f'[label="{info.method_name}"];')
            lines.append("}")
            return "\n".join(lines)

        else:
            raise ValueError(f"Unsupported format: {format}")

    def get_all_registered_groups(self) -> list[tuple[str, str]]:
        """
        Get all registered choice groups.

        Returns:
            List of (domain, choice_group) tuples
        """
        return list(self._transitions.keys())


# Global registry instance
registry_instance = TransitionRegistry()


# Callback registration helpers


def register_callback(
    model_class: type[models.Model],
    field_name: str,
    source: str,
    target: str,
    callback: Any,
    stage: str = "post",
) -> None:
    """
    Register a callback for a specific state transition.

    Args:
        model_class: The model class to register the callback for.
        field_name: The FSM field name.
        source: Source state (use '*' for any).
        target: Target state (use '*' for any).
        callback: The callback instance.
        stage: When to execute ('pre', 'post', 'error').
    """
    from .callback_base import CallbackStage, callback_registry

    callback_registry.register(
        model_class=model_class,
        field_name=field_name,
        source=source,
        target=target,
        callback=callback,
        stage=CallbackStage(stage) if isinstance(stage, str) else stage,
    )


def register_notification_callback(
    model_class: type[models.Model],
    field_name: str,
    source: str,
    target: str,
    notification_type: str,
    recipient_field: str = "submitted_by",
) -> None:
    """
    Register a notification callback for a state transition.

    Args:
        model_class: The model class.
        field_name: The FSM field name.
        source: Source state.
        target: Target state.
        notification_type: Type of notification to send.
        recipient_field: Field containing the recipient user.
    """
    from .callbacks.notifications import NotificationCallback

    callback = NotificationCallback(
        notification_type=notification_type,
        recipient_field=recipient_field,
    )
    register_callback(model_class, field_name, source, target, callback, "post")


def register_cache_invalidation(
    model_class: type[models.Model],
    field_name: str,
    cache_patterns: list[str] | None = None,
    source: str = "*",
    target: str = "*",
) -> None:
    """
    Register cache invalidation for state transitions.

    Args:
        model_class: The model class.
        field_name: The FSM field name.
        cache_patterns: List of cache key patterns to invalidate.
        source: Source state filter.
        target: Target state filter.
    """
    from .callbacks.cache import CacheInvalidationCallback

    callback = CacheInvalidationCallback(patterns=cache_patterns or [])
    register_callback(model_class, field_name, source, target, callback, "post")


def register_related_update(
    model_class: type[models.Model],
    field_name: str,
    update_func: Callable,
    source: str = "*",
    target: str = "*",
) -> None:
    """
    Register a related model update callback.

    Args:
        model_class: The model class.
        field_name: The FSM field name.
        update_func: Function to call with TransitionContext.
        source: Source state filter.
        target: Target state filter.
    """
    from .callbacks.related_updates import RelatedModelUpdateCallback

    callback = RelatedModelUpdateCallback(update_function=update_func)
    register_callback(model_class, field_name, source, target, callback, "post")


def register_transition_callbacks(cls: type[models.Model]) -> type[models.Model]:
    """
    Class decorator to auto-register callbacks from model's Meta.

    Usage:
        @register_transition_callbacks
        class EditSubmission(StateMachineMixin, TrackedModel):
            class Meta:
                transition_callbacks = {
                    ('PENDING', 'APPROVED'): [
                        SubmissionApprovedNotification(),
                        CacheInvalidationCallback(patterns=['*submission*']),
                    ]
                }

    Args:
        cls: The model class to decorate.

    Returns:
        The decorated model class.
    """
    meta = getattr(cls, "Meta", None)
    if not meta:
        return cls

    transition_callbacks = getattr(meta, "transition_callbacks", None)
    if not transition_callbacks:
        return cls

    # Get the FSM field name
    field_name = getattr(meta, "fsm_field", "status")

    # Register each callback
    for (source, target), callbacks in transition_callbacks.items():
        if not isinstance(callbacks, list | tuple):
            callbacks = [callbacks]

        for callback in callbacks:
            register_callback(
                model_class=cls,
                field_name=field_name,
                source=source,
                target=target,
                callback=callback,
            )

    logger.debug(f"Registered transition callbacks for {cls.__name__}")
    return cls


def discover_and_register_callbacks() -> None:
    """
    Discover and register callbacks for all models with StateMachineMixin.

    This function should be called in an AppConfig.ready() method.
    """
    from django.apps import apps

    registered_count = 0

    for model in apps.get_models():
        # Check if model has StateMachineMixin
        if not hasattr(model, "_fsm_metadata") and not hasattr(model, "Meta"):
            continue

        meta = getattr(model, "Meta", None)
        if not meta:
            continue

        transition_callbacks = getattr(meta, "transition_callbacks", None)
        if not transition_callbacks:
            continue

        # Get the FSM field name
        field_name = getattr(meta, "fsm_field", "status")

        # Register callbacks
        for (source, target), callbacks in transition_callbacks.items():
            if not isinstance(callbacks, list | tuple):
                callbacks = [callbacks]

            for callback in callbacks:
                register_callback(
                    model_class=model,
                    field_name=field_name,
                    source=source,
                    target=target,
                    callback=callback,
                )
                registered_count += 1

    logger.info(f"Discovered and registered {registered_count} transition callbacks")


__all__ = [
    "TransitionInfo",
    "TransitionRegistry",
    "registry_instance",
    # Callback registration helpers
    "register_callback",
    "register_notification_callback",
    "register_cache_invalidation",
    "register_related_update",
    "register_transition_callbacks",
    "discover_and_register_callbacks",
]
