"""
Tests for Portfolio Snapshot Service

This module tests the portfolio snapshot generation, health score calculations,
and performance alert functionality.
"""

from django.test import TestCase
from django.utils import timezone
from django.contrib.auth import get_user_model
from decimal import Decimal
from datetime import date, timedelta
from unittest.mock import patch, MagicMock

from users.models import Branch
from users.enhanced_permissions_models import PortfolioSnapshot
from users.portfolio_snapshot_service import PortfolioSnapshotService
from loans.models import Loan, LoanApplication, LoanProduct
from utils.models import Notification

User = get_user_model()


class PortfolioSnapshotServiceTest(TestCase):
    """Test cases for PortfolioSnapshotService"""
    
    def setUp(self):
        """Set up test data"""
        # Create test branch
        self.branch = Branch.objects.create(
            name="Test Branch",
            code="TB001",
            is_active=True
        )
        
        # Create test users
        self.admin = User.objects.create_user(
            username="admin",
            email="admin@test.com",
            password="testpass123",
            role="admin",
            branch=self.branch,
            phone_number="+254700000001"
        )
        
        self.loan_officer = User.objects.create_user(
            username="officer1",
            email="officer1@test.com",
            password="testpass123",
            role="loan_officer",
            branch=self.branch,
            phone_number="+254700000002"
        )
        
        self.borrower = User.objects.create_user(
            username="borrower1",
            email="borrower1@test.com",
            password="testpass123",
            role="borrower",
            branch=self.branch,
            phone_number="+254700000003",
            monthly_income=Decimal('50000.00')
        )
        
        # Create loan product
        self.loan_product = LoanProduct.objects.create(
            name="Test Product",
            product_type="boost",
            description="Test loan product",
            min_amount=Decimal('1000.00'),
            max_amount=Decimal('100000.00'),
            interest_rate=Decimal('10.00'),
            processing_fee=Decimal('5.00'),
            duration_months=1,
            min_duration=7,
            max_duration=30,
            available_repayment_methods=['monthly']
        )
        
        # Initialize service
        self.service = PortfolioSnapshotService()
    
    def test_calculate_portfolio_metrics(self):
        """Test portfolio metrics calculation"""
        target_date = timezone.now().date()
        
        # Create test loan application and loan
        loan_app = LoanApplication.objects.create(
            borrower=self.borrower,
            loan_product=self.loan_product,
            requested_amount=Decimal('10000.00'),
            requested_duration=30,
            purpose="Test loan",
            status='approved'
        )
        
        loan = Loan.objects.create(
            application=loan_app,
            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=target_date,
            due_date=target_date + timedelta(days=30),
            duration_days=30,
            status='active'
        )
        
        # Calculate metrics
        metrics = self.service._calculate_portfolio_metrics(self.loan_officer, target_date)
        
        # Verify metrics
        self.assertEqual(metrics['total_clients'], 1)
        self.assertEqual(metrics['active_loans'], 1)
        self.assertEqual(metrics['total_disbursed'], Decimal('10000.00'))
        self.assertEqual(metrics['new_loans'], 1)
        self.assertEqual(metrics['daily_disbursements'], Decimal('10000.00'))
        self.assertIsInstance(metrics['default_rate'], Decimal)
        self.assertIsInstance(metrics['collection_rate'], Decimal)
    
    def test_generate_daily_snapshots(self):
        """Test daily snapshot generation"""
        target_date = timezone.now().date()
        
        # Mock the _calculate_portfolio_metrics method to avoid complex setup
        with patch.object(self.service, '_calculate_portfolio_metrics') as mock_calc:
            mock_calc.return_value = {
                'branch': self.branch,
                'total_clients': 5,
                'active_clients': 4,
                'new_clients': 1,
                'churned_clients': 0,
                'active_loans': 3,
                'total_loans': 5,
                'new_loans': 1,
                'completed_loans': 0,
                'defaulted_loans': 0,
                'total_disbursed': Decimal('50000.00'),
                'total_outstanding': Decimal('30000.00'),
                'total_collected': Decimal('20000.00'),
                'daily_disbursements': Decimal('10000.00'),
                'daily_collections': Decimal('5000.00'),
                'par_30': Decimal('2000.00'),
                'par_60': Decimal('1000.00'),
                'par_90': Decimal('500.00'),
                'default_rate': Decimal('5.00'),
                'collection_rate': Decimal('85.00'),
                'portfolio_yield': Decimal('12.00'),
                'average_loan_size': Decimal('10000.00')
            }
            
            # Mock alert checking to avoid email sending
            with patch.object(self.service, '_check_performance_alerts'):
                results = self.service.generate_daily_snapshots(target_date)
        
        # Verify results
        self.assertTrue(results['success'])
        self.assertEqual(results['managers_processed'], 1)
        self.assertEqual(results['snapshots_generated'], 1)
        
        # Verify snapshot was created
        snapshot = PortfolioSnapshot.objects.get(
            manager=self.loan_officer,
            snapshot_date=target_date
        )
        self.assertEqual(snapshot.total_clients, 5)
        self.assertEqual(snapshot.active_loans, 3)
    
    def test_portfolio_health_score_calculation(self):
        """Test portfolio health score calculation"""
        # Create a snapshot with known values
        snapshot = PortfolioSnapshot.objects.create(
            manager=self.loan_officer,
            branch=self.branch,
            snapshot_date=timezone.now().date(),
            total_clients=10,
            active_clients=9,
            active_loans=8,
            total_disbursed=Decimal('100000.00'),
            total_outstanding=Decimal('60000.00'),
            total_collected=Decimal('40000.00'),
            collection_rate=Decimal('90.00'),  # Good collection rate
            default_rate=Decimal('2.00'),      # Low default rate
            portfolio_yield=Decimal('15.00')   # Good yield
        )
        
        # Calculate health score
        health_score = snapshot.get_portfolio_health_score()
        
        # Health score should be good (above 70) with these metrics
        self.assertGreater(health_score, 70)
        self.assertLessEqual(health_score, 100)
    
    def test_growth_rate_calculation(self):
        """Test growth rate calculation"""
        today = timezone.now().date()
        yesterday = today - timedelta(days=1)
        
        # Create snapshots for two consecutive days
        snapshot_yesterday = PortfolioSnapshot.objects.create(
            manager=self.loan_officer,
            branch=self.branch,
            snapshot_date=yesterday,
            total_clients=8,
            active_clients=7,
            active_loans=6
        )
        
        snapshot_today = PortfolioSnapshot.objects.create(
            manager=self.loan_officer,
            branch=self.branch,
            snapshot_date=today,
            total_clients=10,
            active_clients=9,
            active_loans=8
        )
        
        # Calculate growth rate
        growth_rate = snapshot_today.calculate_growth_rate(snapshot_yesterday)
        
        # Growth rate should be 25% (from 8 to 10 clients)
        self.assertEqual(growth_rate, Decimal('25.00'))
    
    def test_performance_alerts_critical_health(self):
        """Test performance alerts for critical health score"""
        # Create snapshot with poor metrics
        snapshot = PortfolioSnapshot.objects.create(
            manager=self.loan_officer,
            branch=self.branch,
            snapshot_date=timezone.now().date(),
            total_clients=10,
            collection_rate=Decimal('60.00'),  # Poor collection rate
            default_rate=Decimal('15.00'),     # High default rate
            total_outstanding=Decimal('50000.00'),
            par_30=Decimal('10000.00')         # High PAR
        )
        
        health_score = Decimal('40.00')  # Critical health score
        
        # Mock notification creation and email sending
        with patch('users.portfolio_snapshot_service.Notification') as mock_notification:
            with patch.object(self.service, '_send_alert_email'):
                self.service._check_performance_alerts(self.loan_officer, snapshot, health_score)
        
        # Verify that notifications would be created
        self.assertTrue(mock_notification.objects.create.called)
    
    def test_portfolio_trends_calculation(self):
        """Test portfolio trends calculation"""
        # Create multiple snapshots over time
        base_date = timezone.now().date()
        
        for i in range(5):
            snapshot_date = base_date - timedelta(days=i)
            PortfolioSnapshot.objects.create(
                manager=self.loan_officer,
                branch=self.branch,
                snapshot_date=snapshot_date,
                total_clients=10 + i,
                active_loans=8 + i,
                total_disbursed=Decimal(str(50000 + (i * 10000))),
                collection_rate=Decimal('85.00'),
                default_rate=Decimal('3.00')
            )
        
        # Get trends
        trends = self.service.get_portfolio_trends(self.loan_officer, 5)
        
        # Verify trends structure
        self.assertIn('period', trends)
        self.assertIn('client_growth', trends)
        self.assertIn('daily_data', trends)
        self.assertEqual(len(trends['daily_data']), 5)
    
    def test_performance_report_generation(self):
        """Test performance report generation"""
        target_date = timezone.now().date()
        
        # Create a snapshot
        snapshot = PortfolioSnapshot.objects.create(
            manager=self.loan_officer,
            branch=self.branch,
            snapshot_date=target_date,
            total_clients=15,
            active_clients=14,
            active_loans=12,
            total_disbursed=Decimal('150000.00'),
            total_outstanding=Decimal('90000.00'),
            total_collected=Decimal('60000.00'),
            collection_rate=Decimal('88.00'),
            default_rate=Decimal('4.00'),
            portfolio_yield=Decimal('14.00')
        )
        
        # Mock trends calculation
        with patch.object(self.service, 'get_portfolio_trends') as mock_trends:
            mock_trends.return_value = {'client_growth': 2, 'loan_growth': 1}
            
            # Generate report
            report = self.service.generate_performance_report(self.loan_officer, target_date)
        
        # Verify report structure
        self.assertIn('manager', report)
        self.assertIn('portfolio_overview', report)
        self.assertIn('performance_metrics', report)
        self.assertIn('risk_metrics', report)
        self.assertEqual(report['manager']['name'], self.loan_officer.get_full_name())
        self.assertEqual(report['portfolio_overview']['total_clients'], 15)
    
    def test_calculate_portfolio_health_score_service_method(self):
        """Test the service method for calculating health score"""
        target_date = timezone.now().date()
        
        # Create a snapshot
        snapshot = PortfolioSnapshot.objects.create(
            manager=self.loan_officer,
            branch=self.branch,
            snapshot_date=target_date,
            total_clients=10,
            collection_rate=Decimal('85.00'),
            default_rate=Decimal('5.00'),
            portfolio_yield=Decimal('12.00')
        )
        
        # Calculate health score using service method
        health_score = self.service.calculate_portfolio_health_score(self.loan_officer, target_date)
        
        # Verify health score is calculated
        self.assertIsInstance(health_score, Decimal)
        self.assertGreaterEqual(health_score, 0)
        self.assertLessEqual(health_score, 100)
    
    def test_calculate_growth_rate_service_method(self):
        """Test the service method for calculating growth rate"""
        today = timezone.now().date()
        past_date = today - timedelta(days=30)
        
        # Create snapshots
        PortfolioSnapshot.objects.create(
            manager=self.loan_officer,
            branch=self.branch,
            snapshot_date=past_date,
            total_clients=8
        )
        
        PortfolioSnapshot.objects.create(
            manager=self.loan_officer,
            branch=self.branch,
            snapshot_date=today,
            total_clients=10
        )
        
        # Calculate growth rate
        growth_rate = self.service.calculate_growth_rate(self.loan_officer, 30)
        
        # Verify growth rate calculation
        self.assertEqual(growth_rate, Decimal('25.00'))
    
    def tearDown(self):
        """Clean up test data"""
        # Clean up is handled automatically by Django test framework
        pass


