import logging

from django.db.models import Q
from django.db.models.signals import post_delete, post_save
from django.dispatch import receiver

from apps.rides.models import Ride

from .models import Park

logger = logging.getLogger(__name__)


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


def update_park_search_text(park):
    """
    Update park's search_text computed field.

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

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


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

# Status values that should decrement ride counts
INACTIVE_STATUSES = {"CLOSED_PERM", "DEMOLISHED", "RELOCATED", "REMOVED"}


def update_park_ride_counts(park, old_status=None, new_status=None):
    """
    Update ride_count and coaster_count for a park.

    Args:
        park: The Park instance or park ID to update.
        old_status: The previous status of the ride (for FSM transitions).
        new_status: The new status of the ride (for FSM transitions).
    """
    if park is None:
        logger.warning("Cannot update counts: park is None")
        return

    # Get park ID
    park_id = park.pk if hasattr(park, "pk") else park

    try:
        # Fetch the park if we only have an ID
        if not hasattr(park, "rides"):
            park = Park.objects.get(id=park_id)

        # Build the query for active rides
        active_statuses = list(ACTIVE_STATUSES)
        operating_rides = Q(status__in=active_statuses)

        # Count total operating rides
        ride_count = park.rides.filter(operating_rides).count()

        # Count total operating roller coasters (including water coasters)
        coaster_count = park.rides.filter(operating_rides, category__in=["RC", "WC"]).count()

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

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

    except Park.DoesNotExist:
        logger.warning(f"Park {park_id} does not exist, cannot update counts")
    except Exception as e:
        logger.exception(f"Failed to update park counts for {park_id}: {e}")


def should_update_counts(old_status, new_status):
    """
    Determine if a status change should trigger count updates.

    Args:
        old_status: The previous status value.
        new_status: The new status value.

    Returns:
        True if counts should be updated, False otherwise.
    """
    if old_status == new_status:
        return False

    # Check if either status is in active or inactive sets
    old_active = old_status in ACTIVE_STATUSES if old_status else False
    new_active = new_status in ACTIVE_STATUSES if new_status else False
    old_inactive = old_status in INACTIVE_STATUSES if old_status else False
    new_inactive = new_status in INACTIVE_STATUSES if new_status else False

    # Update if transitioning to/from active status
    return old_active != new_active or old_inactive != new_inactive


@receiver(post_save, sender=Ride)
def ride_saved(sender, instance, created, **kwargs):
    """
    Update park counts when a ride is saved.

    Integrates with FSM transitions by checking for status changes.
    """
    # For new rides, always update counts
    if created:
        update_park_ride_counts(instance.park)
        return

    # Check if status changed using model's tracker if available
    if hasattr(instance, "tracker") and hasattr(instance.tracker, "has_changed"):
        if instance.tracker.has_changed("status"):
            old_status = instance.tracker.previous("status")
            new_status = instance.status
            if should_update_counts(old_status, new_status):
                logger.info(f"Ride {instance.pk} status changed: {old_status} → {new_status}")
                update_park_ride_counts(instance.park, old_status, new_status)
    else:
        # Fallback: always update counts on save
        update_park_ride_counts(instance.park)


@receiver(post_delete, sender=Ride)
def ride_deleted(sender, instance, **kwargs):
    """
    Update park counts when a ride is deleted.

    Logs the deletion for audit purposes.
    """
    logger.info(f"Ride {instance.pk} deleted from park {instance.park_id}")
    update_park_ride_counts(instance.park)


# FSM transition signal handlers


def handle_ride_status_transition(instance, source, target, user, **kwargs):
    """
    Handle ride status FSM transitions.

    This function is called by the FSM callback system when a ride
    status transition occurs.

    Args:
        instance: The Ride instance.
        source: The source state.
        target: The target state.
        user: The user who initiated the transition.
    """
    if should_update_counts(source, target):
        logger.info(f"FSM transition: Ride {instance.pk} {source} → {target} " f"by {user if user else 'system'}")
        update_park_ride_counts(instance.park, source, target)


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


@receiver(post_save, sender="parks.ParkLocation")
def update_park_search_text_on_location_change(sender, instance, **kwargs):
    """
    Update park search_text when location changes.

    When a park's location is updated (city, state, country changes),
    the park's search_text needs to be regenerated to include the new
    location information.
    """
    try:
        if hasattr(instance, "park") and instance.park:
            update_park_search_text(instance.park)
    except Exception as e:
        logger.exception(f"Failed to update park search_text on location change: {e}")


@receiver(post_save, sender="parks.Company")
def update_park_search_text_on_company_change(sender, instance, **kwargs):
    """
    Update park search_text when operator/owner name changes.

    When a company's name changes, all parks operated or owned by that
    company need their search_text regenerated.
    """
    try:
        # Update all parks operated by this company
        for park in instance.operated_parks.all():
            update_park_search_text(park)

        # Update all parks owned by this company
        for park in instance.owned_parks.all():
            update_park_search_text(park)

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