"""
Comprehensive tests for Aimantis Dashboard API.

Tests cover:
- Business rule validation
- Occupancy calculations
- Multi-tenant isolation
- Edge cases
- Performance optimization
- Frontend response stability
"""

from datetime import date, timedelta
from decimal import Decimal
from django.test import TestCase, override_settings
from django.contrib.auth.models import User
from django.utils import timezone
from rest_framework.test import APIClient
from rest_framework import status

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


@override_settings(CACHES={
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
})
class DashboardBusinessRulesTestCase(TestCase):
    """
    Test dashboard against Aimantis business rules.
    
    CRITICAL RULES:
    1. Reservation exists = active reservation
    2. Reservation deleted = cancelled
    3. is_checked_in only for operational state, NOT occupancy
    4. Occupancy based on dates: check_in <= today < check_out
    """
    
    def setUp(self):
        """Set up test data with realistic scenarios."""
        self.today = timezone.localdate()
        
        # Create users
        self.user1 = User.objects.create_user(
            username='owner1',
            password='testpass123',
            email='owner1@test.com'
        )
        self.user2 = User.objects.create_user(
            username='owner2',
            password='testpass123',
            email='owner2@test.com'
        )
        
        # Create structures (hotels)
        self.structure1 = Structure.objects.create(
            user=self.user1,
            name='Hotel Roma',
            structure_type='hotel',
            zip_code='00100'
        )
        self.structure2 = Structure.objects.create(
            user=self.user2,
            name='Hotel Milano',
            structure_type='hotel',
            zip_code='20100'
        )
        
        # Create property types
        self.property_type1 = PropertyType.objects.create(
            structure=self.structure1,
            name='Deluxe Double Room',
            max_guests=2,
            num_beds=1,
            num_bathrooms=1,
            status=PropertyType.Status.MAPPED
        )
        self.property_type2 = PropertyType.objects.create(
            structure=self.structure2,
            name='Standard Single Room',
            max_guests=1,
            num_beds=1,
            num_bathrooms=1,
            status=PropertyType.Status.MAPPED
        )
        
        # Create properties (actual rooms)
        self.rooms_s1 = []
        for i in range(1, 11):  # 10 rooms in structure 1
            room = Property.objects.create(
                structure=self.structure1,
                property_type=self.property_type1,
                name=f'Room {i}',
                status=Property.PropertyStatus.MAPPED,
                availability=Property.Availability.AVAILABLE
            )
            self.rooms_s1.append(room)
        
        self.rooms_s2 = []
        for i in range(1, 6):  # 5 rooms in structure 2
            room = Property.objects.create(
                structure=self.structure2,
                property_type=self.property_type2,
                name=f'Room {i}',
                status=Property.PropertyStatus.MAPPED,
                availability=Property.Availability.AVAILABLE
            )
            self.rooms_s2.append(room)
        
        # Create realistic booking scenarios for structure 1
        self._create_booking_scenarios()
        
        # Authenticate client
        self.client = APIClient()
        self.client.force_authenticate(user=self.user1)
    
    def _create_booking_scenarios(self):
        """Create realistic booking scenarios for testing."""
        
        # Scenario 1: Current active stay (checked in 2 days ago, checks out in 3 days)
        self.booking_active = Booking.objects.create(
            structure=self.structure1,
            property_type=self.property_type1,
            property=self.rooms_s1[0],
            check_in_date=self.today - timedelta(days=2),
            check_out_date=self.today + timedelta(days=3),
            length_of_stay=5,
            adults_count=2,
            total_price=Decimal('500.00'),
            is_checked_in=True
        )
        Guest.objects.create(
            booking=self.booking_active,
            full_name='John Active',
            is_main_guest=True
        )
        Guest.objects.create(
            booking=self.booking_active,
            full_name='Jane Active',
            is_main_guest=False
        )
        
        # Scenario 2: Check-in TODAY (future check-in, not checked in yet)
        self.booking_checkin_today = Booking.objects.create(
            structure=self.structure1,
            property_type=self.property_type1,
            property=self.rooms_s1[1],
            check_in_date=self.today,
            check_out_date=self.today + timedelta(days=4),
            length_of_stay=4,
            adults_count=1,
            total_price=Decimal('400.00'),
            is_checked_in=False  # NOT checked in yet
        )
        Guest.objects.create(
            booking=self.booking_checkin_today,
            full_name='Tom Arrival',
            is_main_guest=True
        )
        
        # Scenario 3: Check-out TODAY (checked in yesterday, checks out today)
        self.booking_checkout_today = Booking.objects.create(
            structure=self.structure1,
            property_type=self.property_type1,
            property=self.rooms_s1[2],
            check_in_date=self.today - timedelta(days=2),
            check_out_date=self.today,
            length_of_stay=2,
            adults_count=1,
            total_price=Decimal('200.00'),
            is_checked_in=True
        )
        Guest.objects.create(
            booking=self.booking_checkout_today,
            full_name='Sally Departure',
            is_main_guest=True
        )
        
        # Scenario 4: Future booking (starts in 5 days)
        self.booking_future = Booking.objects.create(
            structure=self.structure1,
            property_type=self.property_type1,
            property=self.rooms_s1[3],
            check_in_date=self.today + timedelta(days=5),
            check_out_date=self.today + timedelta(days=10),
            length_of_stay=5,
            adults_count=2,
            total_price=Decimal('600.00'),
            is_checked_in=False
        )
        Guest.objects.create(
            booking=self.booking_future,
            full_name='Future Guest',
            is_main_guest=True
        )
        
        # Scenario 5: Long stay across months (started last month, ends next month)
        self.booking_long_stay = Booking.objects.create(
            structure=self.structure1,
            property_type=self.property_type1,
            property=self.rooms_s1[4],
            check_in_date=self.today - timedelta(days=15),
            check_out_date=self.today + timedelta(days=30),
            length_of_stay=45,
            adults_count=1,
            total_price=Decimal('3000.00'),
            is_checked_in=True
        )
        Guest.objects.create(
            booking=self.booking_long_stay,
            full_name='Long Stay Guest',
            is_main_guest=True
        )
        
        # Scenario 6: NOT checked in but room is occupied (business rule test)
        self.booking_not_checked_in = Booking.objects.create(
            structure=self.structure1,
            property_type=self.property_type1,
            property=self.rooms_s1[5],
            check_in_date=self.today - timedelta(days=1),
            check_out_date=self.today + timedelta(days=2),
            length_of_stay=3,
            adults_count=1,
            total_price=Decimal('300.00'),
            is_checked_in=False  # Forgot to check in!
        )
        Guest.objects.create(
            booking=self.booking_not_checked_in,
            full_name='Forgot Checkin',
            is_main_guest=True
        )
        
        # Create bookings for structure 2 (for isolation testing)
        self.booking_s2 = Booking.objects.create(
            structure=self.structure2,
            property_type=self.property_type2,
            property=self.rooms_s2[0],
            check_in_date=self.today - timedelta(days=1),
            check_out_date=self.today + timedelta(days=2),
            length_of_stay=3,
            adults_count=1,
            total_price=Decimal('150.00'),
            is_checked_in=True
        )
        Guest.objects.create(
            booking=self.booking_s2,
            full_name='Structure 2 Guest',
            is_main_guest=True
        )
    
    def test_occupied_rooms_uses_dates_not_checked_in(self):
        """
        BUSINESS RULE: Occupied rooms must use dates, NOT is_checked_in.
        
        Expected: Rooms 0, 1, 2, 4, 5 should be occupied (6 rooms)
        - Room 0: Active stay
        - Room 1: Check-in today
        - Room 2: Check-out today (still occupied until checkout)
        - Room 4: Long stay
        - Room 5: Not checked in but date-based occupied
        """
        from dashboard.services.overview_service import OverviewService
        
        service = OverviewService(structure_id=self.structure1.id)
        occupied = service.get_occupied_rooms()
        
        # Should include rooms based on dates, regardless of is_checked_in
        self.assertEqual(occupied, 6, 
            "Occupied rooms must use date-based filtering, not is_checked_in")
    
    def test_guests_in_structure_uses_dates(self):
        """
        BUSINESS RULE: Guests count must use date-based filtering.
        
        Expected: 8 guests total
        - booking_active: 2 guests
        - booking_checkin_today: 1 guest
        - booking_checkout_today: 1 guest (checkout today, still counted)
        - booking_long_stay: 1 guest
        - booking_not_checked_in: 1 guest
        """
        from dashboard.services.overview_service import OverviewService
        
        service = OverviewService(structure_id=self.structure1.id)
        guests = service.get_guests_in_structure()
        
        # Should count all guests from date-based active stays
        self.assertEqual(guests, 6,
            "Guests must be counted from date-based active stays")
    
    def test_today_checkins_count(self):
        """
        BUSINESS RULE: Today check-ins = bookings where check_in_date == today
        """
        from dashboard.services.overview_service import OverviewService
        
        service = OverviewService(structure_id=self.structure1.id)
        checkins = service.get_checkins_today()
        
        self.assertEqual(checkins, 1,
            "Should count only bookings with check_in_date == today")
    
    def test_today_checkouts_count(self):
        """
        BUSINESS RULE: Today check-outs = bookings where check_out_date == today
        """
        from dashboard.services.overview_service import OverviewService
        
        service = OverviewService(structure_id=self.structure1.id)
        checkouts = service.get_checkouts_today()
        
        self.assertEqual(checkouts, 1,
            "Should count only bookings with check_out_date == today")
    
    def test_checkout_date_not_counted_as_occupied_tomorrow(self):
        """
        CRITICAL EDGE CASE: Checkout date must NOT count as occupied.
        
        Formula: check_in <= today < check_out
        
        A guest checking out today should NOT be counted as occupied tomorrow.
        """
        from dashboard.services.overview_service import OverviewService
        
        # Test for tomorrow
        tomorrow = self.today + timedelta(days=1)
        
        # booking_checkout_today has check_out_date == today
        # So tomorrow it should NOT be occupied
        self.assertLess(tomorrow, self.booking_checkout_today.check_out_date + timedelta(days=1))
        
        # Verify the logic: today < check_out means occupied
        # Tomorrow: tomorrow < check_out_date (today)? NO
        is_occupied_tomorrow = (
            self.booking_checkout_today.check_in_date <= tomorrow <
            self.booking_checkout_today.check_out_date
        )
        self.assertFalse(is_occupied_tomorrow,
            "Guest checking out today should not occupy room tomorrow")


