"""
Property-based tests for rolled-over loans functionality.

These tests verify that rolled-over loans are handled correctly across the system.
"""

from hypothesis import given, strategies as st, settings
from django.test import TestCase, TransactionTestCase
from django.utils import timezone
from datetime import date, timedelta
from decimal import Decimal
import uuid

from loans.models import Loan, LoanProduct, LoanApplication
from users.models import CustomUser


class TestRolledOverLoansProperties(TransactionTestCase):
    """
    Test properties related to rolled-over loans handling
    """
    
    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',
            role='borrower'
        )
        
        # 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']
        )
    
    def create_loan(self, is_rolled_over=False, is_deleted=False, status='active'):
        """Helper to create a loan with specified properties"""
        # Create application
        application = LoanApplication.objects.create(
            borrower=self.user,
            loan_product=self.product,
            requested_amount=Decimal('10000'),
            requested_duration=30,
            purpose='Test loan',
            status='approved'
        )
        
        # Create loan
        loan = Loan.objects.create(
            application=application,
            borrower=self.user,
            principal_amount=Decimal('10000'),
            interest_amount=Decimal('1000'),
            processing_fee=Decimal('500'),
            total_amount=Decimal('11500'),
            disbursement_date=timezone.now(),
            due_date=timezone.now() + timedelta(days=30),
            duration_days=30,
            status=status,
            is_rolled_over=is_rolled_over,
            is_deleted=is_deleted
        )
        
        return loan
    
    @settings(max_examples=50, deadline=None)
    @given(
        num_rolled_over=st.integers(min_value=1, max_value=10),
        num_active=st.integers(min_value=1, max_value=10),
        num_paid=st.integers(min_value=0, max_value=5)
    )
    def test_property_14_rolled_over_loans_page_exclusivity(
        self, num_rolled_over, num_active, num_paid
    ):
        """
        Feature: reports-system-enhancement, Property 14: Rolled-over loans page exclusivity
        Validates: Requirements 4.5
        
        For any loan displayed on the rolled-over loans page, it should have either
        status equal to 'rolled_over' OR is_rolled_over flag equal to true.
        """
        # Create rolled-over loans
        rolled_over_loans = []
        for _ in range(num_rolled_over):
            loan = self.create_loan(is_rolled_over=True, status='rolled_over')
            rolled_over_loans.append(loan)
        
        # Create active loans (should not appear)
        active_loans = []
        for _ in range(num_active):
            loan = self.create_loan(is_rolled_over=False, status='active')
            active_loans.append(loan)
        
        # Create paid loans (should not appear)
        paid_loans = []
        for _ in range(num_paid):
            loan = self.create_loan(is_rolled_over=False, status='paid')
            paid_loans.append(loan)
        
        # Query for rolled-over loans (simulating the view logic)
        queryset = Loan.objects.filter(status='rolled_over') | Loan.objects.filter(is_rolled_over=True)
        queryset = queryset.distinct()
        
        # Property: All returned loans should be rolled over
        for loan in queryset:
            self.assertTrue(
                loan.status == 'rolled_over' or loan.is_rolled_over,
                f"Loan {loan.loan_number} appears in rolled-over page but is not rolled over"
            )
        
        # Property: All rolled-over loans should be in the result
        self.assertEqual(
            queryset.count(),
            num_rolled_over,
            f"Expected {num_rolled_over} rolled-over loans, got {queryset.count()}"
        )
        
        # Property: No active or paid loans should appear
        for loan in active_loans:
            self.assertNotIn(loan, queryset, f"Active loan {loan.loan_number} should not appear")
        
        for loan in paid_loans:
            self.assertNotIn(loan, queryset, f"Paid loan {loan.loan_number} should not appear")
        
        # Cleanup
        Loan.objects.all().delete()
        LoanApplication.objects.all().delete()
    
    @settings(max_examples=50, deadline=None)
    @given(
        num_rolled_over_active=st.integers(min_value=1, max_value=10),
        num_rolled_over_deleted=st.integers(min_value=1, max_value=10)
    )
    def test_property_29_rollover_statistics_exclusion(
        self, num_rolled_over_active, num_rolled_over_deleted
    ):
        """
        Feature: reports-system-enhancement, Property 29: Rollover statistics exclusion
        Validates: Requirements 11.5
        
        For any rollover statistics calculation, loans with is_deleted flag set to true
        should not contribute to the statistics.
        """
        # Create active rolled-over loans (should be included in statistics)
        active_rolled_over = []
        for _ in range(num_rolled_over_active):
            loan = self.create_loan(is_rolled_over=True, is_deleted=False, status='rolled_over')
            active_rolled_over.append(loan)
        
        # Create deleted rolled-over loans (should be excluded from statistics)
        deleted_rolled_over = []
        for _ in range(num_rolled_over_deleted):
            loan = self.create_loan(is_rolled_over=True, is_deleted=True, status='rolled_over')
            deleted_rolled_over.append(loan)
        
        # Query for rollover statistics (excluding deleted)
        stats_queryset = Loan.objects.filter(
            status='rolled_over',
            is_deleted=False
        )
        
        # Property: Count should only include non-deleted rolled-over loans
        self.assertEqual(
            stats_queryset.count(),
            num_rolled_over_active,
            f"Statistics should include {num_rolled_over_active} loans, got {stats_queryset.count()}"
        )
        
        # Property: No deleted loans should be in statistics
        for loan in deleted_rolled_over:
            self.assertNotIn(
                loan,
                stats_queryset,
                f"Deleted loan {loan.loan_number} should not be in statistics"
            )
        
        # Property: All active rolled-over loans should be in statistics
        for loan in active_rolled_over:
            self.assertIn(
                loan,
                stats_queryset,
                f"Active rolled-over loan {loan.loan_number} should be in statistics"
            )
        
        # Calculate total amount (simulating statistics calculation)
        total_amount = sum(loan.principal_amount for loan in stats_queryset)
        expected_total = sum(loan.principal_amount for loan in active_rolled_over)
        
        # Property: Total amount should only include non-deleted loans
        self.assertEqual(
            total_amount,
            expected_total,
            f"Total amount should be {expected_total}, got {total_amount}"
        )
        
        # Cleanup
        Loan.objects.all().delete()
        LoanApplication.objects.all().delete()

