"""
Unit tests for expenses app models

This module tests all models in the expenses app:
- Expense
"""

import pytest
from decimal import Decimal
from datetime import date, timedelta
from django.utils import timezone
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from expenses.models import Expense
from users.models import Branch

User = get_user_model()


@pytest.mark.django_db
class TestExpense:
    """Test cases for Expense model"""
    
    @pytest.fixture
    def branch(self):
        """Create a test branch"""
        return Branch.objects.create(
            name='Main Branch',
            code='MAIN',
            is_main_branch=True
        )
    
    @pytest.fixture
    def staff_user(self, branch):
        """Create a staff user"""
        return User.objects.create_user(
            username='staff',
            phone_number='+254700000001',
            role='loan_officer',
            is_staff=True,
            branch=branch
        )
    
    @pytest.fixture
    def admin_user(self, branch):
        """Create an admin user"""
        return User.objects.create_user(
            username='admin',
            phone_number='+254700000002',
            role='admin',
            is_staff=True,
            is_superuser=True,
            branch=branch
        )
    
    @pytest.fixture
    def expense(self, branch, staff_user):
        """Create a test expense"""
        return Expense.objects.create(
            title='Office Supplies',
            description='Purchase of stationery',
            category='office',
            amount=Decimal('5000.00'),
            payment_method='cash',
            paid_to='Stationery Store',
            branch=branch,
            staff=staff_user,
            expense_date=date.today()
        )
    
    def test_expense_creation(self, expense):
        """Test expense is created successfully"""
        assert expense.id is not None
        assert expense.title == 'Office Supplies'
        assert expense.amount == Decimal('5000.00')
        assert expense.status == 'pending'
    
    def test_expense_str_representation(self, expense):
        """Test string representation"""
        expected = 'Office Supplies - KES 5000.00 (Pending)'
        assert str(expense) == expected
    
    def test_expense_categories(self):
        """Test all expense categories are available"""
        categories = [choice[0] for choice in Expense.CATEGORY_CHOICES]
        assert 'operational' in categories
        assert 'staff' in categories
        assert 'marketing' in categories
        assert 'loan_related' in categories
        assert 'utilities' in categories
        assert 'office' in categories
        assert 'transport' in categories
        assert 'maintenance' in categories
        assert 'other' in categories
    
    def test_payment_methods(self):
        """Test all payment methods are available"""
        methods = [choice[0] for choice in Expense.PAYMENT_METHOD_CHOICES]
        assert 'cash' in methods
        assert 'mpesa' in methods
        assert 'bank' in methods
        assert 'cheque' in methods
    
    def test_expense_statuses(self):
        """Test all expense statuses are available"""
        statuses = [choice[0] for choice in Expense.STATUS_CHOICES]
        assert 'pending' in statuses
        assert 'approved' in statuses
        assert 'rejected' in statuses
    
    def test_approve_expense(self, expense, admin_user):
        """Test approving an expense"""
        expense.approve(admin_user)
        
        assert expense.status == 'approved'
        assert expense.approved_by == admin_user
        assert expense.approved_at is not None
    
    def test_reject_expense(self, expense, admin_user):
        """Test rejecting an expense"""
        reason = 'Insufficient documentation'
        expense.reject(admin_user, reason)
        
        assert expense.status == 'rejected'
        assert expense.approved_by == admin_user
        assert expense.approved_at is not None
        assert expense.rejection_reason == reason
    
    def test_is_pending_property(self, expense):
        """Test is_pending property"""
        assert expense.is_pending is True
        expense.status = 'approved'
        assert expense.is_pending is False
    
    def test_is_approved_property(self, expense):
        """Test is_approved property"""
        assert expense.is_approved is False
        expense.status = 'approved'
        assert expense.is_approved is True
    
    def test_is_rejected_property(self, expense):
        """Test is_rejected property"""
        assert expense.is_rejected is False
        expense.status = 'rejected'
        assert expense.is_rejected is True
    
    def test_has_receipt_property(self, expense):
        """Test has_receipt property"""
        assert expense.has_receipt is False
        # In a real scenario, you would upload a file
        # For testing, we just set the field
        expense.receipt_path = 'expenses/receipts/2024/01/receipt.pdf'
        assert expense.has_receipt is True
    
    def test_expense_with_branch(self, branch, staff_user):
        """Test expense with branch association"""
        expense = Expense.objects.create(
            title='Branch Utilities',
            category='utilities',
            amount=Decimal('10000.00'),
            payment_method='bank',
            paid_to='Kenya Power',
            branch=branch,
            staff=staff_user,
            expense_date=date.today()
        )
        assert expense.branch == branch
    
    def test_expense_without_branch(self, staff_user):
        """Test expense without branch (head office expense)"""
        expense = Expense.objects.create(
            title='Head Office Expense',
            category='operational',
            amount=Decimal('20000.00'),
            payment_method='bank',
            paid_to='Service Provider',
            staff=staff_user,
            expense_date=date.today()
        )
        assert expense.branch is None
    
    def test_expense_with_loan_reference(self, branch, staff_user):
        """Test expense with loan reference"""
        from loans.models import LoanProduct, LoanApplication, Loan
        
        # Create loan
        borrower = User.objects.create_user(
            username='borrower',
            phone_number='+254700000003',
            role='borrower',
            branch=branch
        )
        product = LoanProduct.objects.create(
            name='Test Product',
            product_type='biashara',
            description='Test',
            min_amount=Decimal('10000.00'),
            max_amount=Decimal('500000.00'),
            interest_rate=Decimal('18.00'),
            processing_fee=Decimal('5.00'),
            duration_months=6,
            min_duration=30,
            max_duration=180,
            available_repayment_methods=['monthly']
        )
        application = LoanApplication.objects.create(
            borrower=borrower,
            loan_product=product,
            requested_amount=Decimal('100000.00'),
            requested_duration=90,
            purpose='Test',
            status='approved'
        )
        loan = Loan.objects.create(
            application=application,
            borrower=borrower,
            principal_amount=Decimal('100000.00'),
            interest_amount=Decimal('18000.00'),
            processing_fee=Decimal('5000.00'),
            total_amount=Decimal('100000.00'),
            disbursement_date=timezone.now(),
            due_date=timezone.now() + timedelta(days=90),
            duration_days=90,
            status='active'
        )
        
        # Create expense related to loan
        expense = Expense.objects.create(
            title='Loan Processing Cost',
            category='loan_related',
            amount=Decimal('1000.00'),
            payment_method='cash',
            paid_to='Agent',
            loan=loan,
            branch=branch,
            staff=staff_user,
            expense_date=date.today()
        )
        assert expense.loan == loan
    
    def test_expense_with_reference_number(self, branch, staff_user):
        """Test expense with transaction reference number"""
        expense = Expense.objects.create(
            title='Online Service',
            category='operational',
            amount=Decimal('3000.00'),
            payment_method='mpesa',
            paid_to='Service Provider',
            reference_number='ABC123XYZ',
            branch=branch,
            staff=staff_user,
            expense_date=date.today()
        )
        assert expense.reference_number == 'ABC123XYZ'
    
    def test_expense_ordering(self, branch, staff_user):
        """Test expenses are ordered by date descending"""
        expense1 = Expense.objects.create(
            title='Old Expense',
            category='office',
            amount=Decimal('1000.00'),
            payment_method='cash',
            paid_to='Vendor A',
            branch=branch,
            staff=staff_user,
            expense_date=date.today() - timedelta(days=10)
        )
        expense2 = Expense.objects.create(
            title='Recent Expense',
            category='office',
            amount=Decimal('2000.00'),
            payment_method='cash',
            paid_to='Vendor B',
            branch=branch,
            staff=staff_user,
            expense_date=date.today()
        )
        
        expenses = list(Expense.objects.all())
        assert expenses[0] == expense2  # Most recent first
        assert expenses[1] == expense1
    
    def test_expense_zero_amount_validation(self, branch, staff_user):
        """Test that zero amount fails validation"""
        with pytest.raises(ValidationError):
            expense = Expense(
                title='Invalid Expense',
                category='office',
                amount=Decimal('0.00'),
                payment_method='cash',
                paid_to='Vendor',
                branch=branch,
                staff=staff_user,
                expense_date=date.today()
            )
            expense.full_clean()  # This triggers validation
    
    def test_expense_negative_amount_validation(self, branch, staff_user):
        """Test that negative amount fails validation"""
        with pytest.raises(ValidationError):
            expense = Expense(
                title='Invalid Expense',
                category='office',
                amount=Decimal('-100.00'),
                payment_method='cash',
                paid_to='Vendor',
                branch=branch,
                staff=staff_user,
                expense_date=date.today()
            )
            expense.full_clean()  # This triggers validation
