"""Model integration utilities for applying state machines to Django models."""

from collections.abc import Callable
from typing import Any

from django.db import models
from django_fsm import can_proceed

from apps.core.state_machine.builder import (
    StateTransitionBuilder,
    determine_method_name_for_transition,
)
from apps.core.state_machine.decorators import TransitionMethodFactory
from apps.core.state_machine.guards import (
    CompositeGuard,
    create_guard_from_drf_permission,
    extract_guards_from_metadata,
)
from apps.core.state_machine.registry import (
    TransitionInfo,
    registry_instance,
)
from apps.core.state_machine.validators import MetadataValidator


def apply_state_machine(
    model_class: type[models.Model],
    field_name: str,
    choice_group: str,
    domain: str = "core",
) -> None:
    """
    Apply state machine to a Django model.

    Args:
        model_class: Django model class
        field_name: Name of the state field
        choice_group: Choice group name
        domain: Domain namespace

    Raises:
        ValueError: If validation fails
    """
    # Validate metadata
    validator = MetadataValidator(choice_group, domain)
    result = validator.validate_choice_group()

    if not result.is_valid:
        error_messages = [str(e) for e in result.errors]
        raise ValueError("Cannot apply state machine - validation failed:\n" + "\n".join(error_messages))

    # Build transition registry
    registry_instance.build_registry_from_choices(choice_group, domain)

    # Generate and attach transition methods
    generate_transition_methods_for_model(model_class, field_name, choice_group, domain)


def generate_transition_methods_for_model(
    model_class: type[models.Model],
    field_name: str,
    choice_group: str,
    domain: str = "core",
) -> None:
    """
    Dynamically create transition methods on a model.

    Args:
        model_class: Django model class
        field_name: Name of the state field
        choice_group: Choice group name
        domain: Domain namespace
    """
    # Get the actual field from the model class - django-fsm 3.x requires
    # the field object, not just the string name, when creating methods dynamically
    field = model_class._meta.get_field(field_name)
    
    builder = StateTransitionBuilder(choice_group, domain)
    transition_graph = builder.build_transition_graph()
    factory = TransitionMethodFactory()

    # Group transitions by target to avoid overwriting methods
    # {target: [source1, source2, ...]}
    target_to_sources: dict[str, list[str]] = {}
    for source, targets in transition_graph.items():
        for target in targets:
            if target not in target_to_sources:
                target_to_sources[target] = []
            target_to_sources[target].append(source)

    # Create one transition method per target, handling all valid sources
    for target, sources in target_to_sources.items():
        # Use shared method name determination (all sources go to same target = same method)
        method_name = determine_method_name_for_transition(sources[0], target)
        
        # Get target metadata for guards
        target_metadata = builder.get_choice_metadata(target)
        
        # For permission guard, use target metadata only (all sources share the same permission)
        # Source-specific guards would need to be checked via conditions, but for FSM 3.x
        # we use permission which gets called with (instance, user)
        target_guards = extract_guards_from_metadata(target_metadata)
        
        # Create combined guard if we have multiple guards
        combined_guard: Callable | None = None
        if len(target_guards) == 1:
            combined_guard = target_guards[0]
        elif len(target_guards) > 1:
            combined_guard = CompositeGuard(guards=target_guards, operator="AND")

        # Use list of sources for transitions with multiple valid source states
        source_value = sources if len(sources) > 1 else sources[0]

        # Create appropriate transition method - pass actual field object
        if "approve" in method_name or "accept" in method_name:
            method = factory.create_approve_method(
                source=source_value,
                target=target,
                field=field,
                permission_guard=combined_guard,
            )
        elif "reject" in method_name or "deny" in method_name:
            method = factory.create_reject_method(
                source=source_value,
                target=target,
                field=field,
                permission_guard=combined_guard,
            )
        elif "escalate" in method_name:
            method = factory.create_escalate_method(
                source=source_value,
                target=target,
                field=field,
                permission_guard=combined_guard,
            )
        else:
            method = factory.create_generic_transition_method(
                method_name=method_name,
                source=source_value,
                target=target,
                field=field,
                permission_guard=combined_guard,
            )

        # Attach method to model class
        setattr(model_class, method_name, method)