@override_settings(CACHES={
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
})
class DashboardMultiTenantIsolationTestCase(TestCase):
    """
    Test that dashboard metrics never leak between structures.
    """
    
    def setUp(self):
        self.today = timezone.localdate()
        
        self.user1 = User.objects.create_user(
            username='owner1',
            password='testpass123'
        )
        self.user2 = User.objects.create_user(
            username='owner2',
            password='testpass123'
        )
        
        # Create two structures
        self.structure1 = Structure.objects.create(
            user=self.user1,
            name='Hotel A',
            structure_type='hotel',
            zip_code='00100'
        )
        self.structure2 = Structure.objects.create(
            user=self.user2,
            name='Hotel B',
            structure_type='hotel',
            zip_code='00100'
        )
        
        # Create property types and properties
        pt1 = PropertyType.objects.create(
            structure=self.structure1,
            name='Room Type A',
            max_guests=2,
            status=PropertyType.Status.MAPPED
        )
        pt2 = PropertyType.objects.create(
            structure=self.structure2,
            name='Room Type B',
            max_guests=2,
            status=PropertyType.Status.MAPPED
        )
        
        self.room1 = Property.objects.create(
            structure=self.structure1,
            property_type=pt1,
            name='Room A1',
            status=Property.PropertyStatus.MAPPED
        )
        self.room2 = Property.objects.create(
            structure=self.structure2,
            property_type=pt2,
            name='Room B1',
            status=Property.PropertyStatus.MAPPED
        )
        
        # Create bookings for BOTH structures
        Booking.objects.create(
            structure=self.structure1,
            property_type=pt1,
            property=self.room1,
            check_in_date=self.today,
            check_out_date=self.today + timedelta(days=3),
            length_of_stay=3,
            adults_count=2,
            total_price=Decimal('300.00')
        )
        
        Booking.objects.create(
            structure=self.structure2,
            property_type=pt2,
            property=self.room2,
            check_in_date=self.today,
            check_out_date=self.today + timedelta(days=3),
            length_of_stay=3,
            adults_count=2,
            total_price=Decimal('400.00')
        )
        
        self.client = APIClient()
        self.client.force_authenticate(user=self.user1)
    
    def test_structure_filter_isolation(self):
        """
        BUSINESS RULE: ?structure=1 must never include metrics from structure=2
        """
        from dashboard.services.overview_service import OverviewService
        
        # Query structure 1
        service_s1 = OverviewService(structure_id=self.structure1.id)
        overview_s1 = service_s1.get_overview()
        
        # Query structure 2
        service_s2 = OverviewService(structure_id=self.structure2.id)
        overview_s2 = service_s2.get_overview()
        
        # Verify isolation
        self.assertEqual(overview_s1['occupied_rooms'], 1,
            "Structure 1 should only see its own occupied rooms")
        self.assertEqual(overview_s2['occupied_rooms'], 1,
            "Structure 2 should only see its own occupied rooms")
        
        # They should be independent
        self.assertNotEqual(overview_s1['total_rooms'], overview_s2['total_rooms'])
    
    def test_api_endpoint_structure_isolation(self):
        """
        Test API endpoint respects structure filtering.
        """
        # Request structure 1
        response_s1 = self.client.get(
            '/api/dashboard/widgets',
            {'structure': self.structure1.id}
        )
        
        # Request structure 2
        response_s2 = self.client.get(
            '/api/dashboard/widgets',
            {'structure': self.structure2.id}
        )
        
        self.assertEqual(response_s1.status_code, status.HTTP_200_OK)
        self.assertEqual(response_s2.status_code, status.HTTP_200_OK)
        
        data_s1 = response_s1.json()
        data_s2 = response_s2.json()
        
        # Verify structure IDs are different
        self.assertEqual(data_s1['structure']['id'], self.structure1.id)
        self.assertEqual(data_s2['structure']['id'], self.structure2.id)
        
        # Verify metrics are isolated
        self.assertNotEqual(
            data_s1['overview']['occupied_rooms'],
            data_s2['overview']['occupied_rooms']
        )


