#!/usr/bin/env python
"""
Dashboard Aggregation Validation Script for Aimantis PMS

Tests all dashboard calculation scenarios without requiring Django test runner.
Validates correct aggregation logic for:
- occupied_rooms
- available_rooms
- guests_in_structure
- checkins_today
- checkouts_today
- occupancy.today
- occupancy.next_7_days
- occupancy.next_30_days
- upcoming_events
- monthly_occupancy chart

Usage:
    python validate_dashboard_aggregation.py
"""

import sys
import os

# Add Django project to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings')

import django
django.setup()

from datetime import date, timedelta
from decimal import Decimal
from bookings.models import Booking
from guests.models import Guest
from properties.models import Property, PropertyType, PropertyTypeBed
from structures.models import Structure
from users.models import User
from dashboard.services.overview_service import OverviewService
from dashboard.services.occupancy_service import OccupancyService
from dashboard.services.upcoming_events_service import UpcomingEventsService
from dashboard.services.charts_service import ChartsService

# ============================================
# Test Utilities
# ============================================

class TestResult:
    def __init__(self):
        self.passed = 0
        self.failed = 0
        self.tests = []
    
    def add_pass(self, test_name):
        self.passed += 1
        self.tests.append(('PASS', test_name))
        print(f"✓ PASS: {test_name}")
    
    def add_fail(self, test_name, reason):
        self.failed += 1
        self.tests.append(('FAIL', test_name, reason))
        print(f"✗ FAIL: {test_name} - {reason}")
    
    def summary(self):
        total = self.passed + self.failed
        print("\n" + "="*70)
        print(f"VALIDATION SUMMARY")
        print("="*70)
        print(f"Total Tests: {total}")
        print(f"Passed: {self.passed}")
        print(f"Failed: {self.failed}")
        print("="*70)
        
        if self.failed > 0:
            print("\nFailed Tests:")
            for test in self.tests:
                if test[0] == 'FAIL':
                    print(f"  - {test[1]}: {test[2]}")
        
        return self.failed == 0


result = TestResult()

# ============================================
# Setup Test Data
# ============================================

def setup_test_data():
    """Create test structure, property, and bookings."""
    print("\n" + "="*70)
    print("SETTING UP TEST DATA")
    print("="*70)
    
    # Clean up existing test data
    Structure.objects.filter(name__startswith="Test Structure").delete()
    
    # Get or create test user
    user, _ = User.objects.get_or_create(
        email="test@example.com",
        defaults={'username': 'testuser'}
    )
    
    # Create test structure
    structure = Structure.objects.create(
        name="Test Structure Dashboard",
        address="Test Address",
        city="Test City",
        country="IT"
    )
    
    # Create property type
    property_type = PropertyType.objects.create(
        structure=structure,
        name="Test Apartment",
        description="Test Property Type"
    )
    
    # Create bed
    PropertyTypeBed.objects.create(
        property_type=property_type,
        bed_type="Single",
        quantity=2
    )
    
    # Create 5 properties (rooms)
    properties = []
    for i in range(1, 6):
        prop = Property.objects.create(
            structure=structure,
            property_type=property_type,
            name=f"Room {i}",
            floor=i
        )
        properties.append(prop)
    
    today = date.today()
    
    # Booking 1: Active checked-in today (May 14 -> May 19)
    booking1 = Booking.objects.create(
        structure=structure,
        property=properties[2],  # Room 3
        property_type=property_type,
        check_in_date=today,
        check_out_date=today + timedelta(days=5),
        length_of_stay=5,
        adults_count=1,
        children_count=0,
        total_price=Decimal("500.00"),
        is_checked_in=True,
        checked_in_at=django.utils.timezone.now()
    )
    
    # Create guest for booking 1
    Guest.objects.create(
        booking=booking1,
        first_name="Test",
        last_name="Guest",
        is_main_guest=True
    )
    
    # Booking 2: Future booking (May 20 -> May 25) - not checked in
    booking2 = Booking.objects.create(
        structure=structure,
        property=properties[0],  # Room 1
        property_type=property_type,
        check_in_date=today + timedelta(days=6),
        check_out_date=today + timedelta(days=11),
        length_of_stay=5,
        adults_count=2,
        children_count=0,
        total_price=Decimal("600.00"),
        is_checked_in=False
    )
    
    # Booking 3: Checkout today (May 10 -> May 14)
    booking3 = Booking.objects.create(
        structure=structure,
        property=properties[1],  # Room 2
        property_type=property_type,
        check_in_date=today - timedelta(days=4),
        check_out_date=today,
        length_of_stay=4,
        adults_count=1,
        children_count=0,
        total_price=Decimal("400.00"),
        is_checked_in=True,
        checked_in_at=django.utils.timezone.now() - timedelta(days=4)
    )
    
    print(f"✓ Created structure: {structure.name} (ID: {structure.id})")
    print(f"✓ Created 5 properties")
    print(f"✓ Booking 1: Active checked-in (ID: {booking1.id})")
    print(f"✓ Booking 2: Future booking (ID: {booking2.id})")
    print(f"✓ Booking 3: Checkout today (ID: {booking3.id})")
    
    return structure, booking1, booking2, booking3


