"""
Unit Tests for Loan Recalculation

This module contains unit tests to verify loan recalculation logic
for edits, penalty additions, and payment recordings.

Feature: comprehensive-reports-and-fixes
"""

import os
import sys
import django
from decimal import Decimal

# Setup Django
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'branch_system.settings')
django.setup()

from django.test import TestCase
from django.contrib.auth import get_user_model
from django.utils import timezone
from loans.models import Loan, LoanApplication, LoanProduct, PenaltyCharge, Repayment

User = get_user_model()


class RecalculationUnitTests(TestCase):
    """
    Unit tests for loan recalculation functionality.
    
    Tests Requirements 8.9, 8.10, 8.11, 8.12
    """
    
    def setUp(self):
        """Set up test data"""
        # Create or get test user
        self.user, created = User.objects.get_or_create(
            username='testuser_unit',
            defaults={
                'email': 'test_unit@example.com',
                'first_name': 'Test',
                'last_name': 'User',
                'phone_number': '+254712345680'
            }
        )
        
        # Create loan product
        self.loan_product, created = LoanProduct.objects.get_or_create(
            name='Test Product Unit',
            product_type='boost',
            defaults={
                'description': 'Test product for unit 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,
                'available_repayment_methods': ['monthly']
            }
        )
    
    def test_total_amount_calculation_after_edits(self):
        """
        Test total_amount calculation after loan edits.
        
        Validates: Requirements 8.9
        """
        # Create loan application
        application = LoanApplication.objects.create(
            borrower=self.user,
            loan_product=self.loan_product,
            requested_amount=Decimal('10000.00'),
            requested_duration=30,
            purpose='Test loan',
            interest_amount=Decimal('1000.00'),
            processing_fee_amount=Decimal('500.00'),
            total_amount=Decimal('11500.00')
        )
        
        # Create loan
        loan = Loan.objects.create(
            application=application,
            borrower=self.user,
            principal_amount=Decimal('10000.00'),
            interest_amount=Decimal('1000.00'),
            processing_fee=Decimal('500.00'),
            total_amount=Decimal('11500.00'),
            disbursement_date=timezone.now(),
            due_date=timezone.now() + timezone.timedelta(days=30),
            duration_days=30,
            status='active'
        )
        
        # Verify initial total
        self.assertEqual(loan.total_amount, Decimal('11500.00'))
        
        # Edit loan amounts
        loan.principal_amount = Decimal('12000.00')
        loan.interest_amount = Decimal('1200.00')
        loan.processing_fee = Decimal('600.00')
        loan.save()
        
        # Reload from database
        loan.refresh_from_db()
        
        # Verify total_amount was recalculated
        expected_total = Decimal('12000.00') + Decimal('1200.00') + Decimal('600.00')
        self.assertEqual(loan.total_amount, expected_total)
        
        # Clean up
        loan.delete()
        application.delete()
    
    def test_outstanding_amount_after_penalty_addition(self):
        """
        Test outstanding_amount after penalty addition.
        
        Validates: Requirements 8.10
        """
        # Create loan application
        application = LoanApplication.objects.create(
            borrower=self.user,
            loan_product=self.loan_product,
            requested_amount=Decimal('10000.00'),
            requested_duration=30,
            purpose='Test loan',
            interest_amount=Decimal('1000.00'),
            processing_fee_amount=Decimal('500.00'),
            total_amount=Decimal('11500.00')
        )
        
        # Create loan
        loan = Loan.objects.create(
            application=application,
            borrower=self.user,
            principal_amount=Decimal('10000.00'),
            interest_amount=Decimal('1000.00'),
            processing_fee=Decimal('500.00'),
            total_amount=Decimal('11500.00'),
            disbursement_date=timezone.now(),
            due_date=timezone.now() + timezone.timedelta(days=30),
            duration_days=30,
            status='active'
        )
        
        # Record initial outstanding amount
        initial_outstanding = loan.outstanding_amount
        self.assertEqual(initial_outstanding, Decimal('11500.00'))
        
        # Add penalty
        penalty = PenaltyCharge.objects.create(
            loan=loan,
            amount=Decimal('500.00'),
            penalty_rate=Decimal('5.00'),
            days_overdue=1,
            outstanding_amount=loan.outstanding_amount,
            penalty_date=timezone.now().date()
        )
        
        # Reload loan
        loan.refresh_from_db()
        
        # Verify outstanding_amount increased by penalty amount
        expected_outstanding = initial_outstanding + Decimal('500.00')
        self.assertEqual(loan.outstanding_amount, expected_outstanding)
        
        # Clean up
        penalty.delete()
        loan.delete()
        application.delete()
    
    def test_amount_paid_after_payment_recording(self):
        """
        Test amount_paid after payment recording.
        
        Validates: Requirements 8.11
        """
        # Create loan application
        application = LoanApplication.objects.create(
            borrower=self.user,
            loan_product=self.loan_product,
            requested_amount=Decimal('10000.00'),
            requested_duration=30,
            purpose='Test loan',
            interest_amount=Decimal('1000.00'),
            processing_fee_amount=Decimal('500.00'),
            total_amount=Decimal('11500.00')
        )
        
        # Create loan
        loan = Loan.objects.create(
            application=application,
            borrower=self.user,
            principal_amount=Decimal('10000.00'),
            interest_amount=Decimal('1000.00'),
            processing_fee=Decimal('500.00'),
            total_amount=Decimal('11500.00'),
            disbursement_date=timezone.now(),
            due_date=timezone.now() + timezone.timedelta(days=30),
            duration_days=30,
            status='active'
        )
        
        # Verify initial amount_paid is zero
        self.assertEqual(loan.amount_paid, Decimal('0.00'))
        
        # Record payment
        repayment = Repayment.objects.create(
            loan=loan,
            amount=Decimal('5000.00'),
            payment_method='mpesa',
            payment_date=timezone.now()
        )
        
        # Reload loan
        loan.refresh_from_db()
        
        # Verify amount_paid increased
        self.assertEqual(loan.amount_paid, Decimal('5000.00'))
        
        # Record another payment
        repayment2 = Repayment.objects.create(
            loan=loan,
            amount=Decimal('3000.00'),
            payment_method='mpesa',
            payment_date=timezone.now()
        )
        
        # Reload loan
        loan.refresh_from_db()
        
        # Verify amount_paid is sum of all payments
        self.assertEqual(loan.amount_paid, Decimal('8000.00'))
        
        # Clean up
        repayment2.delete()
        repayment.delete()
        loan.delete()
        application.delete()
    
    def test_decimal_precision_maintained(self):
        """
        Test that Decimal precision is maintained in calculations.
        
        Validates: Requirements 8.12
        """
        # Create loan application with precise decimal values
        application = LoanApplication.objects.create(
            borrower=self.user,
            loan_product=self.loan_product,
            requested_amount=Decimal('10000.33'),
            requested_duration=30,
            purpose='Test loan',
            interest_amount=Decimal('1000.55'),
            processing_fee_amount=Decimal('500.77'),
            total_amount=Decimal('11501.65')
        )
        
        # Create loan
        loan = Loan.objects.create(
            application=application,
            borrower=self.user,
            principal_amount=Decimal('10000.33'),
            interest_amount=Decimal('1000.55'),
            processing_fee=Decimal('500.77'),
            total_amount=Decimal('11501.65'),
            disbursement_date=timezone.now(),
            due_date=timezone.now() + timezone.timedelta(days=30),
            duration_days=30,
            status='active'
        )
        
        # Verify all amounts are Decimal type
        self.assertIsInstance(loan.principal_amount, Decimal)
        self.assertIsInstance(loan.interest_amount, Decimal)
        self.assertIsInstance(loan.processing_fee, Decimal)
        self.assertIsInstance(loan.total_amount, Decimal)
        
        # Verify precision is maintained (2 decimal places)
        self.assertEqual(str(loan.principal_amount), '10000.33')
        self.assertEqual(str(loan.interest_amount), '1000.55')
        self.assertEqual(str(loan.processing_fee), '500.77')
        self.assertEqual(str(loan.total_amount), '11501.65')
        
        # Clean up
        loan.delete()
        application.delete()
    
    def test_rounding_to_2_decimal_places(self):
        """
        Test that all monetary values are rounded to 2 decimal places.
        
        Validates: Requirements 8.12
        """
        # Test LoanProduct calculation methods
        result = self.loan_product.calculate_interest(Decimal('10000.00'), 1)
        self.assertEqual(len(str(result).split('.')[-1]), 2, "Interest should have 2 decimal places")
        
        result = self.loan_product.calculate_processing_fee(Decimal('10000.00'), 1)
        self.assertEqual(len(str(result).split('.')[-1]), 2, "Processing fee should have 2 decimal places")
        
        # Create loan and test calculated properties
        application = LoanApplication.objects.create(
            borrower=self.user,
            loan_product=self.loan_product,
            requested_amount=Decimal('10000.00'),
            requested_duration=30,
            purpose='Test loan',
            interest_amount=Decimal('1000.00'),
            processing_fee_amount=Decimal('500.00'),
            total_amount=Decimal('11500.00')
        )
        
        loan = Loan.objects.create(
            application=application,
            borrower=self.user,
            principal_amount=Decimal('10000.00'),
            interest_amount=Decimal('1000.00'),
            processing_fee=Decimal('500.00'),
            total_amount=Decimal('11500.00'),
            disbursement_date=timezone.now(),
            due_date=timezone.now() + timezone.timedelta(days=30),
            duration_days=30,
            status='active'
        )
        
        # Test amount_paid property
        amount_paid = loan.amount_paid
        self.assertEqual(len(str(amount_paid).split('.')[-1]), 2, "Amount paid should have 2 decimal places")
        
        # Test outstanding_amount property
        outstanding = loan.outstanding_amount
        self.assertEqual(len(str(outstanding).split('.')[-1]), 2, "Outstanding amount should have 2 decimal places")
        
        # Clean up
        loan.delete()
        application.delete()


if __name__ == '__main__':
    import unittest
    
    print("="*70)
    print("UNIT TESTS: Loan Recalculation")
    print("="*70)
    print()
    
    # Create test suite
    suite = unittest.TestLoader().loadTestsFromTestCase(RecalculationUnitTests)
    
    # Run tests
    runner = unittest.TextTestRunner(verbosity=2)
    result = runner.run(suite)
    
    # Print summary
    print()
    print("="*70)
    print("TEST SUMMARY")
    print("="*70)
    print(f"Tests run: {result.testsRun}")
    print(f"Failures: {len(result.failures)}")
    print(f"Errors: {len(result.errors)}")
    print(f"Success: {result.wasSuccessful()}")
    print("="*70)
    
    sys.exit(0 if result.wasSuccessful() else 1)