@override_settings(CACHES={
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
})
class DashboardResponseStabilityTestCase(TestCase):
    """
    Test that API response is frontend-safe and stable.
    """
    
    def setUp(self):
        self.today = timezone.localdate()
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
        
        self.structure = Structure.objects.create(
            user=self.user,
            name='Test Hotel',
            structure_type='hotel',
            zip_code='00100'
        )
        
        self.property_type = PropertyType.objects.create(
            structure=self.structure,
            name='Test Room',
            max_guests=2,
            status=PropertyType.Status.MAPPED
        )
        
        self.room = Property.objects.create(
            structure=self.structure,
            property_type=self.property_type,
            name='Room 1',
            status=Property.PropertyStatus.MAPPED
        )
        
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)
    
    def test_response_schema_stability(self):
        """
        Ensure API response has stable, predictable schema.
        """
        response = self.client.get(
            '/api/dashboard/widgets',
            {'structure': self.structure.id}
        )
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        data = response.json()
        
        # Verify top-level keys
        required_keys = [
            'date',
            'structure',
            'overview',
            'occupancy',
            'average_rates',
            'upcoming_events',
            'charts'
        ]
        
        for key in required_keys:
            self.assertIn(key, data, f"Missing required key: {key}")
    
    def test_numeric_consistency(self):
        """
        Ensure numeric fields are consistent and non-negative.
        """
        response = self.client.get(
            '/api/dashboard/widgets',
            {'structure': self.structure.id}
        )
        
        data = response.json()
        overview = data['overview']
        occupancy = data['occupancy']
        rates = data['average_rates']
        
        # Overview metrics should be non-negative integers
        for key in ['checkins_today', 'checkouts_today', 'guests_in_structure',
                    'available_rooms', 'occupied_rooms', 'total_rooms', 'total_beds']:
            self.assertIsInstance(overview[key], int,
                f"{key} should be integer")
            self.assertGreaterEqual(overview[key], 0,
                f"{key} should be non-negative")
        
        # Occupancy should be 0-100
        for key in ['today', 'next_7_days', 'next_30_days']:
            self.assertIsInstance(occupancy[key], int,
                f"occupancy.{key} should be integer")
            self.assertGreaterEqual(occupancy[key], 0)
            self.assertLessEqual(occupancy[key], 100)
        
        # Rates should be non-negative floats
        for key in ['today', 'next_7_days', 'next_30_days']:
            self.assertIsInstance(rates[key], (int, float),
                f"average_rates.{key} should be numeric")
            self.assertGreaterEqual(rates[key], 0)
    
    def test_upcoming_events_format(self):
        """
        Ensure upcoming events have stable format.
        """
        response = self.client.get(
            '/api/dashboard/widgets',
            {'structure': self.structure.id}
        )
        
        data = response.json()
        events = data['upcoming_events']
        
        self.assertIsInstance(events, list)
        
        if events:
            event = events[0]
            required_event_keys = [
                'booking_id',
                'event_type',
                'guest_name',
                'channel',
                'nights',
                'amount',
                'date'
            ]
            
            for key in required_event_keys:
                self.assertIn(key, event,
                    f"Missing event key: {key}")
    
    def test_empty_structure_response(self):
        """
        Test response when structure has no bookings.
        """
        # Create empty structure
        empty_structure = Structure.objects.create(
            user=self.user,
            name='Empty Hotel',
            structure_type='hotel',
            zip_code='00100'
        )
        
        response = self.client.get(
            '/api/dashboard/widgets',
            {'structure': empty_structure.id}
        )
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        data = response.json()
        
        # Should have zero values, not errors
        self.assertEqual(data['overview']['checkins_today'], 0)
        self.assertEqual(data['overview']['occupied_rooms'], 0)
        self.assertEqual(data['occupancy']['today'], 0)
        self.assertIsInstance(data['upcoming_events'], list)
        self.assertEqual(len(data['upcoming_events']), 0)


