"""
Unit tests for IntegrationService

Tests the automatic journal entry creation for loan and expense transactions.
"""

from decimal import Decimal
from datetime import date, datetime, timedelta
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.utils import timezone

from accounting.models import Account, JournalEntry, JournalEntryLine
from accounting.services.integration_service import IntegrationService
from loans.models import Loan, LoanProduct, LoanApplication, Repayment
from expenses.models import Expense
from users.models import Branch

User = get_user_model()


class IntegrationServiceTests(TestCase):
    """Test suite for IntegrationService"""
    
    def setUp(self):
        """Set up test data"""
        self.service = IntegrationService()
        
        # Create test user
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123',
            first_name='Test',
            last_name='User',
            phone_number='0712345678'
        )
        
        # Create test branch
        self.branch = Branch.objects.create(
            name='Test Branch',
            code='TB001'
        )
        
        # Create system accounts
        self.loan_portfolio_account = Account.objects.create(
            code='202001',
            name='Loan Portfolio',
            account_type='asset',
            subtype='loan_portfolio',
            description='Loan Portfolio',
            is_system_account=True,
            is_active=True,
            created_by=self.user
        )
        
        self.accrued_interest_account = Account.objects.create(
            code='202002',
            name='Accrued Interest Receivable',
            account_type='asset',
            subtype='current_asset',
            description='Accrued Interest Receivable',
            is_system_account=True,
            is_active=True,
            created_by=self.user
        )
        
        self.cash_account = Account.objects.create(
            code='202003',
            name='Cash',
            account_type='asset',
            subtype='current_asset',
            description='Cash Account',
            is_system_account=True,
            is_active=True,
            created_by=self.user
        )
        
        self.bank_account = Account.objects.create(
            code='202004',
            name='Bank',
            account_type='asset',
            subtype='current_asset',
            description='Bank Account',
            is_system_account=True,
            is_active=True,
            created_by=self.user
        )
        
        self.mpesa_account = Account.objects.create(
            code='202005',
            name='M-Pesa',
            account_type='asset',
            subtype='current_asset',
            description='M-Pesa Account',
            is_system_account=True,
            is_active=True,
            created_by=self.user
        )
        
        self.interest_income_account = Account.objects.create(
            code='401001',
            name='Interest Income',
            account_type='income',
            description='Interest Income from Loans',
            is_system_account=True,
            is_active=True,
            created_by=self.user
        )
        
        self.fee_income_account = Account.objects.create(
            code='401002',
            name='Fee Income',
            account_type='income',
            description='Fee Income from Loans',
            is_system_account=True,
            is_active=True,
            created_by=self.user
        )
        
        # Create expense accounts
        self.operational_expense_account = Account.objects.create(
            code='502001',
            name='Operating Expenses',
            account_type='expense',
            description='Operating Expenses',
            is_active=True,
            created_by=self.user
        )
        
        self.staff_expense_account = Account.objects.create(
            code='502002',
            name='Staff Costs',
            account_type='expense',
            description='Staff Costs',
            is_active=True,
            created_by=self.user
        )
        
        self.marketing_expense_account = Account.objects.create(
            code='502003',
            name='Marketing Expenses',
            account_type='expense',
            description='Marketing Expenses',
            is_active=True,
            created_by=self.user
        )
        
        self.loan_loss_provision_account = Account.objects.create(
            code='502004',
            name='Loan Loss Provisions',
            account_type='expense',
            description='Loan Loss Provisions',
            is_active=True,
            created_by=self.user
        )
        
        self.utilities_expense_account = Account.objects.create(
            code='502005',
            name='Utilities Expenses',
            account_type='expense',
            description='Utilities Expenses',
            is_active=True,
            created_by=self.user
        )
        
        self.office_expense_account = Account.objects.create(
            code='502006',
            name='Office Expenses',
            account_type='expense',
            description='Office Expenses',
            is_active=True,
            created_by=self.user
        )
        
        self.transport_expense_account = Account.objects.create(
            code='502007',
            name='Transport Expenses',
            account_type='expense',
            description='Transport Expenses',
            is_active=True,
            created_by=self.user
        )
        
        self.maintenance_expense_account = Account.objects.create(
            code='502008',
            name='Maintenance Expenses',
            account_type='expense',
            description='Maintenance Expenses',
            is_active=True,
            created_by=self.user
        )
        
        self.other_expense_account = Account.objects.create(
            code='502009',
            name='Other Expenses',
            account_type='expense',
            description='Other Expenses',
            is_active=True,
            created_by=self.user
        )
        
        # Create borrower
        self.borrower = User.objects.create_user(
            username='borrower',
            email='borrower@example.com',
            password='pass123',
            first_name='John',
            last_name='Doe',
            phone_number='0723456789'
        )
        self.borrower.branch = self.branch
        self.borrower.save()
        
        # Create loan product
        self.loan_product = LoanProduct.objects.create(
            name='Test Loan Product',
            product_type='biashara',
            description='Test product',
            min_amount=Decimal('1000.00'),
            max_amount=Decimal('100000.00'),
            interest_rate=Decimal('10.00'),
            processing_fee=Decimal('5.00'),
            min_duration=30,
            max_duration=365,
            is_active=True
        )
        
        # Create loan application
        self.loan_application = LoanApplication.objects.create(
            application_number='APP-000001',
            borrower=self.borrower,
            loan_product=self.loan_product,
            requested_amount=Decimal('10000.00'),
            requested_duration=90,
            purpose='Business expansion',
            repayment_method='monthly',
            status='approved'
        )
        
        # Create loan
        self.loan = Loan.objects.create(
            loan_number='LOAN-000001',
            application=self.loan_application,
            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=timezone.now(),
            due_date=timezone.now() + timedelta(days=90),
            duration_days=90,
            status='active',
            disbursed_by=self.user,
            created_by=self.user
        )
    
    def test_create_loan_disbursement_entry(self):
        """Test create_loan_disbursement_entry creates correct journal entry"""
        entry = self.service.create_loan_disbursement_entry(self.loan)
        
        # Verify journal entry was created
        self.assertIsNotNone(entry)
        self.assertEqual(entry.reference_number, f"LOAN-DISB-{self.loan.loan_number}")
        self.assertEqual(entry.status, 'draft')
        self.assertEqual(entry.branch, self.branch)
        self.assertEqual(entry.loan, self.loan)
        
        # Verify lines were created
        lines = entry.lines.all().order_by('line_number')
        self.assertEqual(lines.count(), 2)
        
        # Line 1: Debit Loan Portfolio
        line1 = lines[0]
        self.assertEqual(line1.account, self.loan_portfolio_account)
        self.assertEqual(line1.debit_amount, self.loan.principal_amount)
        self.assertEqual(line1.credit_amount, Decimal('0.00'))
        
        # Line 2: Credit Cash
        line2 = lines[1]
        self.assertEqual(line2.account, self.cash_account)
        self.assertEqual(line2.debit_amount, Decimal('0.00'))
        self.assertEqual(line2.credit_amount, self.loan.principal_amount)
    
    def test_create_loan_repayment_entry(self):
        """Test create_loan_repayment_entry splits payment correctly"""
        # Create a repayment
        repayment = Repayment.objects.create(
            loan=self.loan,
            amount=Decimal('5000.00'),
            payment_method='mpesa',
            payment_source='automatic',
            payment_date=timezone.now()
        )
        
        entry = self.service.create_loan_repayment_entry(repayment)
        
        # Verify journal entry was created
        self.assertIsNotNone(entry)
        self.assertEqual(entry.reference_number, f"LOAN-REPAY-{repayment.id}")
        self.assertEqual(entry.status, 'draft')
        self.assertEqual(entry.branch, self.branch)
        self.assertEqual(entry.loan, self.loan)
        
        # Verify lines were created (at minimum: debit cash, credit loan portfolio)
        lines = entry.lines.all().order_by('line_number')
        self.assertGreaterEqual(lines.count(), 2)
        
        # Line 1: Debit Cash
        line1 = lines[0]
        self.assertEqual(line1.account, self.cash_account)
        self.assertEqual(line1.debit_amount, repayment.amount)
        self.assertEqual(line1.credit_amount, Decimal('0.00'))
        
        # Line 2: Credit Loan Portfolio (principal portion)
        line2 = lines[1]
        self.assertEqual(line2.account, self.loan_portfolio_account)
        self.assertEqual(line2.debit_amount, Decimal('0.00'))
        self.assertEqual(line2.credit_amount, repayment.amount)
    
    def test_create_interest_accrual_entry(self):
        """Test create_interest_accrual_entry creates accrual entry"""
        interest_amount = Decimal('100.00')
        accrual_date = date.today()
        
        entry = self.service.create_interest_accrual_entry(
            self.loan,
            interest_amount,
            accrual_date
        )
        
        # Verify journal entry was created
        self.assertIsNotNone(entry)
        self.assertTrue(entry.reference_number.startswith(f"INT-ACCR-{self.loan.loan_number}"))
        self.assertEqual(entry.status, 'draft')
        self.assertEqual(entry.transaction_date, accrual_date)
        self.assertEqual(entry.branch, self.branch)
        self.assertEqual(entry.loan, self.loan)
        
        # Verify lines were created
        lines = entry.lines.all().order_by('line_number')
        self.assertEqual(lines.count(), 2)
        
        # Line 1: Debit Accrued Interest Receivable
        line1 = lines[0]
        self.assertEqual(line1.account, self.accrued_interest_account)
        self.assertEqual(line1.debit_amount, interest_amount)
        self.assertEqual(line1.credit_amount, Decimal('0.00'))
        
        # Line 2: Credit Interest Income
        line2 = lines[1]
        self.assertEqual(line2.account, self.interest_income_account)
        self.assertEqual(line2.debit_amount, Decimal('0.00'))
        self.assertEqual(line2.credit_amount, interest_amount)
    
    def test_create_expense_entry_cash(self):
        """Test create_expense_entry maps categories and payment methods - cash"""
        expense = Expense.objects.create(
            title='Office Supplies',
            description='Pens and paper',
            category='office',
            amount=Decimal('500.00'),
            payment_method='cash',
            paid_to='Stationery Shop',
            expense_date=date.today(),
            branch=self.branch,
            staff=self.user,
            status='approved',
            approved_by=self.user,
            approved_at=timezone.now()
        )
        
        entry = self.service.create_expense_entry(expense)
        
        # Verify journal entry was created
        self.assertIsNotNone(entry)
        self.assertEqual(entry.reference_number, f"EXP-{expense.id}")
        self.assertEqual(entry.status, 'draft')
        self.assertEqual(entry.branch, self.branch)
        self.assertEqual(entry.expense, expense)
        
        # Verify lines were created
        lines = entry.lines.all().order_by('line_number')
        self.assertEqual(lines.count(), 2)
        
        # Line 1: Debit Office Expense
        line1 = lines[0]
        self.assertEqual(line1.account, self.office_expense_account)
        self.assertEqual(line1.debit_amount, expense.amount)
        self.assertEqual(line1.credit_amount, Decimal('0.00'))
        
        # Line 2: Credit Cash
        line2 = lines[1]
        self.assertEqual(line2.account, self.cash_account)
        self.assertEqual(line2.debit_amount, Decimal('0.00'))
        self.assertEqual(line2.credit_amount, expense.amount)
    
    def test_create_expense_entry_mpesa(self):
        """Test create_expense_entry with M-Pesa payment"""
        expense = Expense.objects.create(
            title='Transport Cost',
            category='transport',
            amount=Decimal('1000.00'),
            payment_method='mpesa',
            paid_to='Taxi Driver',
            expense_date=date.today(),
            branch=self.branch,
            staff=self.user,
            status='approved',
            approved_by=self.user,
            approved_at=timezone.now()
        )
        
        entry = self.service.create_expense_entry(expense)
        
        # Verify M-Pesa account was credited
        lines = entry.lines.all().order_by('line_number')
        line2 = lines[1]
        self.assertEqual(line2.account, self.mpesa_account)
        self.assertEqual(line2.credit_amount, expense.amount)
    
    def test_create_expense_entry_bank(self):
        """Test create_expense_entry with bank payment"""
        expense = Expense.objects.create(
            title='Utilities Bill',
            category='utilities',
            amount=Decimal('2000.00'),
            payment_method='bank',
            paid_to='Power Company',
            expense_date=date.today(),
            branch=self.branch,
            staff=self.user,
            status='approved',
            approved_by=self.user,
            approved_at=timezone.now()
        )
        
        entry = self.service.create_expense_entry(expense)
        
        # Verify Bank account was credited
        lines = entry.lines.all().order_by('line_number')
        line2 = lines[1]
        self.assertEqual(line2.account, self.bank_account)
        self.assertEqual(line2.credit_amount, expense.amount)
    
    def test_get_branch_cash_account(self):
        """Test _get_branch_cash_account returns correct account"""
        account = self.service._get_branch_cash_account(self.branch)
        self.assertEqual(account, self.cash_account)
    
    def test_get_expense_account_operational(self):
        """Test _get_expense_account maps operational category correctly"""
        account = self.service._get_expense_account('operational')
        self.assertEqual(account, self.operational_expense_account)
    
    def test_get_expense_account_staff(self):
        """Test _get_expense_account maps staff category correctly"""
        account = self.service._get_expense_account('staff')
        self.assertEqual(account, self.staff_expense_account)
    
    def test_get_expense_account_marketing(self):
        """Test _get_expense_account maps marketing category correctly"""
        account = self.service._get_expense_account('marketing')
        self.assertEqual(account, self.marketing_expense_account)
    
    def test_get_expense_account_invalid(self):
        """Test _get_expense_account raises error for invalid category"""
        with self.assertRaises(ValidationError):
            self.service._get_expense_account('invalid_category')
    
    def test_get_payment_account_cash(self):
        """Test _get_payment_account maps cash correctly"""
        account = self.service._get_payment_account('cash', self.branch)
        self.assertEqual(account, self.cash_account)
    
    def test_get_payment_account_bank(self):
        """Test _get_payment_account maps bank correctly"""
        account = self.service._get_payment_account('bank', self.branch)
        self.assertEqual(account, self.bank_account)
    
    def test_get_payment_account_mpesa(self):
        """Test _get_payment_account maps mpesa correctly"""
        account = self.service._get_payment_account('mpesa', self.branch)
        self.assertEqual(account, self.mpesa_account)
    
    def test_get_payment_account_cheque(self):
        """Test _get_payment_account maps cheque to bank"""
        account = self.service._get_payment_account('cheque', self.branch)
        self.assertEqual(account, self.bank_account)
    
    def test_get_payment_account_invalid(self):
        """Test _get_payment_account raises error for invalid method"""
        with self.assertRaises(ValidationError):
            self.service._get_payment_account('invalid_method', self.branch)
    
    def test_missing_loan_portfolio_account(self):
        """Test error handling when loan portfolio account is missing"""
        # Deactivate the loan portfolio account
        self.loan_portfolio_account.is_active = False
        self.loan_portfolio_account.save()
        
        with self.assertRaises(ValidationError) as context:
            self.service.create_loan_disbursement_entry(self.loan)
        
        self.assertIn('202001', str(context.exception))
    
    def test_missing_expense_account(self):
        """Test error handling when expense account is missing"""
        # Deactivate the office expense account
        self.office_expense_account.is_active = False
        self.office_expense_account.save()
        
        expense = Expense.objects.create(
            title='Office Supplies',
            category='office',
            amount=Decimal('500.00'),
            payment_method='cash',
            paid_to='Shop',
            expense_date=date.today(),
            branch=self.branch,
            staff=self.user,
            status='approved'
        )
        
        with self.assertRaises(ValidationError) as context:
            self.service.create_expense_entry(expense)
        
        self.assertIn('502006', str(context.exception))
