#!/usr/bin/env python
"""
ADR (Average Daily Rate) Validation Script for Aimantis PMS

Tests all ADR calculation scenarios without requiring Django test runner.
Validates correct ADR logic for:
- today (checked-in bookings only)
- next_7_days (all bookings)
- next_30_days (all bookings)

Usage:
    python validate_adr_calculation.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.dashboard_metrics_service import (
    calculate_per_night_revenue,
    expand_booking_to_revenue_nights,
    calculate_adr_for_window,
    calculate_all_adr_metrics,
)

# ============================================
# 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"ADR 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 ADR").delete()
    
    # Get or create test user
    user, _ = User.objects.get_or_create(
        email="adr-test@example.com",
        defaults={'username': 'adrtestuser'}
    )
    
    # Create test structure
    structure = Structure.objects.create(
        name="Test ADR Structure",
        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()
    
    print(f"✓ Created structure: {structure.name} (ID: {structure.id})")
    print(f"✓ Created 5 properties")
    print(f"✓ Today's date: {today}")
    
    return structure, properties


def create_booking(structure, property_obj, check_in, check_out, total_price, is_checked_in=False):
    """Helper to create a booking with guest."""
    length_of_stay = (check_out - check_in).days
    
    booking = Booking.objects.create(
        structure=structure,
        property=property_obj,
        property_type=property_obj.property_type,
        check_in_date=check_in,
        check_out_date=check_out,
        length_of_stay=length_of_stay,
        adults_count=1,
        children_count=0,
        total_price=Decimal(str(total_price)),
        is_checked_in=is_checked_in,
        checked_in_at=django.utils.timezone.now() if is_checked_in else None
    )
    
    # Create guest
    Guest.objects.create(
        booking=booking,
        first_name="Test",
        last_name=f"Guest{booking.id}",
        is_main_guest=True
    )
    
    return booking


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

def test_1_per_night_revenue_calculation():
    """Test 1: Per-night revenue calculation"""
    print("\n" + "="*70)
    print("TEST 1: Per-night revenue calculation")
    print("="*70)
    
    structure, properties = setup_test_data()
    today = date.today()
    
    # Booking 182: €276 for 5 nights = €55.2/night
    booking = create_booking(
        structure=structure,
        property_obj=properties[0],
        check_in=today,
        check_out=today + timedelta(days=5),
        total_price=276.00,
        is_checked_in=True
    )
    
    per_night = calculate_per_night_revenue(booking)
    expected = Decimal("55.2")
    
    if per_night == expected:
        result.add_pass(f"Per-night revenue = €{per_night}")
    else:
        result.add_fail(f"Per-night revenue = €{expected}", f"Got €{per_night}")
    
    return structure, booking


def test_2_stay_night_expansion(structure, booking):
    """Test 2: Stay night expansion (check_out excluded)"""
    print("\n" + "="*70)
    print("TEST 2: Stay night expansion")
    print("="*70)
    
    today = date.today()
    window_start = today
    window_end = today + timedelta(days=7)
    
    revenue_nights = expand_booking_to_revenue_nights(
        booking=booking,
        window_start=window_start,
        window_end=window_end,
    )
    
    # Should have 5 nights: 14, 15, 16, 17, 18 (NOT 19)
    expected_nights = 5
    actual_nights = len(revenue_nights)
    
    if actual_nights == expected_nights:
        result.add_pass(f"Expanded to {actual_nights} nights")
    else:
        result.add_fail(f"Expanded to {expected_nights} nights", f"Got {actual_nights}")
    
    # Verify dates
    expected_dates = [
        today,
        today + timedelta(days=1),
        today + timedelta(days=2),
        today + timedelta(days=3),
        today + timedelta(days=4),
    ]
    
    actual_dates = [night.night_date for night in revenue_nights]
    
    if actual_dates == expected_dates:
        result.add_pass(f"Dates correct (checkout excluded)")
    else:
        result.add_fail(f"Dates correct", f"Got {actual_dates}")
    
    # Verify revenue per night
    expected_revenue = Decimal("55.2")
    all_correct = all(night.revenue == expected_revenue for night in revenue_nights)
    
    if all_correct:
        result.add_pass(f"Revenue per night = €{expected_revenue}")
    else:
        result.add_fail(f"Revenue per night = €{expected_revenue}", "Mismatch found")


def test_3_today_adr_active_booking(structure, booking):
    """Test 3: Today ADR with active checked-in booking"""
    print("\n" + "="*70)
    print("TEST 3: Today ADR with active booking")
    print("="*70)
    
    today = date.today()
    
    adr = calculate_adr_for_window(
        structure_id=structure.id,
        window_start=today,
        window_end=today + timedelta(days=1),
        only_checked_in=True,
    )
    
    # Expected: €55.2 (1 occupied night, €55.2 revenue)
    expected = 55.2
    
    if abs(adr - expected) < 0.01:
        result.add_pass(f"Today ADR = €{adr}")
    else:
        result.add_fail(f"Today ADR = €{expected}", f"Got €{adr}")


def test_4_next_7_days_adr(structure, booking):
    """Test 4: Next 7 days ADR"""
    print("\n" + "="*70)
    print("TEST 4: Next 7 days ADR")
    print("="*70)
    
    today = date.today()
    
    adr = calculate_adr_for_window(
        structure_id=structure.id,
        window_start=today,
        window_end=today + timedelta(days=7),
        only_checked_in=False,  # Include all bookings for future
    )
    
    # Booking contributes 5 nights, €276 total
    # ADR = 276 / 5 = 55.2
    expected = 55.2
    
    if abs(adr - expected) < 0.01:
        result.add_pass(f"7-day ADR = €{adr}")
    else:
        result.add_fail(f"7-day ADR = €{expected}", f"Got €{adr}")


def test_5_next_30_days_multiple_bookings(structure):
    """Test 5: Next 30 days ADR with multiple bookings"""
    print("\n" + "="*70)
    print("TEST 5: Next 30 days ADR (multiple bookings)")
    print("="*70)
    
    today = date.today()
    properties = Property.objects.filter(structure=structure)[:5]
    
    # Booking 182: May 14-19, €276, 5 nights (already created)
    # Create Booking 181: May 20-23, €197, 3 nights
    booking2 = create_booking(
        structure=structure,
        property_obj=properties[1],
        check_in=today + timedelta(days=6),  # May 20
        check_out=today + timedelta(days=9),  # May 23
        total_price=197.00,
        is_checked_in=False  # Future booking
    )
    
    # Calculate 30-day ADR
    metrics = calculate_all_adr_metrics(
        structure_id=structure.id,
        today=today,
    )
    
    # Total revenue: €276 + €197 = €473
    # Total nights: 5 + 3 = 8
    # ADR: 473 / 8 = 59.125 -> 59.13 (rounded)
    expected = 59.13
    actual = metrics['next_30_days']
    
    if abs(actual - expected) < 0.01:
        result.add_pass(f"30-day ADR = €{actual}")
    else:
        result.add_fail(f"30-day ADR = €{expected}", f"Got €{actual}")
    
    print(f"  Booking 1: €276 / 5 nights = €55.2/night")
    print(f"  Booking 2: €197 / 3 nights = €65.67/night")
    print(f"  Combined: €473 / 8 nights = €{expected}")


def test_6_no_bookings_scenario():
    """Test 6: Structure with no bookings"""
    print("\n" + "="*70)
    print("TEST 6: No bookings scenario")
    print("="*70)
    
    structure, properties = setup_test_data()
    today = date.today()
    
    metrics = calculate_all_adr_metrics(
        structure_id=structure.id,
        today=today,
    )
    
    all_zero = all(v == 0.0 for v in metrics.values())
    
    if all_zero:
        result.add_pass("All ADR metrics = 0 (no bookings)")
    else:
        result.add_fail("All ADR metrics = 0", f"Got {metrics}")


def test_7_future_only_bookings():
    """Test 7: Future-only bookings (not checked in)"""
    print("\n" + "="*70)
    print("TEST 7: Future-only bookings")
    print("="*70)
    
    structure, properties = setup_test_data()
    today = date.today()
    
    # Create future booking (not checked in)
    create_booking(
        structure=structure,
        property_obj=properties[0],
        check_in=today + timedelta(days=10),
        check_out=today + timedelta(days=15),
        total_price=500.00,
        is_checked_in=False
    )
    
    metrics = calculate_all_adr_metrics(
        structure_id=structure.id,
        today=today,
    )
    
    # Today should be 0 (no checked-in bookings)
    if metrics['today'] == 0.0:
        result.add_pass("Today ADR = 0 (no checked-in)")
    else:
        result.add_fail("Today ADR = 0", f"Got €{metrics['today']}")
    
    # 30-day should include future booking
    if metrics['next_30_days'] > 0:
        result.add_pass(f"30-day ADR = €{metrics['next_30_days']} > 0")
    else:
        result.add_fail("30-day ADR > 0", f"Got €{metrics['next_30_days']}")


def test_8_multiple_overlapping_bookings():
    """Test 8: Multiple overlapping bookings"""
    print("\n" + "="*70)
    print("TEST 8: Multiple overlapping bookings")
    print("="*70)
    
    structure, properties = setup_test_data()
    today = date.today()
    
    # Create 2 overlapping bookings
    create_booking(
        structure=structure,
        property_obj=properties[0],
        check_in=today,
        check_out=today + timedelta(days=5),
        total_price=300.00,
        is_checked_in=True
    )
    
    create_booking(
        structure=structure,
        property_obj=properties[1],
        check_in=today,
        check_out=today + timedelta(days=3),
        total_price=150.00,
        is_checked_in=True
    )
    
    adr = calculate_adr_for_window(
        structure_id=structure.id,
        window_start=today,
        window_end=today + timedelta(days=1),
        only_checked_in=True,
    )
    
    # Today: 2 bookings, 2 occupied nights
    # Revenue: €300/5 + €150/3 = €60 + €50 = €110
    # ADR: €110 / 2 = €55
    expected = 55.0
    
    if abs(adr - expected) < 0.01:
        result.add_pass(f"Overlapping bookings ADR = €{adr}")
    else:
        result.add_fail(f"Overlapping bookings ADR = €{expected}", f"Got €{adr}")


def test_9_division_by_zero_safety():
    """Test 9: Division by zero handling"""
    print("\n" + "="*70)
    print("TEST 9: Division by zero safety")
    print("="*70)
    
    structure, properties = setup_test_data()
    today = date.today()
    
    # Create booking with zero length of stay (edge case)
    booking = Booking.objects.create(
        structure=structure,
        property=properties[0],
        property_type=properties[0].property_type,
        check_in_date=today,
        check_out_date=today,  # Same day!
        length_of_stay=0,
        adults_count=1,
        total_price=Decimal("100.00"),
        is_checked_in=True
    )
    
    per_night = calculate_per_night_revenue(booking)
    
    if per_night == Decimal('0'):
        result.add_pass("Zero length of stay handled safely")
    else:
        result.add_fail("Zero length of stay handled", f"Got €{per_night}")


def test_10_all_metrics_consistency(structure, booking):
    """Test 10: All metrics consistency"""
    print("\n" + "="*70)
    print("TEST 10: All metrics consistency")
    print("="*70)
    
    today = date.today()
    
    metrics = calculate_all_adr_metrics(
        structure_id=structure.id,
        today=today,
    )
    
    print(f"  Today: €{metrics['today']}")
    print(f"  7-day: €{metrics['next_7_days']}")
    print(f"  30-day: €{metrics['next_30_days']}")
    
    # All should be > 0 (we have active bookings)
    all_positive = all(v > 0 for v in metrics.values())
    
    if all_positive:
        result.add_pass("All ADR metrics > 0")
    else:
        result.add_fail("All ADR metrics > 0", f"Got {metrics}")
    
    # Values should be reasonable (between €10 and €500)
    all_reasonable = all(10 < v < 500 for v in metrics.values())
    
    if all_reasonable:
        result.add_pass("All ADR values reasonable (€10-€500)")
    else:
        result.add_fail("ADR values reasonable", f"Got {metrics}")


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

if __name__ == "__main__":
    print("\n" + "="*70)
    print("ADR (AVERAGE DAILY RATE) VALIDATION")
    print("="*70)
    print(f"Date: {date.today()}")
    print("="*70)
    
    try:
        # Run all tests
        structure, booking = test_1_per_night_revenue_calculation()
        test_2_stay_night_expansion(structure, booking)
        test_3_today_adr_active_booking(structure, booking)
        test_4_next_7_days_adr(structure, booking)
        test_5_next_30_days_multiple_bookings(structure)
        test_6_no_bookings_scenario()
        test_7_future_only_bookings()
        test_8_multiple_overlapping_bookings()
        test_9_division_by_zero_safety()
        test_10_all_metrics_consistency(structure, booking)
        
        # 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)