class StateMachineModelMixin:
    """Mixin providing state machine helper methods for models."""

    def get_available_state_transitions(self, field_name: str = "status") -> list[TransitionInfo]:
        """
        Get available transitions from current state.

        Args:
            field_name: Name of the state field

        Returns:
            List of available TransitionInfo objects
        """
        # Get choice group and domain from field
        field = self._meta.get_field(field_name)
        if not hasattr(field, "choice_group"):
            return []

        choice_group = field.choice_group
        domain = field.domain
        current_state = getattr(self, field_name)

        return registry_instance.get_available_transitions(choice_group, domain, current_state)

    def can_transition_to(
        self,
        target_state: str,
        field_name: str = "status",
        user: Any | None = None,
    ) -> bool:
        """
        Check if transition to target state is allowed.

        Args:
            target_state: Target state value
            field_name: Name of the state field
            user: User attempting transition

        Returns:
            True if transition is allowed
        """
        current_state = getattr(self, field_name)

        # Get field metadata
        field = self._meta.get_field(field_name)
        if not hasattr(field, "choice_group"):
            return False

        choice_group = field.choice_group
        domain = field.domain

        # Check if transition exists in registry
        transition = registry_instance.get_transition(choice_group, domain, current_state, target_state)

        if not transition:
            return False

        # Get transition method and check if it can proceed
        method_name = transition.method_name
        method = getattr(self, method_name, None)

        if method is None:
            return False

        # Use django-fsm's can_proceed
        return can_proceed(method)

    def get_transition_method(self, target_state: str, field_name: str = "status") -> Callable | None:
        """
        Get the transition method for moving to target state.

        Args:
            target_state: Target state value
            field_name: Name of the state field

        Returns:
            Transition method or None
        """
        current_state = getattr(self, field_name)

        field = self._meta.get_field(field_name)
        if not hasattr(field, "choice_group"):
            return None

        choice_group = field.choice_group
        domain = field.domain

        transition = registry_instance.get_transition(choice_group, domain, current_state, target_state)

        if not transition:
            return None

        return getattr(self, transition.method_name, None)

    def execute_transition(
        self,
        target_state: str,
        field_name: str = "status",
        user: Any | None = None,
        **kwargs: Any,
    ) -> bool:
        """
        Execute a transition to target state.

        Args:
            target_state: Target state value
            field_name: Name of the state field
            user: User executing transition
            **kwargs: Additional arguments for transition method

        Returns:
            True if transition succeeded

        Raises:
            ValueError: If transition is not allowed
        """
        if not self.can_transition_to(target_state, field_name, user):
            raise ValueError(f"Cannot transition to {target_state} from current state")

        method = self.get_transition_method(target_state, field_name)
        if method is None:
            raise ValueError(f"No transition method found for {target_state}")

        # Execute transition
        method(self, user=user, **kwargs)
        return True


def state_machine_model(field_name: str, choice_group: str, domain: str = "core"):
    """
    Class decorator to automatically apply state machine to models.

    Args:
        field_name: Name of the state field
        choice_group: Choice group name
        domain: Domain namespace

    Returns:
        Decorator function
    """

    def decorator(model_class: type[models.Model]) -> type[models.Model]:
        """Apply state machine to model class."""
        apply_state_machine(model_class, field_name, choice_group, domain)
        return model_class

    return decorator


def validate_model_state_machine(model_class: type[models.Model], field_name: str) -> bool:
    """
    Ensure model is properly configured with state machine.

    Args:
        model_class: Django model class
        field_name: Name of the state field

    Returns:
        True if properly configured

    Raises:
        ValueError: If configuration is invalid
    """
    # Check field exists
    try:
        field = model_class._meta.get_field(field_name)
    except Exception:
        raise ValueError(f"Field {field_name} not found on {model_class}") from None

    # Check if field has choice_group attribute
    if not hasattr(field, "choice_group"):
        raise ValueError(f"Field {field_name} is not a RichFSMField or RichChoiceField")

    # Validate metadata
    choice_group = field.choice_group
    domain = field.domain

    validator = MetadataValidator(choice_group, domain)
    result = validator.validate_choice_group()

    if not result.is_valid:
        error_messages = [str(e) for e in result.errors]
        raise ValueError("State machine validation failed:\n" + "\n".join(error_messages))

    return True


__all__ = [
    "apply_state_machine",
    "generate_transition_methods_for_model",
    "StateMachineModelMixin",
    "state_machine_model",
    "validate_model_state_machine",
    "create_guard_from_drf_permission",
]