class PortfolioSnapshotModelTest(TestCase):
    """Test cases for PortfolioSnapshot model"""
    
    def setUp(self):
        """Set up test data"""
        self.branch = Branch.objects.create(
            name="Test Branch",
            code="TB001",
            is_active=True
        )
        
        self.loan_officer = User.objects.create_user(
            username="officer1",
            email="officer1@test.com",
            password="testpass123",
            role="loan_officer",
            branch=self.branch,
            phone_number="+254700000002"
        )
    
    def test_portfolio_snapshot_creation(self):
        """Test creating a portfolio snapshot"""
        snapshot = PortfolioSnapshot.objects.create(
            manager=self.loan_officer,
            branch=self.branch,
            snapshot_date=timezone.now().date(),
            total_clients=10,
            active_clients=9,
            active_loans=8,
            total_disbursed=Decimal('100000.00'),
            total_outstanding=Decimal('60000.00'),
            collection_rate=Decimal('85.00'),
            default_rate=Decimal('5.00')
        )
        
        self.assertEqual(str(snapshot), f"{self.loan_officer.get_full_name()} - {snapshot.snapshot_date}")
        self.assertEqual(snapshot.manager, self.loan_officer)
        self.assertEqual(snapshot.total_clients, 10)
    
    def test_health_score_calculation_edge_cases(self):
        """Test health score calculation with edge cases"""
        # Test with zero values
        snapshot = PortfolioSnapshot.objects.create(
            manager=self.loan_officer,
            branch=self.branch,
            snapshot_date=timezone.now().date(),
            total_clients=0,
            collection_rate=Decimal('0.00'),
            default_rate=Decimal('0.00'),
            portfolio_yield=Decimal('0.00')
        )
        
        health_score = snapshot.get_portfolio_health_score()
        self.assertGreaterEqual(health_score, 0)
        self.assertLessEqual(health_score, 100)
    
    def test_growth_rate_with_no_previous_snapshot(self):
        """Test growth rate calculation when no previous snapshot exists"""
        snapshot = PortfolioSnapshot.objects.create(
            manager=self.loan_officer,
            branch=self.branch,
            snapshot_date=timezone.now().date(),
            total_clients=10
        )
        
        growth_rate = snapshot.calculate_growth_rate()
        self.assertEqual(growth_rate, 0)
    
    def test_unique_constraint(self):
        """Test unique constraint on manager and snapshot_date"""
        date = timezone.now().date()
        
        # Create first snapshot
        PortfolioSnapshot.objects.create(
            manager=self.loan_officer,
            branch=self.branch,
            snapshot_date=date,
            total_clients=10
        )
        
        # Try to create duplicate - should raise IntegrityError
        from django.db import IntegrityError
        with self.assertRaises(IntegrityError):
            PortfolioSnapshot.objects.create(
                manager=self.loan_officer,
                branch=self.branch,
                snapshot_date=date,
                total_clients=15
            )