"""
Related model update callbacks for FSM state transitions.

This module provides callback implementations that update related models
when state transitions occur.
"""

import logging
from collections.abc import Callable

from django.conf import settings
from django.db import models, transaction

from ..callback_base import PostTransitionCallback, TransitionContext

logger = logging.getLogger(__name__)


class RelatedModelUpdateCallback(PostTransitionCallback):
    """
    Base callback for updating related models after state transitions.

    Executes custom update logic when a state transition completes.
    """

    name: str = "RelatedModelUpdateCallback"

    def __init__(
        self,
        update_function: Callable[[TransitionContext], bool] | None = None,
        use_transaction: bool = True,
        **kwargs,
    ):
        """
        Initialize the related model update callback.

        Args:
            update_function: Optional function to call with the context.
            use_transaction: Whether to wrap updates in a transaction.
            **kwargs: Additional arguments passed to parent class.
        """
        super().__init__(**kwargs)
        self.update_function = update_function
        self.use_transaction = use_transaction

    def should_execute(self, context: TransitionContext) -> bool:
        """Check if related updates are enabled."""
        callback_settings = getattr(settings, "STATE_MACHINE_CALLBACKS", {})
        if not callback_settings.get("related_updates_enabled", True):
            logger.debug("Related model updates disabled via settings")
            return False
        return True

    def perform_update(self, context: TransitionContext) -> bool:
        """
        Perform the actual update logic.

        Override this method in subclasses to define specific update behavior.

        Args:
            context: The transition context.

        Returns:
            True if update succeeded, False otherwise.
        """
        if self.update_function:
            return self.update_function(context)
        return True

    def execute(self, context: TransitionContext) -> bool:
        """Execute the related model update."""
        try:
            if self.use_transaction:
                with transaction.atomic():
                    return self.perform_update(context)
            else:
                return self.perform_update(context)

        except Exception as e:
            logger.exception(f"Failed to update related models for {context}: {e}")
            return False


class ParkCountUpdateCallback(RelatedModelUpdateCallback):
    """
    Updates park ride counts when ride status changes.

    Recalculates ride_count and coaster_count on the parent Park
    when a Ride transitions to or from an operational status.
    """

    name: str = "ParkCountUpdateCallback"

    # Status values that count as "active" rides
    ACTIVE_STATUSES = {"OPERATING", "SEASONAL", "UNDER_CONSTRUCTION"}

    # Status values that indicate a ride is no longer countable
    INACTIVE_STATUSES = {"CLOSED_PERM", "DEMOLISHED", "RELOCATED", "REMOVED"}

    def should_execute(self, context: TransitionContext) -> bool:
        """Only execute when status affects ride counts."""
        if not super().should_execute(context):
            return False

        # Check if this transition affects ride counts
        source = context.source_state
        target = context.target_state

        # Execute if transitioning to/from an active or inactive status
        source_affects = source in self.ACTIVE_STATUSES or source in self.INACTIVE_STATUSES
        target_affects = target in self.ACTIVE_STATUSES or target in self.INACTIVE_STATUSES

        return source_affects or target_affects

    # Category value for roller coasters (from rides domain choices)
    COASTER_CATEGORY = "RC"

    def perform_update(self, context: TransitionContext) -> bool:
        """Update park ride counts."""
        instance = context.instance

        # Get the parent park
        park = getattr(instance, "park", None)
        if not park:
            logger.debug(f"No park found for ride {instance.pk}")
            return True

        try:
            # Import here to avoid circular imports
            from apps.parks.models.parks import Park
            from apps.rides.models.rides import Ride

            # Get the park ID (handle both object and ID)
            park_id = park.pk if hasattr(park, "pk") else park

            # Calculate new counts efficiently
            ride_queryset = Ride.objects.filter(park_id=park_id)

            # Count active rides
            active_statuses = list(self.ACTIVE_STATUSES)
            ride_count = ride_queryset.filter(status__in=active_statuses).count()

            # Count active coasters (category='RC' for Roller Coaster)
            coaster_count = ride_queryset.filter(status__in=active_statuses, category=self.COASTER_CATEGORY).count()

            # Update park counts
            Park.objects.filter(id=park_id).update(
                ride_count=ride_count,
                coaster_count=coaster_count,
            )

            logger.info(f"Updated park {park_id} counts: " f"ride_count={ride_count}, coaster_count={coaster_count}")
            return True

        except Exception as e:
            logger.exception(f"Failed to update park counts for {instance.pk}: {e}")
            return False


class SearchTextUpdateCallback(RelatedModelUpdateCallback):
    """
    Recalculates search_text field when status changes.

    Updates the search_text field to include the new status label
    for search indexing purposes.
    """

    name: str = "SearchTextUpdateCallback"

    def perform_update(self, context: TransitionContext) -> bool:
        """Update the search_text field."""
        instance = context.instance

        # Check if instance has search_text field
        if not hasattr(instance, "search_text"):
            logger.debug(f"{context.model_name} has no search_text field")
            return True

        try:
            # Call the model's update_search_text method if available
            if hasattr(instance, "update_search_text"):
                instance.update_search_text()
                instance.save(update_fields=["search_text"])
                logger.info(f"Updated search_text for {context.model_name} {instance.pk}")
            else:
                # Build search text manually
                self._build_search_text(instance, context)

            return True

        except Exception as e:
            logger.exception(f"Failed to update search_text for {instance.pk}: {e}")
            return False

    def _build_search_text(
        self,
        instance: models.Model,
        context: TransitionContext,
    ) -> None:
        """Build search text from instance fields."""
        parts = []

        # Common searchable fields
        for field in ["name", "title", "description", "location"]:
            value = getattr(instance, field, None)
            if value:
                parts.append(str(value))

        # Add status label
        status_field = getattr(instance, context.field_name, None)
        if status_field:
            # Try to get the display label
            display_method = f"get_{context.field_name}_display"
            if hasattr(instance, display_method):
                parts.append(getattr(instance, display_method)())
            else:
                parts.append(str(status_field))

        # Update search_text
        instance.search_text = " ".join(parts)
        instance.save(update_fields=["search_text"])


