import logging

from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.utils import timezone

from apps.core.utils import capture_and_log

from .models import Ride

logger = logging.getLogger(__name__)


# =============================================================================
# Computed Field Maintenance
# =============================================================================


def update_ride_search_text(ride):
    """
    Update ride's search_text computed field.

    This is called when related objects (park, manufacturer, ride_model)
    change and might affect the ride's search text.
    """
    if ride is None:
        return

    try:
        ride._populate_computed_fields()
        ride.save(update_fields=["search_text"])
        logger.debug(f"Updated search_text for ride {ride.pk}")
    except Exception as e:
        logger.exception(f"Failed to update search_text for ride {ride.pk}: {e}")


@receiver(pre_save, sender=Ride)
def handle_ride_status(sender, instance, **kwargs):
    """
    Handle ride status changes based on closing date.

    Integrates with FSM transitions by using transition methods when available.
    """
    if not instance.closing_date:
        return

    today = timezone.now().date()

    # If we've reached the closing date and status is "CLOSING"
    if today >= instance.closing_date and instance.status == "CLOSING":
        target_status = instance.post_closing_status or "SBNO"

        logger.info(f"Ride {instance.pk} closing date reached, " f"transitioning to {target_status}")

        # Try to use FSM transition method if available
        transition_method_name = f"transition_to_{target_status.lower()}"
        if hasattr(instance, transition_method_name):
            # Check if transition is allowed before attempting
            if hasattr(instance, "can_proceed"):
                can_proceed = getattr(instance, f"can_transition_to_{target_status.lower()}", None)
                if can_proceed and callable(can_proceed) and not can_proceed():
                    logger.warning(f"FSM transition to {target_status} not allowed " f"for ride {instance.pk}")
                    # Fall back to direct status change
                    instance.status = target_status
                    instance.status_since = instance.closing_date
                    return

            try:
                method = getattr(instance, transition_method_name)
                method()
                instance.status_since = instance.closing_date
                logger.info(f"Applied FSM transition to {target_status} for ride {instance.pk}")
            except Exception as e:
                logger.exception(f"Failed to apply FSM transition for ride {instance.pk}: {e}")
                # Fall back to direct status change
                instance.status = target_status
                instance.status_since = instance.closing_date
        else:
            # No FSM transition method, use direct assignment
            instance.status = target_status
            instance.status_since = instance.closing_date


@receiver(pre_save, sender=Ride)
def validate_closing_status(sender, instance, **kwargs):
    """
    Validate that post_closing_status is set when entering CLOSING state.
    """
    # Only validate if this is an existing ride being updated
    if not instance.pk:
        return

    # Check if we're transitioning to CLOSING
    if instance.status == "CLOSING":
        # Ensure post_closing_status is set
        if not instance.post_closing_status:
            logger.warning(f"Ride {instance.pk} entering CLOSING without post_closing_status set")
            # Default to SBNO if not set
            instance.post_closing_status = "SBNO"

        # Ensure closing_date is set
        if not instance.closing_date:
            logger.warning(f"Ride {instance.pk} entering CLOSING without closing_date set")
            # Default to today's date
            instance.closing_date = timezone.now().date()


# FSM transition signal handlers


def handle_ride_transition_to_closing(instance, source, target, user, **kwargs):
    """
    Validate transition to CLOSING status.

    This function is called by the FSM callback system before a ride
    transitions to CLOSING status.

    Args:
        instance: The Ride instance.
        source: The source state.
        target: The target state.
        user: The user who initiated the transition.

    Returns:
        True if transition should proceed, False to abort.
    """
    if target != "CLOSING":
        return True

    if not instance.post_closing_status:
        # Capture to dashboard as a validation error
        capture_and_log(
            ValueError('post_closing_status not set for CLOSING transition'),
            f'Ride transition to CLOSING for ride {instance.pk}',
            source='signal',
            severity='medium',
        )
        return False

    if not instance.closing_date:
        logger.warning(f"Ride {instance.pk} transitioning to CLOSING without closing_date")

    return True


