"""
Charts service for Aimantis dashboard.

Generates monthly occupancy chart data for the last 12 months
using optimized calculations.
"""

import calendar
from datetime import date, timedelta
from typing import Dict, Any, List, Optional

from dateutil.relativedelta import relativedelta

from bookings.models import Booking
from properties.models import Property

from dashboard.constants import MONTHLY_CHART_MONTHS, OCCUPANCY_ROUNDING
from dashboard.services.utils import (
    calculate_overlapping_nights,
    get_today,
    safe_divide,
    round_occupancy,
)


class ChartsService:
    """
    Service for generating chart data for the dashboard.
    
    Currently provides monthly occupancy data for the last 12 months.
    """
    
    def __init__(self, structure_id: Optional[int] = None):
        """
        Initialize the charts service.
        
        Args:
            structure_id: Optional structure ID for multi-tenant filtering
        """
        self.structure_id = structure_id
        self.today = get_today()
    
    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_total_rooms(self) -> int:
        """Get total room count for the structure."""
        qs = Property.objects.all()
        if self.structure_id:
            qs = qs.filter(structure_id=self.structure_id)
        return qs.count()
    
    def _calculate_month_occupancy(
        self,
        month_start: date,
        month_end: date,
    ) -> int:
        """
        Calculate occupancy percentage for a specific month.
        
        CRITICAL BUSINESS RULE:
        - Counts DISTINCT occupied properties per day (not booking rows)
        - Occupancy can NEVER exceed 100%
        - Multiple bookings for the same room on the same day = 1 occupied room
        
        Args:
            month_start: First day of the month
            month_end: Last day of the month
            
        Returns:
            Occupancy percentage (0-100), capped at 100
        """
        total_rooms = self._get_total_rooms()
        if total_rooms == 0:
            return 0
        
        # Calculate total possible nights in month
        days_in_month = (month_end - month_start).days + 1
        total_possible_nights = total_rooms * days_in_month
        
        if total_possible_nights == 0:
            return 0
        
        # Fetch all bookings overlapping the month (active reservations based on dates)
        # ACTIVE STAY LOGIC: check_in_date <= month_end AND check_out_date > month_start
        # This counts all reservations for the period, not just checked-in bookings
        # ORM Optimization:
        # - NO select_related: We only access booking.property_id and date fields
        # - .only(): Selects minimal fields needed for DISTINCT property counting
        bookings = Booking.objects.filter(
            check_in_date__lte=month_end,
            check_out_date__gt=month_start,
            **self._get_structure_filter()
        ).only(
            'property_id',        # CRITICAL: Needed for DISTINCT room counting
            'check_in_date',
            'check_out_date',
        )
        
        # CRITICAL FIX: Count DISTINCT occupied properties per day
        # This prevents occupancy from exceeding 100% when multiple bookings
        # overlap for the same room (e.g., double-booking edge cases)
        
        # Build a set of (date, property_id) tuples for each occupied night
        occupied_room_nights = set()
        
        # Month end is inclusive in charts, so add 1 day for exclusive comparison
        month_end_exclusive = month_end + timedelta(days=1)
        
        for booking in bookings:
            # For each booking, mark each night as occupied for this property
            current_date = max(booking.check_in_date, month_start)
            booking_end = min(booking.check_out_date, month_end_exclusive)
            
            while current_date < booking_end:
                # Tuple: (date, property_id) ensures DISTINCT counting
                occupied_room_nights.add((current_date, booking.property_id))
                current_date += timedelta(days=1)
        
        # Total reserved nights = count of unique (date, property_id) combinations
        total_reserved_nights = len(occupied_room_nights)
        
        # Calculate percentage (capped at 100%)
        occupancy = safe_divide(
            total_reserved_nights * 100,
            total_possible_nights,
            default=0.0,
        )
        
        # CRITICAL: Cap at 100% (defensive, should never exceed due to DISTINCT logic)
        occupancy_capped = min(occupancy, 100)
        
        return round_occupancy(occupancy_capped, OCCUPANCY_ROUNDING)
    
    def get_monthly_occupancy(self) -> List[Dict[str, Any]]:
        """
        Generate monthly occupancy data for the last N months.
        
        Returns:
            List of dicts with month, year, and occupancy percentage
        """
        monthly_data = []
        
        # Iterate from oldest to newest month
        for i in range(MONTHLY_CHART_MONTHS - 1, -1, -1):
            # Calculate month boundaries
            month_start = (self.today.replace(day=1) - relativedelta(months=i))
            last_day = calendar.monthrange(month_start.year, month_start.month)[1]
            month_end = month_start.replace(day=last_day)
            
            # Calculate occupancy
            occupancy = self._calculate_month_occupancy(month_start, month_end)
            
            monthly_data.append({
                "month": month_start.strftime("%b"),
                "year": month_start.strftime("%Y"),
                "occupancy": occupancy,
            })
        
        return monthly_data
    
    def get_charts_data(self) -> Dict[str, Any]:
        """
        Get all chart data in a single call.
        
        Returns:
            Dict with chart data structures
        """
        return {
            "monthly_occupancy": self.get_monthly_occupancy(),
        }