@override_settings(CACHES={
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
})
class DashboardPerformanceTestCase(TestCase):
    """
    Test dashboard performance and query optimization.
    """
    
    def setUp(self):
        self.today = timezone.localdate()
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
        
        self.structure = Structure.objects.create(
            user=self.user,
            name='Performance Test Hotel',
            structure_type='hotel',
            zip_code='00100'
        )
        
        self.property_type = PropertyType.objects.create(
            structure=self.structure,
            name='Test Room',
            max_guests=2,
            status=PropertyType.Status.MAPPED
        )
        
        # Create 20 rooms
        self.rooms = []
        for i in range(20):
            room = Property.objects.create(
                structure=self.structure,
                property_type=self.property_type,
                name=f'Room {i}',
                status=Property.PropertyStatus.MAPPED
            )
            self.rooms.append(room)
        
        # Create 50 bookings
        for i in range(50):
            room = self.rooms[i % 20]
            Booking.objects.create(
                structure=self.structure,
                property_type=self.property_type,
                property=room,
                check_in_date=self.today - timedelta(days=i % 10),
                check_out_date=self.today + timedelta(days=(i % 10) + 1),
                length_of_stay=(i % 10) + 1,
                adults_count=1,
                total_price=Decimal('100.00') * (i % 5 + 1)
            )
        
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)
    
    def test_no_n_plus_one_queries(self):
        """
        Verify dashboard doesn't execute N+1 queries.
        """
        from django.db import connection
        from django.test.utils import CaptureQueriesContext
        
        # Clear connection
        connection.queries_log.clear()
        
        with CaptureQueriesContext(connection) as context:
            response = self.client.get(
                '/api/dashboard/widgets',
                {'structure': self.structure.id}
            )
        
        query_count = len(context.captured_queries)
        
        # Should complete in reasonable number of queries (< 50)
        self.assertLess(query_count, 50,
            f"Dashboard executed {query_count} queries, possible N+1 issue")
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