def apply_post_closing_status(instance, user=None):
    """
    Apply the post_closing_status to a ride in CLOSING state.

    This function can be called by the FSM callback system or directly
    when a ride's closing date is reached.

    Args:
        instance: The Ride instance in CLOSING state.
        user: The user initiating the change (optional).

    Returns:
        True if status was applied, False otherwise.
    """
    if instance.status != "CLOSING":
        logger.debug(f"Ride {instance.pk} not in CLOSING state, skipping")
        return False

    target_status = instance.post_closing_status
    if not target_status:
        logger.warning(f"Ride {instance.pk} in CLOSING but no post_closing_status set")
        return False

    # Try to use FSM transition
    transition_method_name = f"transition_to_{target_status.lower()}"
    if hasattr(instance, transition_method_name):
        try:
            method = getattr(instance, transition_method_name)
            method(user=user)
            instance.post_closing_status = None
            instance.save(update_fields=["post_closing_status"])
            logger.info(f"Applied post_closing_status {target_status} to ride {instance.pk}")
            return True
        except Exception as e:
            logger.exception(f"Failed to apply post_closing_status for ride {instance.pk}: {e}")
            return False
    else:
        # Direct status change
        instance.status = target_status
        instance.post_closing_status = None
        instance.status_since = timezone.now().date()
        instance.save(update_fields=["status", "post_closing_status", "status_since"])
        logger.info(f"Applied post_closing_status {target_status} to ride {instance.pk} (direct)")
        return True


# =============================================================================
# Computed Field Maintenance Signal Handlers
# =============================================================================


@receiver(post_save, sender="parks.Park")
def update_ride_search_text_on_park_change(sender, instance, **kwargs):
    """
    Update ride search_text when park name or location changes.

    When a park's name changes, all rides at that park need their
    search_text regenerated.
    """
    try:
        for ride in instance.rides.all():
            update_ride_search_text(ride)
    except Exception as e:
        logger.exception(f"Failed to update ride search_text on park change: {e}")


@receiver(post_save, sender="rides.Company")
def update_ride_search_text_on_company_change(sender, instance, **kwargs):
    """
    Update ride search_text when manufacturer/designer name changes.

    When a company's name changes, all rides manufactured or designed
    by that company need their search_text regenerated.
    """
    try:
        # Update all rides manufactured by this company
        for ride in instance.manufactured_rides.all():
            update_ride_search_text(ride)

        # Update all rides designed by this company
        for ride in instance.designed_rides.all():
            update_ride_search_text(ride)

    except Exception as e:
        logger.exception(f"Failed to update ride search_text on company change: {e}")


@receiver(post_save, sender="rides.RideModel")
def update_ride_search_text_on_ride_model_change(sender, instance, **kwargs):
    """
    Update ride search_text when ride model name changes.

    When a ride model's name changes, all rides using that model need
    their search_text regenerated.
    """
    try:
        for ride in instance.rides.all():
            update_ride_search_text(ride)
    except Exception as e:
        logger.exception(f"Failed to update ride search_text on ride model change: {e}")


# =============================================================================
# Automatic Name History Tracking
# =============================================================================


@receiver(pre_save, sender=Ride)
def track_ride_name_changes(sender, instance, **kwargs):
    """
    Automatically create RideNameHistory when a ride's name changes.

    This ensures versioning is automatic - when a ride is renamed,
    the previous name is preserved in the name history.
    """
    if not instance.pk:
        return  # Skip new rides

    try:
        old_instance = Ride.objects.get(pk=instance.pk)

        if old_instance.name != instance.name:
            from .models import RideNameHistory

            current_year = timezone.now().year

            # Create history entry for the old name
            RideNameHistory.objects.create(
                ride=instance,
                former_name=old_instance.name,
                to_year=current_year,
                reason="Name changed",
            )

            logger.info(
                f"Ride {instance.pk} name changed from '{old_instance.name}' "
                f"to '{instance.name}' - history entry created"
            )
    except Ride.DoesNotExist:
        pass  # New ride, no history to track
    except Exception as e:
        logger.exception(f"Failed to track name change for ride {instance.pk}: {e}")

