"""
Dashboard Active-Stay Logic Validation Tests.

Tests that occupancy metrics use date-based active-stay logic
(check_in <= date < check_out) instead of is_checked_in=True filter.

Run with:
    python manage.py test dashboard.tests_active_stay_validation -v 2
"""

from datetime import date, timedelta
from decimal import Decimal

from django.test import TestCase
from django.contrib.auth import get_user_model

from bookings.models import Booking
from guests.models import Guest
from properties.models import Property, PropertyType, Bed
from structures.models import Structure

from dashboard.services.occupancy_service import OccupancyService
from dashboard.services.overview_service import OverviewService
from dashboard.services.charts_service import ChartsService
from dashboard.services.dashboard_metrics_service import (
    calculate_adr_for_window,
    calculate_all_adr_metrics,
)


User = get_user_model()


class ActiveStayLogicValidationTest(TestCase):
    """
    Validate that dashboard uses active-stay date-range logic,
    not is_checked_in=True filter.
    """

    def setUp(self):
        """Set up test data."""
        self.user = User.objects.create_user(
            username="testuser",
            email="test@example.com",
            password="testpass123"
        )
        
        self.structure = Structure.objects.create(
            name="Test Hotel",
            user=self.user,
            structure_type="hotel",
            zip_code="00100"
        )
        
        self.property_type = PropertyType.objects.create(
            structure=self.structure,
            name="Standard Room"
        )
        
        Bed.objects.create(
            property_type=self.property_type,
            name="Single Bed",
            quantity=1
        )
        
        self.room1 = Property.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            name="Room 101"
        )
        
        self.room2 = Property.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            name="Room 102"
        )
        
        self.today = date.today()

    def test_occupied_rooms_includes_not_checked_in(self):
        """
        CRITICAL TEST: Occupied rooms should include reservations
        where is_checked_in=False but dates are active.
        """
        # Create booking with is_checked_in=False but active dates
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room1,
            check_in_date=self.today - timedelta(days=1),
            check_out_date=self.today + timedelta(days=2),
            length_of_stay=3,
            adults_count=2,
            base_price=Decimal("150.00"),
            is_checked_in=False  # NOT checked in
        )
        
        # Calculate occupied rooms
        overview = OverviewService(structure_id=self.structure.id)
        occupied = overview.get_occupied_rooms()
        
        # Should count as occupied based on dates, not check-in status
        self.assertEqual(occupied, 1, 
            "Room should be occupied based on active dates, regardless of is_checked_in status")

    def test_occupied_rooms_excludes_past_checkout(self):
        """
        Rooms with check_out_date <= today should NOT be counted as occupied.
        """
        # Booking that already checked out yesterday
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room1,
            check_in_date=self.today - timedelta(days=3),
            check_out_date=self.today - timedelta(days=1),  # Checked out yesterday
            length_of_stay=2,
            adults_count=1,
            base_price=Decimal("100.00"),
            is_checked_in=True
        )
        
        overview = OverviewService(structure_id=self.structure.id)
        occupied = overview.get_occupied_rooms()
        
        # Should be 0 (booking already ended)
        self.assertEqual(occupied, 0, 
            "Room should NOT be occupied after check-out date")

    def test_occupied_rooms_excludes_future_checkin(self):
        """
        Rooms with check_in_date > today should NOT be counted as occupied today.
        """
        # Future booking starting tomorrow
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room1,
            check_in_date=self.today + timedelta(days=1),  # Tomorrow
            check_out_date=self.today + timedelta(days=3),
            length_of_stay=2,
            adults_count=1,
            base_price=Decimal("120.00"),
            is_checked_in=False
        )
        
        overview = OverviewService(structure_id=self.structure.id)
        occupied = overview.get_occupied_rooms()
        
        # Should be 0 (booking hasn't started yet)
        self.assertEqual(occupied, 0, 
            "Room should NOT be occupied before check-in date")

    def test_guests_in_structure_uses_dates(self):
        """
        Guests count should be based on active reservations,
        not is_checked_in status.
        """
        # Create booking with is_checked_in=False
        booking = Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room1,
            check_in_date=self.today - timedelta(days=1),
            check_out_date=self.today + timedelta(days=2),
            length_of_stay=3,
            adults_count=2,
            base_price=Decimal("150.00"),
            is_checked_in=False
        )
        
        # Add guests to booking
        Guest.objects.create(
            booking=booking,
            full_name="John Doe",
            is_main_guest=True,
            nationality="US"
        )
        
        Guest.objects.create(
            booking=booking,
            full_name="Jane Doe",
            is_main_guest=False,
            nationality="US"
        )
        
        # Count guests
        overview = OverviewService(structure_id=self.structure.id)
        guests = overview.get_guests_in_structure()
        
        # Should count 2 guests based on active dates
        self.assertEqual(guests, 2, 
            "Guests should be counted based on active reservation dates")

    def test_occupancy_percentage_uses_active_reservations(self):
        """
        Occupancy percentage should include all active reservations.
        """
        # Room 1: Active reservation, not checked in
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room1,
            check_in_date=self.today,
            check_out_date=self.today + timedelta(days=1),
            length_of_stay=1,
            adults_count=1,
            base_price=Decimal("100.00"),
            is_checked_in=False
        )
        
        # Room 2: Active reservation, checked in
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room2,
            check_in_date=self.today,
            check_out_date=self.today + timedelta(days=1),
            length_of_stay=1,
            adults_count=1,
            base_price=Decimal("100.00"),
            is_checked_in=True
        )
        
        # Calculate occupancy
        occupancy_service = OccupancyService(structure_id=self.structure.id)
        occupancy = occupancy_service.get_today_occupancy()
        
        # Both rooms should be counted = 100% occupancy
        self.assertEqual(occupancy, 100, 
            "Occupancy should be 100% when both rooms have active reservations")

    def test_adr_uses_active_reservations(self):
        """
        ADR calculation should include all active reservations,
        not just checked-in bookings.
        """
        # Active reservation, not checked in
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room1,
            check_in_date=self.today,
            check_out_date=self.today + timedelta(days=1),
            length_of_stay=1,
            adults_count=1,
            base_price=Decimal("120.00"),
            is_checked_in=False
        )
        
        # Calculate ADR
        adr = calculate_adr_for_window(
            structure_id=self.structure.id,
            window_start=self.today,
            window_end=self.today + timedelta(days=1),
            only_checked_in=False  # Use active-stay logic
        )
        
        # ADR should be 120.00 (from the active reservation)
        self.assertEqual(adr, 120.00, 
            "ADR should include active reservations regardless of check-in status")

    def test_mixed_scenarios_today(self):
        """
        Complex scenario with multiple bookings in different states.
        """
        # Booking 1: Active, checked in
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room1,
            check_in_date=self.today - timedelta(days=2),
            check_out_date=self.today + timedelta(days=1),
            length_of_stay=3,
            adults_count=1,
            base_price=Decimal("90.00"),
            is_checked_in=True
        )
        
        # Booking 2: Active, NOT checked in
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room2,
            check_in_date=self.today,
            check_out_date=self.today + timedelta(days=2),
            length_of_stay=2,
            adults_count=2,
            base_price=Decimal("110.00"),
            is_checked_in=False
        )
        
        # Calculate metrics
        overview = OverviewService(structure_id=self.structure.id)
        occupied = overview.get_occupied_rooms()
        
        # Both rooms should be occupied
        self.assertEqual(occupied, 2, 
            "Both rooms should be occupied based on active dates")

    def test_checkout_day_not_counted(self):
        """
        Check-out day should NOT be counted as occupied (exclusive boundary).
        """
        # Booking checking out today
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room1,
            check_in_date=self.today - timedelta(days=2),
            check_out_date=self.today,  # Checking out TODAY
            length_of_stay=2,
            adults_count=1,
            base_price=Decimal("100.00"),
            is_checked_in=True
        )
        
        overview = OverviewService(structure_id=self.structure.id)
        occupied = overview.get_occupied_rooms()
        
        # Room should NOT be occupied on check-out day
        self.assertEqual(occupied, 0, 
            "Room should NOT be occupied on check-out day (exclusive boundary)")

    def test_occupancy_window_includes_future_reservations(self):
        """
        7-day and 30-day occupancy should include future reservations.
        """
        # Future reservation starting in 3 days
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room1,
            check_in_date=self.today + timedelta(days=3),
            check_out_date=self.today + timedelta(days=5),
            length_of_stay=2,
            adults_count=1,
            base_price=Decimal("100.00"),
            is_checked_in=False
        )
        
        # Calculate 7-day occupancy
        occupancy_service = OccupancyService(structure_id=self.structure.id)
        occupancy_7 = occupancy_service.get_next_7_days_occupancy()
        
        # Should include the future reservation in the calculation
        # 2 occupied nights out of 14 possible (2 rooms * 7 days) = ~14.29%
        self.assertGreater(occupancy_7, 0, 
            "7-day occupancy should include future reservations")

    def test_charts_use_active_reservations(self):
        """
        Monthly occupancy charts should use active-stay logic.
        """
        # Create booking for current month
        month_start = self.today.replace(day=1)
        
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room1,
            check_in_date=month_start,
            check_out_date=month_start + timedelta(days=5),
            length_of_stay=5,
            adults_count=1,
            base_price=Decimal("250.00"),
            is_checked_in=False  # Not checked in
        )
        
        # Generate charts
        charts_service = ChartsService(structure_id=self.structure.id)
        monthly_data = charts_service.get_monthly_occupancy()
        
        # Current month should have some occupancy
        current_month_data = [
            m for m in monthly_data 
            if m['month'] == self.today.strftime('%b') and m['year'] == self.today.strftime('%Y')
        ]
        
        if current_month_data:
            self.assertGreater(current_month_data[0]['occupancy'], 0,
                "Monthly occupancy chart should include active reservations")