# ============================================
# Test Scenarios
# ============================================

def test_1_active_checked_in_booking(structure, booking1):
    """Test 1: Active checked-in booking today"""
    print("\n" + "="*70)
    print("TEST 1: Active checked-in booking today")
    print("="*70)
    
    service = OverviewService(structure_id=structure.id)
    overview = service.get_overview()
    
    # occupied_rooms should be 1
    if overview['occupied_rooms'] == 1:
        result.add_pass("occupied_rooms = 1")
    else:
        result.add_fail("occupied_rooms = 1", f"Got {overview['occupied_rooms']}")
    
    # available_rooms should be 4 (5 total - 1 occupied)
    if overview['available_rooms'] == 4:
        result.add_pass("available_rooms = 4")
    else:
        result.add_fail("available_rooms = 4", f"Got {overview['available_rooms']}")
    
    # guests_in_structure should be 1
    if overview['guests_in_structure'] == 1:
        result.add_pass("guests_in_structure = 1")
    else:
        result.add_fail("guests_in_structure = 1", f"Got {overview['guests_in_structure']}")
    
    # checkins_today should be 1 (booking 1 checks in today)
    if overview['checkins_today'] == 1:
        result.add_pass("checkins_today = 1")
    else:
        result.add_fail("checkins_today = 1", f"Got {overview['checkins_today']}")
    
    # checkouts_today should be 1 (booking 3 checks out today)
    if overview['checkouts_today'] == 1:
        result.add_pass("checkouts_today = 1")
    else:
        result.add_fail("checkouts_today = 1", f"Got {overview['checkouts_today']}")


def test_2_occupancy_today(structure):
    """Test 2: Occupancy today calculation"""
    print("\n" + "="*70)
    print("TEST 2: Occupancy today calculation")
    print("="*70)
    
    service = OccupancyService(structure_id=structure.id)
    occupancy = service.get_occupancy()
    
    # occupancy.today should be 20% (1 occupied / 5 total * 100)
    if occupancy['today'] == 20:
        result.add_pass("occupancy.today = 20%")
    else:
        result.add_fail("occupancy.today = 20%", f"Got {occupancy['today']}%")


def test_3_future_booking_not_affecting_today(structure, booking2):
    """Test 3: Future booking should NOT affect today's occupied rooms"""
    print("\n" + "="*70)
    print("TEST 3: Future booking NOT affecting today")
    print("="*70)
    
    service = OverviewService(structure_id=structure.id)
    overview = service.get_overview()
    
    # Future booking should not be counted
    if overview['occupied_rooms'] == 1:
        result.add_pass("Future booking not counted in occupied_rooms")
    else:
        result.add_fail("Future booking not counted in occupied_rooms", 
                       f"Got {overview['occupied_rooms']} (expected 1)")


