"""
Property-based tests for loans in arrears report.

These tests verify the correctness of arrears definition, calculation, and exclusion logic.
"""

from hypothesis import given, strategies as st, settings, assume
from django.test import TestCase
from django.utils import timezone
from datetime import date, datetime, timedelta
from decimal import Decimal
import uuid

from loans.models import Loan, LoanProduct, LoanApplication, Repayment
from users.models import CustomUser
from reports.simple_reports_service import SimpleReportsService


class TestArrearsProperties(TestCase):
    """
    Property-based tests for loans in arrears report
    """
    
    def setUp(self):
        """Set up test data"""
        # Create test user
        self.user = CustomUser.objects.create_user(
            username='testuser',
            email='test@example.com',
            phone_number='+254700000000',
            first_name='Test',
            last_name='User'
        )
        
        # Create test loan product
        self.product = LoanProduct.objects.create(
            name='Test Product',
            product_type='boost',
            description='Test product',
            min_amount=Decimal('1000'),
            max_amount=Decimal('50000'),
            interest_rate=Decimal('10.0'),
            processing_fee=Decimal('5.0'),
            min_duration=7,
            max_duration=90,
            available_repayment_methods=['monthly']
        )
        
        self.service = SimpleReportsService()
    
    def tearDown(self):
        """Clean up test data"""
        Repayment.objects.all().delete()
        Loan.objects.all().delete()
        LoanApplication.objects.all().delete()
        CustomUser.objects.filter(username='testuser').delete()
        LoanProduct.objects.filter(name='Test Product').delete()
    
    @settings(max_examples=50, deadline=None)
    @given(
        days_past_due=st.integers(min_value=1, max_value=365),
        outstanding_amount=st.decimals(
            min_value=Decimal('0.01'),
            max_value=Decimal('100000'),
            places=2
        )
    )
    def test_property_34_arrears_definition(self, days_past_due, outstanding_amount):
        """
        Feature: reports-system-enhancement, Property 34: Arrears definition
        Validates: Requirements 14.1
        
        For any loan in the loans in arrears report, it should satisfy:
        due_date < current_date AND outstanding_amount > 0
        """
        # Create loan application
        application = LoanApplication.objects.create(
            borrower=self.user,
            loan_product=self.product,
            requested_amount=Decimal('10000'),
            duration_days=30,
            status='approved'
        )
        
        # Create loan with due date in the past
        today = timezone.now()
        due_date = today - timedelta(days=days_past_due)
        disbursement_date = due_date - timedelta(days=30)
        
        principal = Decimal('10000')
        interest = principal * Decimal('0.10')
        processing_fee = principal * Decimal('0.05')
        total = principal + interest + processing_fee
        
        loan = Loan.objects.create(
            application=application,
            borrower=self.user,
            principal_amount=principal,
            interest_amount=interest,
            processing_fee=processing_fee,
            total_amount=total,
            disbursement_date=disbursement_date,
            due_date=due_date,
            duration_days=30,
            status='active',
            is_deleted=False,
            is_rolled_over=False
        )
        
        # Create repayments to achieve the desired outstanding amount
        amount_to_pay = total - outstanding_amount
        if amount_to_pay > 0:
            Repayment.objects.create(
                loan=loan,
                amount=amount_to_pay,
                payment_date=disbursement_date + timedelta(days=5),
                payment_method='mpesa',
                transaction_id=f'TEST{uuid.uuid4().hex[:8]}'
            )
        
        # Get arrears report
        report_data = self.service.get_loans_in_arrears_report()
        
        # Verify the loan appears in the arrears report
        loan_ids_in_report = [loan_data['id'] for loan_data in report_data['loans']]
        
        # The loan should be in the report because:
        # 1. due_date < current_date (it's past due)
        # 2. outstanding_amount > 0 (there's still money owed)
        self.assertIn(
            str(loan.id),
            loan_ids_in_report,
            f"Loan with due_date {days_past_due} days ago and outstanding {outstanding_amount} "
            f"should appear in arrears report"
        )
        
        # Verify the loan data in the report
        loan_in_report = next(
            (l for l in report_data['loans'] if l['id'] == str(loan.id)),
            None
        )
        self.assertIsNotNone(loan_in_report)
        
        # Verify days overdue is positive
        self.assertGreater(
            loan_in_report['days_overdue'],
            0,
            "Days overdue should be positive for loans in arrears"
        )
        
        # Verify arrears amount is positive
        self.assertGreater(
            loan_in_report['arrears_amount'],
            0,
            "Arrears amount should be positive for loans in arrears"
        )
    
    @settings(max_examples=50, deadline=None)
    @given(
        days_past_due=st.integers(min_value=1, max_value=365)
    )
    def test_property_35_days_in_arrears_calculation(self, days_past_due):
        """
        Feature: reports-system-enhancement, Property 35: Days in arrears calculation
        Validates: Requirements 14.2
        
        For any loan in arrears, the days_in_arrears should equal
        (current_date - due_date).days
        """
        # Create loan application
        application = LoanApplication.objects.create(
            borrower=self.user,
            loan_product=self.product,
            requested_amount=Decimal('10000'),
            duration_days=30,
            status='approved'
        )
        
        # Create loan with due date in the past
        today = timezone.now()
        due_date = today - timedelta(days=days_past_due)
        disbursement_date = due_date - timedelta(days=30)
        
        principal = Decimal('10000')
        interest = principal * Decimal('0.10')
        processing_fee = principal * Decimal('0.05')
        total = principal + interest + processing_fee
        
        loan = Loan.objects.create(
            application=application,
            borrower=self.user,
            principal_amount=principal,
            interest_amount=interest,
            processing_fee=processing_fee,
            total_amount=total,
            disbursement_date=disbursement_date,
            due_date=due_date,
            duration_days=30,
            status='active',
            is_deleted=False,
            is_rolled_over=False
        )
        
        # Get arrears report
        report_data = self.service.get_loans_in_arrears_report()
        
        # Find the loan in the report
        loan_in_report = next(
            (l for l in report_data['loans'] if l['id'] == str(loan.id)),
            None
        )
        
        if loan_in_report:
            # Calculate expected days overdue
            # Note: We need to handle timezone-aware vs naive datetime
            if hasattr(due_date, 'date'):
                due_date_only = due_date.date()
            else:
                due_date_only = due_date
            
            if hasattr(today, 'date'):
                today_only = today.date()
            else:
                today_only = today
            
            expected_days_overdue = (today_only - due_date_only).days
            
            # Verify days overdue calculation
            # Allow for small differences due to timing
            actual_days_overdue = loan_in_report['days_overdue']
            
            self.assertGreaterEqual(
                actual_days_overdue,
                expected_days_overdue - 1,
                f"Days overdue should be at least {expected_days_overdue - 1}, got {actual_days_overdue}"
            )
            self.assertLessEqual(
                actual_days_overdue,
                expected_days_overdue + 1,
                f"Days overdue should be at most {expected_days_overdue + 1}, got {actual_days_overdue}"
            )
    
    @settings(max_examples=50, deadline=None)
    @given(
        days_past_due=st.integers(min_value=1, max_value=365)
    )
    def test_property_36_fully_paid_exclusion_from_arrears(self, days_past_due):
        """
        Feature: reports-system-enhancement, Property 36: Fully paid exclusion from arrears
        Validates: Requirements 14.3
        
        For any loan with outstanding_amount equal to zero, it should not appear
        in the loans in arrears report regardless of due_date.
        """
        # Create loan application
        application = LoanApplication.objects.create(
            borrower=self.user,
            loan_product=self.product,
            requested_amount=Decimal('10000'),
            duration_days=30,
            status='approved'
        )
        
        # Create loan with due date in the past
        today = timezone.now()
        due_date = today - timedelta(days=days_past_due)
        disbursement_date = due_date - timedelta(days=30)
        
        principal = Decimal('10000')
        interest = principal * Decimal('0.10')
        processing_fee = principal * Decimal('0.05')
        total = principal + interest + processing_fee
        
        loan = Loan.objects.create(
            application=application,
            borrower=self.user,
            principal_amount=principal,
            interest_amount=interest,
            processing_fee=processing_fee,
            total_amount=total,
            disbursement_date=disbursement_date,
            due_date=due_date,
            duration_days=30,
            status='active',
            is_deleted=False,
            is_rolled_over=False
        )
        
        # Fully pay the loan
        Repayment.objects.create(
            loan=loan,
            amount=total,
            payment_date=disbursement_date + timedelta(days=5),
            payment_method='mpesa',
            transaction_id=f'TEST{uuid.uuid4().hex[:8]}'
        )
        
        # Get arrears report
        report_data = self.service.get_loans_in_arrears_report()
        
        # Verify the loan does NOT appear in the arrears report
        loan_ids_in_report = [loan_data['id'] for loan_data in report_data['loans']]
        
        self.assertNotIn(
            str(loan.id),
            loan_ids_in_report,
            f"Fully paid loan (outstanding=0) should NOT appear in arrears report, "
            f"even though due_date was {days_past_due} days ago"
        )

