"""
Property-Based Tests for Date Filtering

Feature: comprehensive-reports-and-fixes
Tests Properties 2, 3, and 21 for date filtering accuracy and consistency.

Requirements: 6.1, 6.2, 6.3, 6.4, 6.7, 6.10
"""

import pytest
from hypothesis import given, strategies as st, settings
from hypothesis.extra.django import TestCase
from datetime import date, datetime, timedelta
from decimal import Decimal
from django.utils import timezone
from loans.models import Loan, LoanApplication
from users.models import CustomUser, Branch
from reports.comprehensive_reports import ComprehensiveReportsService


class TestDateFilteringProperties(TestCase):
    """Property-based tests for date filtering in reports"""
    
    def setUp(self):
        """Set up test data"""
        # Create test branch
        self.branch = Branch.objects.create(
            name='Test Branch',
            code='TB001',
            is_main_branch=True
        )
        
        # Create test user (borrower)
        self.borrower = CustomUser.objects.create_user(
            username='testborrower',
            email='borrower@test.com',
            password='testpass123',
            first_name='Test',
            last_name='Borrower',
            phone_number='0712345678',
            branch=self.branch
        )
        
        # Create loan product
        from loans.models import LoanProduct
        self.loan_product = LoanProduct.objects.create(
            name='Test Product',
            product_type='boost',
            description='Test product for date filtering tests',
            min_amount=Decimal('1000.00'),
            max_amount=Decimal('100000.00'),
            interest_rate=Decimal('10.00'),
            processing_fee=Decimal('5.00'),
            min_duration=7,
            max_duration=90,
            is_active=True
        )
    
    @given(
        days_offset=st.integers(min_value=-30, max_value=30),
        num_loans=st.integers(min_value=1, max_value=10)
    )
    @settings(max_examples=50, deadline=None)
    def test_property_2_date_filtering_accuracy(self, days_offset, num_loans):
        """
        Property 2: Date Filtering Accuracy
        
        For any date range (start_date, end_date) and any set of loans,
        when filtering by date range, all returned loans should have
        disbursement_date >= start_date AND disbursement_date <= end_date,
        and all loans meeting this criteria should be included.
        
        Validates: Requirements 1.4, 1.5, 1.6, 6.1, 6.2, 6.4, 6.7
        """
        # Clean up any existing loans
        Loan.objects.all().delete()
        LoanApplication.objects.all().delete()
        
        today = timezone.now().date()
        target_date = today + timedelta(days=days_offset)
        
        # Create loans with due dates around the target date
        created_loans = []
        for i in range(num_loans):
            # Create loan application
            application = LoanApplication.objects.create(
                borrower=self.borrower,
                loan_product=self.loan_product,
                requested_amount=Decimal('10000.00'),
                requested_duration=30,  # 30 days
                status='approved'
            )
            
            # Create loan with due date at target_date + i days
            loan_due_date = target_date + timedelta(days=i)
            loan = Loan.objects.create(
                loan_number=f'LOAN-{i:04d}',
                application=application,
                borrower=self.borrower,
                principal_amount=Decimal('10000.00'),
                interest_amount=Decimal('1000.00'),
                processing_fee=Decimal('500.00'),
                total_amount=Decimal('11500.00'),
                disbursement_date=today,
                due_date=datetime.combine(loan_due_date, datetime.min.time()),
                status='active',
                is_deleted=False,
                is_rolled_over=False
            )
            created_loans.append(loan)
        
        # Define date range for filtering
        start_date = target_date
        end_date = target_date + timedelta(days=num_loans - 1)
        
        # Get loans using the comprehensive reports service
        reports_service = ComprehensiveReportsService()
        report_data = reports_service.get_loans_due_report(
            start_date=start_date,
            end_date=end_date,
            today_only=False
        )
        
        filtered_loans = report_data['loans']
        
        # Property: All returned loans should have due_date within range
        for loan_data in filtered_loans:
            loan_due_date = loan_data['due_date']
            if isinstance(loan_due_date, datetime):
                loan_due_date = loan_due_date.date()
            
            assert start_date <= loan_due_date <= end_date, \
                f"Loan {loan_data['loan_number']} due date {loan_due_date} is outside range [{start_date}, {end_date}]"
        
        # Property: All loans within range should be included
        expected_loan_ids = set(
            loan.id for loan in created_loans
            if start_date <= loan.due_date.date() <= end_date
        )
        actual_loan_ids = set(loan_data['id'] for loan_data in filtered_loans)
        
        assert expected_loan_ids == actual_loan_ids, \
            f"Expected loans {expected_loan_ids} but got {actual_loan_ids}"
    
    @given(
        days_offset=st.integers(min_value=-10, max_value=10)
    )
    @settings(max_examples=50, deadline=None)
    def test_property_3_date_comparison_consistency(self, days_offset):
        """
        Property 3: Date Comparison Consistency
        
        For any loan with a due_date datetime field, when filtering by a specific date,
        the system should convert the datetime to date before comparison to ensure
        loans are matched regardless of time component.
        
        Validates: Requirements 6.3, 6.4
        """
        # Clean up any existing loans
        Loan.objects.all().delete()
        LoanApplication.objects.all().delete()
        
        today = timezone.now().date()
        target_date = today + timedelta(days=days_offset)
        
        # Create loan application
        application = LoanApplication.objects.create(
            borrower=self.borrower,
            loan_product=self.loan_product,
            requested_amount=Decimal('10000.00'),
            requested_duration=30,  # 30 days
            status='approved'
        )
        
        # Create loan with due_date at different times of the target date
        # This tests that date comparison ignores time component
        loan_due_datetime = datetime.combine(target_date, datetime.min.time().replace(hour=14, minute=30))
        loan = Loan.objects.create(
            loan_number='LOAN-TIME-TEST',
            application=application,
            borrower=self.borrower,
            principal_amount=Decimal('10000.00'),
            interest_amount=Decimal('1000.00'),
            processing_fee=Decimal('500.00'),
            total_amount=Decimal('11500.00'),
            disbursement_date=today,
            due_date=timezone.make_aware(loan_due_datetime),
            status='active',
            is_deleted=False,
            is_rolled_over=False
        )
        
        # Filter for loans due on target_date using today_only if target_date is today
        reports_service = ComprehensiveReportsService()
        
        if target_date == today:
            report_data = reports_service.get_loans_due_report(
                today_only=True
            )
        else:
            report_data = reports_service.get_loans_due_report(
                start_date=target_date,
                end_date=target_date,
                today_only=False
            )
        
        filtered_loans = report_data['loans']
        
        # Property: Loan should be included regardless of time component
        loan_ids = [loan_data['id'] for loan_data in filtered_loans]
        assert loan.id in loan_ids, \
            f"Loan with due_date {loan.due_date} should be included when filtering for {target_date}"
        
        # Property: The due_date should match the target date (ignoring time)
        for loan_data in filtered_loans:
            if loan_data['id'] == loan.id:
                loan_due_date = loan_data['due_date']
                if isinstance(loan_due_date, datetime):
                    loan_due_date = loan_due_date.date()
                
                assert loan_due_date == target_date, \
                    f"Loan due date {loan_due_date} should match target date {target_date}"
    
    @given(
        num_reports=st.integers(min_value=2, max_value=3),
        days_offset=st.integers(min_value=0, max_value=10)
    )
    @settings(max_examples=50, deadline=None)
    def test_property_21_date_filter_consistency(self, num_reports, days_offset):
        """
        Property 21: Date Filter Consistency
        
        For any report page with date filtering, the same date comparison logic
        (date-only, inclusive ranges) should be applied.
        
        Validates: Requirements 6.10
        """
        # Clean up any existing loans
        Loan.objects.all().delete()
        LoanApplication.objects.all().delete()
        
        today = timezone.now().date()
        start_date = today
        end_date = today + timedelta(days=days_offset)
        
        # Create test loans
        for i in range(5):
            application = LoanApplication.objects.create(
                borrower=self.borrower,
                loan_product=self.loan_product,
                requested_amount=Decimal('10000.00'),
                requested_duration=30,  # 30 days
                status='approved'
            )
            
            loan_due_date = start_date + timedelta(days=i)
            Loan.objects.create(
                loan_number=f'LOAN-CONS-{i:04d}',
                application=application,
                borrower=self.borrower,
                principal_amount=Decimal('10000.00'),
                interest_amount=Decimal('1000.00'),
                processing_fee=Decimal('500.00'),
                total_amount=Decimal('11500.00'),
                disbursement_date=today,
                due_date=datetime.combine(loan_due_date, datetime.min.time()),
                status='active',
                is_deleted=False,
                is_rolled_over=False
            )
        
        # Get loans from comprehensive reports service
        reports_service = ComprehensiveReportsService()
        report_data = reports_service.get_loans_due_report(
            start_date=start_date,
            end_date=end_date,
            today_only=False
        )
        
        filtered_loans_count = len(report_data['loans'])
        
        # Property: The number of loans should be consistent with the date range
        # All loans with due_date in [start_date, end_date] should be included
        expected_count = min(days_offset + 1, 5)  # Can't exceed total loans created
        
        assert filtered_loans_count == expected_count, \
            f"Expected {expected_count} loans in range [{start_date}, {end_date}], got {filtered_loans_count}"
        
        # Property: All returned loans should have due_date within the inclusive range
        for loan_data in report_data['loans']:
            loan_due_date = loan_data['due_date']
            if isinstance(loan_due_date, datetime):
                loan_due_date = loan_due_date.date()
            
            assert start_date <= loan_due_date <= end_date, \
                f"Loan due date {loan_due_date} is outside inclusive range [{start_date}, {end_date}]"

