"""
Upcoming events service for Aimantis dashboard.

Generates chronological list of upcoming check-ins and check-outs
with optimized queries and proper prefetching.

ORM Optimization Strategy:
- NO select_related() for property/property_type because we only access booking fields
  (platform, length_of_stay, total_price, id, check_in_date, check_out_date)
- prefetch_related() is used for guests collection to avoid N+1 queries
- .only() explicitly selects minimal fields needed for event display
"""

from datetime import date
from itertools import chain
from typing import Dict, Any, List, Optional

from django.db.models import Prefetch

from bookings.models import Booking
from guests.models import Guest

from dashboard.constants import (
    DEFAULT_UPCOMING_EVENTS_LIMIT,
    MAX_UPCOMING_EVENTS_LIMIT,
    EVENT_TYPE_CHECK_IN,
    EVENT_TYPE_CHECK_OUT,
)
from dashboard.services.utils import get_today, normalize_channel_name


class UpcomingEventsService:
    """
    Service for generating upcoming dashboard events.
    
    Properly prefetches main guests to avoid N+1 queries
    and normalizes channel names for display.
    """
    
    def __init__(
        self,
        structure_id: Optional[int] = None,
        limit: Optional[int] = None,
    ):
        """
        Initialize the upcoming events service.
        
        Args:
            structure_id: Optional structure ID for multi-tenant filtering
            limit: Maximum number of events to return (default: 5)
        """
        self.structure_id = structure_id
        self.today = get_today()
        self.limit = min(limit or DEFAULT_UPCOMING_EVENTS_LIMIT, MAX_UPCOMING_EVENTS_LIMIT)
    
    def _get_structure_filter(self) -> Dict[str, Any]:
        """Get structure filter dict for queries."""
        return {"structure_id": self.structure_id} if self.structure_id else {}
    
    def _get_bookings_with_guests(self, start_date: date) -> List[Booking]:
        """
        Fetch bookings with prefetched main guests.
        
        Uses Prefetch to only load the main guest for each booking,
        avoiding N+1 queries in the event generation loop.
        
        For check-in events:
        - Only include future check-ins (check_in_date >= today)
        - Exclude already checked-in bookings from today
        
        ORM Optimization:
        - NO select_related(): We only access booking.platform, booking.length_of_stay,
          booking.total_price, booking.id, booking.check_in_date, booking.check_out_date.
          We do NOT access booking.property or booking.property_type objects.
        - prefetch_related('guests'): Required because we iterate booking.main_guest_list
          to get guest names. Without prefetch, this would cause N+1 queries.
        - .only(): Selects only fields needed for event display to minimize data transfer.
        
        Args:
            start_date: Fetch bookings from this date onwards
            
        Returns:
            List of Booking objects with prefetched guests
        """
        main_guest_prefetch = Prefetch(
            "guests",
            queryset=Guest.objects.filter(is_main_guest=True),
            to_attr="main_guest_list",
        )
        
        # For upcoming events, only include FUTURE events
        # Check-ins: check_in_date >= today (but not already checked in for today)
        return list(
            Booking.objects.filter(
                check_in_date__gte=start_date,
                **self._get_structure_filter()
            )
            # NO select_related - we only access booking fields, not property objects
            .prefetch_related(main_guest_prefetch)  # Required: guests collection is accessed
            .only(
                'id',
                'check_in_date',
                'check_out_date',
                'length_of_stay',
                'total_price',  # Used for event display (amount)
                'platform',     # Used for channel attribution
                'is_checked_in',  # Used to filter out already checked-in bookings
            )
            .order_by("check_in_date")
        )
    
    def _get_main_guest_name(self, booking: Booking) -> str:
        """
        Extract main guest name from booking.
        
        Uses the prefetched main_guest_list attribute.
        
        Args:
            booking: Booking object
            
        Returns:
            Main guest full name or "—" if not found
        """
        main_guests = getattr(booking, "main_guest_list", [])
        if main_guests:
            return main_guests[0].full_name
        return "—"
    
    def _build_event(
        self,
        booking: Booking,
        event_type: str,
        event_date: date,
    ) -> Dict[str, Any]:
        """
        Build a single event dictionary.
        
        Args:
            booking: Booking object
            event_type: "check_in" or "check_out"
            event_date: Date of the event
            
        Returns:
            Event dictionary
        """
        return {
            "booking_id": booking.id,
            "event_type": event_type,
            "guest_name": self._get_main_guest_name(booking),
            "channel": normalize_channel_name(booking.platform),
            "nights": booking.length_of_stay or 0,
            "amount": f"{booking.total_price:.2f}" if booking.total_price else "0.00",
            "date": event_date.isoformat(),
        }
    
    def get_events(self) -> List[Dict[str, Any]]:
        """
        Get chronological list of upcoming events.
        
        Fetches check-ins and check-outs, combines them,
        sorts by date, and removes duplicates.
        
        Rules:
        - Only include FUTURE events
        - Exclude already checked-in bookings for today
        - Show check-in OR check-out (not both for same booking)
        
        Returns:
            List of event dictionaries, sorted chronologically
        """
        # OPTIMIZATION: Fetch bookings ONCE and reuse for both check-in and check-out events
        # This avoids duplicate queries (was fetching same queryset twice before)
        all_bookings = self._get_bookings_with_guests(self.today)
        
        # Filter out already checked-in bookings for today's check-ins
        # (they already happened, not "upcoming")
        bookings_checkin = [
            b for b in all_bookings
            if not (b.check_in_date == self.today and b.is_checked_in)
        ]
        
        # Use same bookings for checkouts
        bookings_checkout = all_bookings
        
        # Combine into events list
        combined_events = list(
            chain(
                [
                    (EVENT_TYPE_CHECK_IN, b, b.check_in_date)
                    for b in bookings_checkin
                ],
                [
                    (EVENT_TYPE_CHECK_OUT, b, b.check_out_date)
                    for b in bookings_checkout
                ],
            )
        )
        
        # Sort by event date
        combined_events.sort(key=lambda x: x[2])
        
        # Build events list, avoiding duplicates
        events = []
        added_booking_ids = set()
        
        for event_type, booking, event_date in combined_events:
            # Skip check-out events if we already added the check-in
            if event_type == EVENT_TYPE_CHECK_OUT and booking.id in added_booking_ids:
                continue
            
            event = self._build_event(booking, event_type, event_date)
            events.append(event)
            
            # Track booking IDs to avoid duplicates
            if event_type == EVENT_TYPE_CHECK_IN:
                added_booking_ids.add(booking.id)
            
            # Stop if we've reached the limit
            if len(events) >= self.limit:
                break
        
        return events