def test_4_checkout_day_not_occupied(structure, booking3):
    """Test 4: Checkout day should NOT count as occupied"""
    print("\n" + "="*70)
    print("TEST 4: Checkout day NOT occupied")
    print("="*70)
    
    today = date.today()
    
    # Booking 3 checks out today, so it should NOT be in occupied_rooms
    service = OverviewService(structure_id=structure.id)
    overview = service.get_overview()
    
    # Only booking 1 should be occupied (booking 3 is checkout day)
    if overview['occupied_rooms'] == 1:
        result.add_pass("Checkout day not counted as occupied")
    else:
        result.add_fail("Checkout day not counted as occupied",
                       f"Got {overview['occupied_rooms']} (expected 1)")


def test_5_upcoming_events_exclude_checked_in(structure, booking1):
    """Test 5: Upcoming events should exclude already checked-in bookings"""
    print("\n" + "="*70)
    print("TEST 5: Upcoming events exclude checked-in")
    print("="*70)
    
    service = UpcomingEventsService(structure_id=structure.id, limit=10)
    events = service.get_events()
    
    # Booking 1 already checked in today, should NOT appear as upcoming check-in
    booking1_events = [e for e in events if e['booking_id'] == booking1.id]
    
    if len(booking1_events) == 0 or all(e['event_type'] == 'check_out' for e in booking1_events):
        result.add_pass("Checked-in booking not shown as upcoming check-in")
    else:
        result.add_fail("Checked-in booking not shown as upcoming check-in",
                       f"Found {len(booking1_events)} check-in events for booking {booking1.id}")


def test_6_multiple_guests_same_booking(structure):
    """Test 6: Multiple guests in same booking"""
    print("\n" + "="*70)
    print("TEST 6: Multiple guests same booking")
    print("="*70)
    
    today = date.today()
    
    # Create booking with multiple guests
    booking = Booking.objects.create(
        structure=structure,
        property_id=4,  # Room 4
        check_in_date=today - timedelta(days=1),
        check_out_date=today + timedelta(days=3),
        length_of_stay=4,
        adults_count=2,
        children_count=1,
        total_price=Decimal("400.00"),
        is_checked_in=True,
        checked_in_at=django.utils.timezone.now() - timedelta(days=1)
    )
    
    # Create 3 guests
    for i in range(3):
        Guest.objects.create(
            booking=booking,
            first_name=f"Guest{i+1}",
            last_name=f"Test{i+1}",
            is_main_guest=(i == 0)
        )
    
    service = OverviewService(structure_id=structure.id)
    overview = service.get_overview()
    
    # Should count all 3 guests + 1 from booking1 = 4 total
    if overview['guests_in_structure'] == 4:
        result.add_pass("Multiple guests counted correctly (4)")
    else:
        result.add_fail("Multiple guests counted correctly",
                       f"Got {overview['guests_in_structure']} (expected 4)")
    
    # Should count 2 occupied rooms (booking1 + this booking)
    if overview['occupied_rooms'] == 2:
        result.add_pass("Multiple bookings occupy distinct rooms (2)")
    else:
        result.add_fail("Multiple bookings occupy distinct rooms",
                       f"Got {overview['occupied_rooms']} (expected 2)")


def test_7_occupancy_7_and_30_days(structure):
    """Test 7: 7-day and 30-day occupancy calculations"""
    print("\n" + "="*70)
    print("TEST 7: 7-day and 30-day occupancy")
    print("="*70)
    
    service = OccupancyService(structure_id=structure.id)
    occupancy = service.get_occupancy()
    
    # 30-day occupancy should be >= 7-day occupancy (or close)
    # This was broken before (showing 30-day < 7-day)
    if occupancy['next_30_days'] >= occupancy['next_7_days'] * 0.5:
        result.add_pass(f"30-day ({occupancy['next_30_days']}%) >= 7-day ({occupancy['next_7_days']}%) * 0.5")
    else:
        result.add_fail("30-day occupancy >= 7-day occupancy",
                       f"30-day: {occupancy['next_30_days']}%, 7-day: {occupancy['next_7_days']}%")