class BackwardCompatibilityTest(TestCase):
    """
    Test that only_checked_in parameter still works for legacy code.
    """

    def setUp(self):
        """Set up test data."""
        self.user = User.objects.create_user(
            username="testuser",
            email="test@example.com",
            password="testpass123"
        )
        
        self.structure = Structure.objects.create(
            name="Test Hotel",
            user=self.user,
            structure_type="hotel",
            zip_code="00100"
        )
        
        self.property_type = PropertyType.objects.create(
            structure=self.structure,
            name="Standard Room"
        )
        
        self.room1 = Property.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            name="Room 101"
        )
        
        self.today = date.today()

    def test_only_checked_in_parameter_still_works(self):
        """
        Legacy code using only_checked_in=True should still work.
        """
        # Booking 1: Checked in
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room1,
            check_in_date=self.today,
            check_out_date=self.today + timedelta(days=1),
            length_of_stay=1,
            adults_count=1,
            base_price=Decimal("100.00"),
            is_checked_in=True
        )
        
        # ADR with only_checked_in=True (legacy behavior)
        adr_legacy = calculate_adr_for_window(
            structure_id=self.structure.id,
            window_start=self.today,
            window_end=self.today + timedelta(days=1),
            only_checked_in=True
        )
        
        # Should work and return the booking's rate
        self.assertEqual(adr_legacy, 100.00,
            "Legacy only_checked_in=True parameter should still work")

    def test_default_is_active_stay_logic(self):
        """
        Default behavior (only_checked_in=False) should use active-stay logic.
        """
        # Booking: Not checked in, but active dates
        Booking.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            property=self.room1,
            check_in_date=self.today,
            check_out_date=self.today + timedelta(days=1),
            length_of_stay=1,
            adults_count=1,
            base_price=Decimal("150.00"),
            is_checked_in=False
        )
        
        # ADR with default (only_checked_in=False)
        adr_default = calculate_adr_for_window(
            structure_id=self.structure.id,
            window_start=self.today,
            window_end=self.today + timedelta(days=1)
        )
        
        # Should include the booking
        self.assertEqual(adr_default, 150.00,
            "Default behavior should use active-stay logic")
