"""
Unit tests for penalty addition functionality.

Feature: comprehensive-reports-and-fixes
Validates: Requirements 8.4
"""

from django.test import TestCase, Client
from django.utils import timezone
from datetime import date, timedelta
from decimal import Decimal
import uuid

from loans.models import Loan, LoanProduct, LoanApplication, PenaltyCharge
from users.models import CustomUser, Branch


class PenaltyAdditionUnitTests(TestCase):
    """Unit tests for penalty addition functionality"""
    
    def setUp(self):
        """Set up test data"""
        # Create admin user
        self.admin_user = CustomUser.objects.create_user(
            username='admin_unit',
            email='admin_unit@example.com',
            phone_number='+254700000100',
            first_name='Admin',
            last_name='Unit',
            is_staff=True,
            is_superuser=True
        )
        
        # Create non-admin user
        self.regular_user = CustomUser.objects.create_user(
            username='regular_unit',
            email='regular_unit@example.com',
            phone_number='+254700000101',
            first_name='Regular',
            last_name='Unit'
        )
        
        # Create loan product
        self.loan_product = LoanProduct.objects.create(
            name='Test Product Unit',
            product_type='boost',
            description='Test product',
            min_amount=Decimal('1000'),
            max_amount=Decimal('100000'),
            interest_rate=Decimal('10.0'),
            processing_fee=Decimal('5.0'),
            late_payment_penalty=Decimal('2.0'),
            min_duration=7,
            max_duration=90,
            available_repayment_methods=['monthly']
        )
        
        # Create borrower
        self.borrower = CustomUser.objects.create_user(
            username='borrower_unit',
            email='borrower_unit@example.com',
            phone_number='+254700000102',
            first_name='Borrower',
            last_name='Unit'
        )
        
        # Create loan application
        self.application = LoanApplication.objects.create(
            application_number=f'APP-UNIT-{uuid.uuid4().hex[:6]}',
            borrower=self.borrower,
            loan_product=self.loan_product,
            requested_amount=Decimal('10000'),
            requested_duration=30,
            purpose='Test',
            status='approved'
        )
        
        # Create loan
        self.loan = Loan.objects.create(
            loan_number=f'LOAN-UNIT-{uuid.uuid4().hex[:6]}',
            borrower=self.borrower,
            application=self.application,
            principal_amount=Decimal('10000'),
            interest_amount=Decimal('1000'),
            processing_fee=Decimal('500'),
            total_amount=Decimal('11500'),
            disbursement_date=timezone.now() - timedelta(days=30),
            due_date=timezone.now() - timedelta(days=5),
            duration_days=25,
            status='active'
        )
        
        self.client = Client()
    
    def test_admin_user_can_add_penalty(self):
        """Test that admin user can successfully add a penalty"""
        self.client.force_login(self.admin_user)
        
        response = self.client.post(
            f'/loans/{self.loan.id}/penalty/apply/',
            {
                'penalty_amount': '500.00',
                'penalty_date': date.today().isoformat(),
                'reason': 'Late payment penalty'
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertTrue(data['success'])
        
        # Verify penalty was created
        penalty = PenaltyCharge.objects.filter(loan=self.loan).first()
        self.assertIsNotNone(penalty)
        self.assertEqual(penalty.amount, Decimal('500.00'))
        self.assertEqual(penalty.applied_by, self.admin_user)
    
    def test_non_admin_user_cannot_add_penalty(self):
        """Test that non-admin user cannot add penalties"""
        self.client.force_login(self.regular_user)
        
        response = self.client.post(
            f'/loans/{self.loan.id}/penalty/apply/',
            {
                'penalty_amount': '500.00',
                'penalty_date': date.today().isoformat(),
                'reason': 'Late payment penalty'
            }
        )
        
        # Should redirect or return 403
        self.assertIn(response.status_code, [302, 403])
        
        # Verify no penalty was created
        penalty_count = PenaltyCharge.objects.filter(loan=self.loan).count()
        self.assertEqual(penalty_count, 0)
    
    def test_penalty_with_zero_amount_rejected(self):
        """Test that penalty with zero amount is rejected"""
        self.client.force_login(self.admin_user)
        
        response = self.client.post(
            f'/loans/{self.loan.id}/penalty/apply/',
            {
                'penalty_amount': '0.00',
                'penalty_date': date.today().isoformat(),
                'reason': 'Test penalty'
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        
        self.assertEqual(response.status_code, 400)
        data = response.json()
        self.assertFalse(data['success'])
        self.assertIn('greater than zero', data['error'].lower())
    
    def test_penalty_with_negative_amount_rejected(self):
        """Test that penalty with negative amount is rejected"""
        self.client.force_login(self.admin_user)
        
        response = self.client.post(
            f'/loans/{self.loan.id}/penalty/apply/',
            {
                'penalty_amount': '-100.00',
                'penalty_date': date.today().isoformat(),
                'reason': 'Test penalty'
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        
        self.assertEqual(response.status_code, 400)
        data = response.json()
        self.assertFalse(data['success'])
        self.assertIn('greater than zero', data['error'].lower())
    
    def test_penalty_with_future_date_rejected(self):
        """Test that penalty with future date is rejected"""
        self.client.force_login(self.admin_user)
        
        future_date = date.today() + timedelta(days=7)
        
        response = self.client.post(
            f'/loans/{self.loan.id}/penalty/apply/',
            {
                'penalty_amount': '500.00',
                'penalty_date': future_date.isoformat(),
                'reason': 'Test penalty'
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        
        self.assertEqual(response.status_code, 400)
        data = response.json()
        self.assertFalse(data['success'])
        self.assertIn('future', data['error'].lower())
    
    def test_penalty_updates_outstanding_amount_correctly(self):
        """Test that adding a penalty updates outstanding amount correctly"""
        self.client.force_login(self.admin_user)
        
        # Get initial outstanding amount
        initial_outstanding = self.loan.outstanding_amount
        penalty_amount = Decimal('500.00')
        
        response = self.client.post(
            f'/loans/{self.loan.id}/penalty/apply/',
            {
                'penalty_amount': str(penalty_amount),
                'penalty_date': date.today().isoformat(),
                'reason': 'Late payment penalty'
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        
        self.assertEqual(response.status_code, 200)
        
        # Refresh loan from database
        self.loan.refresh_from_db()
        
        # Verify outstanding amount increased by penalty amount
        new_outstanding = self.loan.outstanding_amount
        expected_outstanding = initial_outstanding + penalty_amount
        self.assertEqual(new_outstanding, expected_outstanding)
    
    def test_multiple_penalties_on_same_loan(self):
        """Test that multiple penalties can be added to the same loan"""
        self.client.force_login(self.admin_user)
        
        # Add first penalty
        response1 = self.client.post(
            f'/loans/{self.loan.id}/penalty/apply/',
            {
                'penalty_amount': '300.00',
                'penalty_date': (date.today() - timedelta(days=2)).isoformat(),
                'reason': 'First penalty'
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        self.assertEqual(response1.status_code, 200)
        
        # Add second penalty
        response2 = self.client.post(
            f'/loans/{self.loan.id}/penalty/apply/',
            {
                'penalty_amount': '200.00',
                'penalty_date': date.today().isoformat(),
                'reason': 'Second penalty'
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        self.assertEqual(response2.status_code, 200)
        
        # Verify both penalties exist
        penalty_count = PenaltyCharge.objects.filter(loan=self.loan).count()
        self.assertEqual(penalty_count, 2)
        
        # Verify total penalties sum correctly
        self.loan.refresh_from_db()
        total_penalties = self.loan.total_penalties
        self.assertEqual(total_penalties, Decimal('500.00'))
    
    def test_penalty_without_reason_rejected(self):
        """Test that penalty without reason is rejected"""
        self.client.force_login(self.admin_user)
        
        response = self.client.post(
            f'/loans/{self.loan.id}/penalty/apply/',
            {
                'penalty_amount': '500.00',
                'penalty_date': date.today().isoformat(),
                'reason': ''
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        
        self.assertEqual(response.status_code, 400)
        data = response.json()
        self.assertFalse(data['success'])
        self.assertIn('reason', data['error'].lower())
    
    def test_penalty_with_past_date_accepted(self):
        """Test that penalty with past date is accepted"""
        self.client.force_login(self.admin_user)
        
        past_date = date.today() - timedelta(days=10)
        
        response = self.client.post(
            f'/loans/{self.loan.id}/penalty/apply/',
            {
                'penalty_amount': '500.00',
                'penalty_date': past_date.isoformat(),
                'reason': 'Late payment penalty'
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertTrue(data['success'])
        
        # Verify penalty was created with correct date
        penalty = PenaltyCharge.objects.filter(loan=self.loan).first()
        self.assertIsNotNone(penalty)
        self.assertEqual(penalty.penalty_date, past_date)
    
    def test_penalty_with_today_date_accepted(self):
        """Test that penalty with today's date is accepted"""
        self.client.force_login(self.admin_user)
        
        response = self.client.post(
            f'/loans/{self.loan.id}/penalty/apply/',
            {
                'penalty_amount': '500.00',
                'penalty_date': date.today().isoformat(),
                'reason': 'Late payment penalty'
            },
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertTrue(data['success'])
        
        # Verify penalty was created with today's date
        penalty = PenaltyCharge.objects.filter(loan=self.loan).first()
        self.assertIsNotNone(penalty)
        self.assertEqual(penalty.penalty_date, date.today())