def test_8_no_bookings_scenario():
    """Test 8: Structure with no bookings"""
    print("\n" + "="*70)
    print("TEST 8: No bookings scenario")
    print("="*70)
    
    # Create empty structure
    structure = Structure.objects.create(
        name="Test Empty Structure",
        address="Test",
        city="Test",
        country="IT"
    )
    
    property_type = PropertyType.objects.create(
        structure=structure,
        name="Empty Property Type"
    )
    
    # Create 3 properties but no bookings
    for i in range(3):
        Property.objects.create(
            structure=structure,
            property_type=property_type,
            name=f"Empty Room {i+1}"
        )
    
    service = OverviewService(structure_id=structure.id)
    overview = service.get_overview()
    
    if overview['occupied_rooms'] == 0 and overview['available_rooms'] == 3:
        result.add_pass("Empty structure: 0 occupied, 3 available")
    else:
        result.add_fail("Empty structure metrics",
                       f"occupied={overview['occupied_rooms']}, available={overview['available_rooms']}")
    
    occupancy_service = OccupancyService(structure_id=structure.id)
    occupancy = occupancy_service.get_occupancy()
    
    if occupancy['today'] == 0:
        result.add_pass("Empty structure: 0% occupancy")
    else:
        result.add_fail("Empty structure occupancy", f"Got {occupancy['today']}%")


def test_9_monthly_occupancy_chart(structure):
    """Test 9: Monthly occupancy chart calculation"""
    print("\n" + "="*70)
    print("TEST 9: Monthly occupancy chart")
    print("="*70)
    
    service = ChartsService(structure_id=structure.id)
    chart_data = service.get_charts_data()
    
    monthly = chart_data['monthly_occupancy']
    
    # Should have 12 months of data
    if len(monthly) == 12:
        result.add_pass("Monthly chart has 12 months")
    else:
        result.add_fail("Monthly chart has 12 months", f"Got {len(monthly)} months")
    
    # Current month should have some occupancy (we created active bookings)
    current_month_data = [m for m in monthly if m['month'] == date.today().strftime('%b')]
    if current_month_data and current_month_data[0]['occupancy'] > 0:
        result.add_pass(f"Current month occupancy > 0 ({current_month_data[0]['occupancy']}%)")
    else:
        result.add_fail("Current month occupancy > 0",
                       f"Got {current_month_data[0]['occupancy'] if current_month_data else 'no data'}%")


def test_10_distinct_room_counting(structure):
    """Test 10: Multiple bookings same property should count as 1 room"""
    print("\n" + "="*70)
    print("TEST 10: Distinct room counting")
    print("="*70)
    
    # This is already tested in test_6, but let's verify explicitly
    today = date.today()
    
    service = OverviewService(structure_id=structure.id)
    overview = service.get_overview()
    
    # We have booking1 (room 3), booking with 3 guests (room 4)
    # Should be 2 distinct rooms, not 3
    if overview['occupied_rooms'] == 2:
        result.add_pass("Distinct room counting correct (2 rooms)")
    else:
        result.add_fail("Distinct room counting",
                       f"Got {overview['occupied_rooms']} (expected 2)")


# ============================================
# Main Execution
# ============================================

if __name__ == "__main__":
    print("\n" + "="*70)
    print("DASHBOARD AGGREGATION VALIDATION")
    print("="*70)
    print(f"Date: {date.today()}")
    print("="*70)
    
    try:
        # Setup test data
        structure, booking1, booking2, booking3 = setup_test_data()
        
        # Run all tests
        test_1_active_checked_in_booking(structure, booking1)
        test_2_occupancy_today(structure)
        test_3_future_booking_not_affecting_today(structure, booking2)
        test_4_checkout_day_not_occupied(structure, booking3)
        test_5_upcoming_events_exclude_checked_in(structure, booking1)
        test_6_multiple_guests_same_booking(structure)
        test_7_occupancy_7_and_30_days(structure)
        test_8_no_bookings_scenario()
        test_9_monthly_occupancy_chart(structure)
        test_10_distinct_room_counting(structure)
        
        # Print summary
        success = result.summary()
        
        sys.exit(0 if success else 1)
        
    except Exception as e:
        print(f"\n✗ ERROR: {e}")
        import traceback
        traceback.print_exc()
        sys.exit(1)