class ComputedFieldUpdateCallback(RelatedModelUpdateCallback):
    """
    Generic callback for updating computed fields after transitions.

    Recalculates specified computed fields when a state transition occurs.
    """

    name: str = "ComputedFieldUpdateCallback"

    def __init__(
        self,
        computed_fields: list[str] | None = None,
        update_method: str | None = None,
        **kwargs,
    ):
        """
        Initialize computed field update callback.

        Args:
            computed_fields: List of field names to update.
            update_method: Name of method to call for updating fields.
            **kwargs: Additional arguments.
        """
        super().__init__(**kwargs)
        self.computed_fields = computed_fields or []
        self.update_method = update_method

    def perform_update(self, context: TransitionContext) -> bool:
        """Update computed fields."""
        instance = context.instance

        try:
            # Call update method if specified
            if self.update_method:
                method = getattr(instance, self.update_method, None)
                if method and callable(method):
                    method()

            # Update specific fields
            updated_fields = []
            for field_name in self.computed_fields:
                update_method_name = f"compute_{field_name}"
                if hasattr(instance, update_method_name):
                    method = getattr(instance, update_method_name)
                    if callable(method):
                        new_value = method()
                        setattr(instance, field_name, new_value)
                        updated_fields.append(field_name)

            # Save updated fields
            if updated_fields:
                instance.save(update_fields=updated_fields)
                logger.info(f"Updated computed fields {updated_fields} for " f"{context.model_name} {instance.pk}")

            return True

        except Exception as e:
            logger.exception(f"Failed to update computed fields for {instance.pk}: {e}")
            return False


class RideStatusUpdateCallback(RelatedModelUpdateCallback):
    """
    Handles ride-specific updates when status changes.

    Updates post_closing_status, closing_date, and related fields.
    """

    name: str = "RideStatusUpdateCallback"

    def should_execute(self, context: TransitionContext) -> bool:
        """Execute for specific ride status transitions."""
        if not super().should_execute(context):
            return False

        # Only execute for Ride model
        return context.model_name == "Ride"

    def perform_update(self, context: TransitionContext) -> bool:
        """Perform ride-specific status updates."""
        instance = context.instance
        target = context.target_state

        try:
            # Handle CLOSING → post_closing_status transition
            if context.source_state == "CLOSING" and target != "CLOSING":
                post_closing_status = getattr(instance, "post_closing_status", None)
                if post_closing_status and target == post_closing_status:
                    # Clear post_closing_status after applying it
                    instance.post_closing_status = None
                    instance.save(update_fields=["post_closing_status"])
                    logger.info(f"Cleared post_closing_status for ride {instance.pk}")

            return True

        except Exception as e:
            logger.exception(f"Failed to update ride status fields for {instance.pk}: {e}")
            return False


class ModerationQueueUpdateCallback(RelatedModelUpdateCallback):
    """
    Updates moderation queue and statistics when submissions change state.
    """

    name: str = "ModerationQueueUpdateCallback"

    def should_execute(self, context: TransitionContext) -> bool:
        """Execute for moderation-related models."""
        if not super().should_execute(context):
            return False

        # Only for submission and report models
        model_name = context.model_name
        return model_name in ("EditSubmission", "PhotoSubmission", "ModerationReport")

    def perform_update(self, context: TransitionContext) -> bool:
        """Update moderation queue entries."""
        instance = context.instance
        target = context.target_state

        try:
            # Mark related queue items as completed when submission is resolved
            if target in ("APPROVED", "REJECTED", "RESOLVED"):
                self._update_queue_items(instance, context)

            return True

        except Exception as e:
            logger.exception(f"Failed to update moderation queue for {instance.pk}: {e}")
            return False

    def _update_queue_items(
        self,
        instance: models.Model,
        context: TransitionContext,
    ) -> None:
        """Update related queue items to completed status."""
        try:
            from apps.moderation.models import ModerationQueue

            # Find related queue items
            content_type_id = self._get_content_type_id(instance)
            if not content_type_id:
                return

            queue_items = ModerationQueue.objects.filter(
                content_type_id=content_type_id,
                object_id=instance.pk,
                status="IN_PROGRESS",
            )

            for item in queue_items:
                if hasattr(item, "complete"):
                    item.complete(user=context.user)
                else:
                    item.status = "COMPLETED"
                    item.save(update_fields=["status"])

            if queue_items.exists():
                logger.info(f"Marked {queue_items.count()} queue items as completed")

        except ImportError:
            logger.debug("ModerationQueue model not available")
        except Exception as e:
            logger.warning(f"Failed to update queue items: {e}")

    def _get_content_type_id(self, instance: models.Model) -> int | None:
        """Get content type ID for the instance."""
        try:
            from django.contrib.contenttypes.models import ContentType

            content_type = ContentType.objects.get_for_model(type(instance))
            return content_type.pk
        except Exception:
            return None
