"""
Status Machine Service

Enforces valid status transitions for jobs/events and records history.
"""
from django.utils import timezone
from django.db import transaction
from typing import Optional, Tuple
from decimal import Decimal

from smoothschedule.scheduling.schedule.models import Event
from smoothschedule.communication.mobile.models import EventStatusHistory, EmployeeLocationUpdate


class StatusTransitionError(Exception):
    """Raised when an invalid status transition is attempted."""
    pass


class StatusMachine:
    """
    Manages event/job status transitions with validation and audit logging.

    Status Flow:
        SCHEDULED → EN_ROUTE → IN_PROGRESS → COMPLETED
                           ↘              ↘
                            CANCELED       NOSHOW
                                              ↓
                                       AWAITING_PAYMENT → PAID

    Rules:
    - Only assigned employees can change status (except admin override)
    - Cannot go backward in the flow (no COMPLETED → SCHEDULED)
    - Location tracking stops on COMPLETED, CANCELED, NOSHOW
    - Some transitions trigger customer notifications
    """

    # Define valid transitions: current_status -> [allowed_next_statuses]
    VALID_TRANSITIONS = {
        Event.Status.SCHEDULED: [
            Event.Status.EN_ROUTE,
            Event.Status.IN_PROGRESS,  # Can skip EN_ROUTE if already at location
            Event.Status.CANCELED,
        ],
        Event.Status.EN_ROUTE: [
            Event.Status.IN_PROGRESS,
            Event.Status.CANCELED,
            Event.Status.NOSHOW,  # Customer not available when arrived
        ],
        Event.Status.IN_PROGRESS: [
            Event.Status.COMPLETED,
            Event.Status.CANCELED,
            Event.Status.NOSHOW,
        ],
        Event.Status.COMPLETED: [
            Event.Status.AWAITING_PAYMENT,  # For variable pricing
        ],
        Event.Status.AWAITING_PAYMENT: [
            Event.Status.PAID,
        ],
        # Terminal states - no transitions allowed
        Event.Status.CANCELED: [],
        Event.Status.PAID: [],
        Event.Status.NOSHOW: [],
    }

    # Statuses that allow location tracking
    TRACKING_STATUSES = {
        Event.Status.EN_ROUTE,
        Event.Status.IN_PROGRESS,
    }

    # Transitions that should trigger customer notification
    NOTIFY_CUSTOMER_TRANSITIONS = {
        (Event.Status.SCHEDULED, Event.Status.EN_ROUTE): 'en_route_notification',
        (Event.Status.EN_ROUTE, Event.Status.IN_PROGRESS): 'arrived_notification',
        (Event.Status.IN_PROGRESS, Event.Status.COMPLETED): 'completed_notification',
    }

    def __init__(self, tenant, user):
        """
        Initialize the status machine.

        Args:
            tenant: The tenant (business) context
            user: The user making the status change
        """
        self.tenant = tenant
        self.user = user

    def can_transition(self, event: Event, new_status: str) -> Tuple[bool, str]:
        """
        Check if a status transition is valid.

        Args:
            event: The event to check
            new_status: The proposed new status

        Returns:
            Tuple of (is_valid, reason_if_invalid)
        """
        current_status = event.status

        # Same status - no change needed
        if current_status == new_status:
            return True, ""

        # Check if transition is in the allowed list
        allowed = self.VALID_TRANSITIONS.get(current_status, [])
        if new_status not in allowed:
            return False, (
                f"Cannot transition from {current_status} to {new_status}. "
                f"Allowed transitions: {', '.join(allowed) if allowed else 'none (terminal state)'}"
            )

        return True, ""

    def is_employee_assigned(self, event: Event) -> bool:
        """
        Check if the current user is assigned to this event as staff.

        Returns True if:
        - User is a participant with STAFF role, OR
        - User is linked to a Resource that is a participant
        """
        from django.contrib.contenttypes.models import ContentType
        from smoothschedule.scheduling.schedule.models import Participant, Resource
        from smoothschedule.identity.users.models import User

        # Check if user is directly a participant
        user_ct = ContentType.objects.get_for_model(User)
        if Participant.objects.filter(
            event=event,
            content_type=user_ct,
            object_id=self.user.id,
            role__in=[Participant.Role.STAFF, Participant.Role.RESOURCE]
        ).exists():
            return True

        # Check if user is linked to a Resource that is a participant
        resource_ct = ContentType.objects.get_for_model(Resource)
        user_resources = Resource.objects.filter(user=self.user).values_list('id', flat=True)

        if Participant.objects.filter(
            event=event,
            content_type=resource_ct,
            object_id__in=user_resources,
            role__in=[Participant.Role.STAFF, Participant.Role.RESOURCE]
        ).exists():
            return True

        return False

    def can_user_change_status(self, event: Event) -> Tuple[bool, str]:
        """
        Check if the current user has permission to change this event's status.

        Returns:
            Tuple of (is_allowed, reason_if_not)
        """
        from smoothschedule.identity.users.models import User

        # Owners and managers can always change status
        if self.user.role in [User.Role.TENANT_OWNER, User.Role.TENANT_MANAGER]:
            return True, ""

        # Staff must be assigned to the event
        if self.user.role == User.Role.TENANT_STAFF:
            if self.is_employee_assigned(event):
                return True, ""
            return False, "You are not assigned to this job"

        return False, "You do not have permission to change job status"

    @transaction.atomic
    def transition(
        self,
        event: Event,
        new_status: str,
        notes: str = "",
        latitude: Optional[Decimal] = None,
        longitude: Optional[Decimal] = None,
        source: str = "mobile_app",
        skip_notifications: bool = False,
    ) -> Event:
        """
        Transition an event to a new status.

        Args:
            event: The event to update
            new_status: The target status
            notes: Optional notes about the change
            latitude: GPS latitude at time of change
            longitude: GPS longitude at time of change
            source: Where the change originated
            skip_notifications: If True, don't send customer notifications

        Returns:
            The updated event

        Raises:
            StatusTransitionError: If the transition is not allowed
        """
        old_status = event.status

        # Check permission
        can_change, reason = self.can_user_change_status(event)
        if not can_change:
            raise StatusTransitionError(reason)

        # Check if transition is valid
        is_valid, reason = self.can_transition(event, new_status)
        if not is_valid:
            raise StatusTransitionError(reason)

        # Perform the transition
        event.status = new_status
        event.save(update_fields=['status', 'updated_at'])

        # Record in history
        EventStatusHistory.objects.create(
            tenant=self.tenant,
            event_id=event.id,
            old_status=old_status,
            new_status=new_status,
            changed_by=self.user,
            notes=notes,
            latitude=latitude,
            longitude=longitude,
            source=source,
        )

        # Handle location tracking cleanup if needed
        if new_status not in self.TRACKING_STATUSES:
            self._stop_location_tracking(event)

        # Emit status change signal (triggers notifications and plugin hooks)
        from smoothschedule.scheduling.schedule.signals import emit_status_change
        emit_status_change(
            event=event,
            old_status=old_status,
            new_status=new_status,
            changed_by=self.user,
            tenant=self.tenant,
            skip_notifications=skip_notifications,
        )

        return event

    def _stop_location_tracking(self, event: Event):
        """
        Mark that location tracking should stop for this event.

        The mobile app checks this to know when to stop sending updates.
        We don't delete existing location data - it's kept for the route history.
        """
        # Future: Could add a field to mark tracking as stopped
        # For now, the app will check the event status before sending updates
        pass

    def get_allowed_transitions(self, event: Event) -> list:
        """
        Get the list of statuses this event can transition to.

        Returns:
            List of allowed status values
        """
        return self.VALID_TRANSITIONS.get(event.status, [])

    def get_status_history(self, event_id: int, limit: int = 50) -> list:
        """
        Get the status change history for an event.

        Args:
            event_id: The event ID
            limit: Maximum number of records to return

        Returns:
            List of EventStatusHistory records
        """
        return list(
            EventStatusHistory.objects.filter(
                tenant=self.tenant,
                event_id=event_id
            ).select_related('changed_by')[:limit]
        )
