"""
Unit tests for users app models

This module tests all models in the users app:
- Branch
- CustomUser
- RolePermission
- UserPermission
- UserAccessLog
- OTPVerification
"""

import pytest
from decimal import Decimal
from datetime import timedelta
from django.utils import timezone
from django.contrib.auth import get_user_model
from users.models import Branch, RolePermission, UserPermission, UserAccessLog

User = get_user_model()


@pytest.mark.django_db
class TestBranch:
    """Test cases for Branch model"""
    
    def test_branch_creation(self):
        """Test branch is created successfully"""
        branch = Branch.objects.create(
            name='Main Branch',
            code='MAIN',
            address='123 Main St',
            phone_number='+254700000000',
            is_main_branch=True
        )
        assert branch.id is not None
        assert branch.name == 'Main Branch'
        assert branch.code == 'MAIN'
    
    def test_branch_str_representation(self):
        """Test string representation of branch"""
        branch = Branch.objects.create(
            name='Downtown Branch',
            code='DOWNTOWN'
        )
        assert str(branch) == 'Downtown Branch'
    
    def test_only_one_main_branch(self):
        """Test only one branch can be main branch"""
        branch1 = Branch.objects.create(
            name='Branch 1',
            code='BR1',
            is_main_branch=True
        )
        assert branch1.is_main_branch is True
        
        branch2 = Branch.objects.create(
            name='Branch 2',
            code='BR2',
            is_main_branch=True
        )
        
        # Refresh branch1 from database
        branch1.refresh_from_db()
        # Branch1 should no longer be main
        assert branch1.is_main_branch is False
        assert branch2.is_main_branch is True
    
    def test_is_main_property_alias(self):
        """Test is_main property is alias for is_main_branch"""
        branch = Branch.objects.create(
            name='Test Branch',
            code='TEST',
            is_main_branch=True
        )
        assert branch.is_main is True
        assert branch.is_main == branch.is_main_branch
    
    def test_branch_mpesa_configuration(self):
        """Test branch M-Pesa configuration"""
        branch = Branch.objects.create(
            name='Branch with M-Pesa',
            code='MPESA',
            mpesa_shortcode='123456',
            mpesa_consumer_key='test_key',
            mpesa_consumer_secret='test_secret',
            mpesa_passkey='test_passkey'
        )
        assert branch.has_mpesa_config() is True
        assert branch.get_mpesa_shortcode() == '123456'
    
    def test_branch_without_mpesa_config(self):
        """Test branch without M-Pesa configuration"""
        branch = Branch.objects.create(
            name='Branch without M-Pesa',
            code='NOMPESA'
        )
        assert branch.has_mpesa_config() is False


@pytest.mark.django_db
class TestCustomUser:
    """Test cases for CustomUser 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 admin_user(self, branch):
        """Create an admin user"""
        return User.objects.create_user(
            username='admin',
            phone_number='+254700000001',
            role='admin',
            is_staff=True,
            is_superuser=True,
            branch=branch
        )
    
    @pytest.fixture
    def borrower_user(self, branch):
        """Create a borrower user"""
        return User.objects.create_user(
            username='borrower',
            phone_number='+254700000002',
            first_name='John',
            last_name='Doe',
            role='borrower',
            status='active',
            branch=branch
        )
    
    @pytest.fixture
    def loan_officer_user(self, branch):
        """Create a loan officer user"""
        return User.objects.create_user(
            username='officer',
            phone_number='+254700000003',
            role='loan_officer',
            is_staff=True,
            branch=branch
        )
    
    def test_user_creation(self, borrower_user):
        """Test user is created successfully"""
        assert borrower_user.id is not None
        assert borrower_user.username == 'borrower'
        assert borrower_user.role == 'borrower'
    
    def test_user_str_representation(self, borrower_user):
        """Test string representation of user"""
        expected = f"John Doe ({borrower_user.phone_number})"
        assert str(borrower_user) == expected
    
    def test_get_full_name(self, borrower_user):
        """Test get_full_name method"""
        assert borrower_user.get_full_name() == 'John Doe'
    
    def test_get_full_name_fallback_to_username(self):
        """Test get_full_name falls back to username when no name set"""
        user = User.objects.create_user(
            username='noname',
            phone_number='+254700000999'
        )
        assert user.get_full_name() == 'noname'
    
    def test_is_admin_method(self, admin_user, borrower_user):
        """Test is_admin method"""
        assert admin_user.is_admin() is True
        assert borrower_user.is_admin() is False
    
    def test_is_borrower_method(self, borrower_user, admin_user):
        """Test is_borrower method"""
        assert borrower_user.is_borrower() is True
        assert admin_user.is_borrower() is False
    
    def test_is_loan_officer_method(self, loan_officer_user, borrower_user):
        """Test is_loan_officer method"""
        assert loan_officer_user.is_loan_officer() is True
        assert borrower_user.is_loan_officer() is False
    
    def test_is_active_user_method(self, borrower_user):
        """Test is_active_user method"""
        assert borrower_user.is_active_user() is True
        borrower_user.status = 'suspended'
        borrower_user.save()
        assert borrower_user.is_active_user() is False
    
    def test_user_verification(self, borrower_user, admin_user):
        """Test user verification"""
        borrower_user.verified_by = admin_user
        borrower_user.save()
        
        borrower_user.refresh_from_db()
        assert borrower_user.is_verified is True
        assert borrower_user.verification_date is not None
    
    def test_get_initials(self, borrower_user):
        """Test get_initials method"""
        assert borrower_user.get_initials() == 'JD'
    
    def test_get_initials_single_name(self):
        """Test get_initials with single name"""
        user = User.objects.create_user(
            username='single',
            phone_number='+254700000888',
            first_name='John'
        )
        assert user.get_initials() == 'J'
    
    def test_admin_has_all_permissions(self, admin_user):
        """Test admin user has all permissions"""
        assert admin_user.has_permission('loans', 'view') is True
        assert admin_user.has_permission('loans', 'create') is True
        assert admin_user.has_permission('reports', 'view') is True
    
    def test_registration_fee_tracking(self, borrower_user):
        """Test registration fee tracking"""
        borrower_user.mark_registration_fee_paid(
            amount=Decimal('1000.00'),
            payment_method='mpesa',
            receipt_number='ABC123',
            notes='Paid via M-Pesa'
        )
        
        assert borrower_user.registration_fee_paid is True
        assert borrower_user.registration_fee_amount == Decimal('1000.00')
        assert borrower_user.registration_fee_payment_method == 'mpesa'
        assert borrower_user.registration_fee_receipt_number == 'ABC123'
    
    def test_get_registration_fee_status(self, borrower_user):
        """Test get_registration_fee_status method"""
        status = borrower_user.get_registration_fee_status()
        assert status['status'] == 'not_set'
        assert status['paid'] is False
        
        borrower_user.mark_registration_fee_paid(
            amount=Decimal('1000.00'),
            payment_method='mpesa'
        )
        status = borrower_user.get_registration_fee_status()
        assert status['status'] == 'paid'
        assert status['paid'] is True
    
    def test_assign_to_portfolio_manager(self, borrower_user, loan_officer_user):
        """Test assigning borrower to portfolio manager"""
        borrower_user.assign_to_portfolio_manager(loan_officer_user)
        
        assert borrower_user.portfolio_manager == loan_officer_user
        assert borrower_user.assigned_date is not None
    
    def test_assign_to_invalid_portfolio_manager_fails(self, borrower_user):
        """Test assigning to invalid portfolio manager fails"""
        invalid_manager = User.objects.create_user(
            username='invalid',
            phone_number='+254700000777',
            role='secretary'  # Secretary cannot be portfolio manager
        )
        
        with pytest.raises(ValueError, match="Portfolio manager must be a loan officer or team leader"):
            borrower_user.assign_to_portfolio_manager(invalid_manager)
    
    def test_add_document(self, borrower_user):
        """Test adding a document"""
        borrower_user.add_document(
            url='/media/docs/test.pdf',
            description='Test document',
            document_type='id_document'
        )
        
        assert len(borrower_user.other_documents) == 1
        assert borrower_user.other_documents[0]['description'] == 'Test document'


@pytest.mark.django_db
class TestRolePermission:
    """Test cases for RolePermission model"""
    
    def test_role_permission_creation(self):
        """Test role permission is created successfully"""
        permission = RolePermission.objects.create(
            role='loan_officer',
            module='loans',
            action='view',
            is_allowed=True
        )
        assert permission.id is not None
        assert permission.role == 'loan_officer'
        assert permission.is_allowed is True
    
    def test_role_permission_str_representation(self):
        """Test string representation"""
        permission = RolePermission.objects.create(
            role='loan_officer',
            module='loans',
            action='create',
            is_allowed=True
        )
        expected = 'loan_officer - loans.create: Allowed'
        assert str(permission) == expected


@pytest.mark.django_db
class TestUserPermission:
    """Test cases for UserPermission model"""
    
    @pytest.fixture
    def branch(self):
        """Create a test branch"""
        return Branch.objects.create(
            name='Main Branch',
            code='MAIN'
        )
    
    @pytest.fixture
    def user(self, branch):
        """Create a test user"""
        return User.objects.create_user(
            username='testuser',
            phone_number='+254700000001',
            role='loan_officer',
            branch=branch
        )
    
    @pytest.fixture
    def admin(self, branch):
        """Create an admin user"""
        return User.objects.create_user(
            username='admin',
            phone_number='+254700000002',
            role='admin',
            is_staff=True,
            branch=branch
        )
    
    def test_user_permission_creation(self, user, admin):
        """Test user permission is created successfully"""
        permission = UserPermission.objects.create(
            user=user,
            module='reports',
            action='view',
            is_allowed=True,
            granted_by=admin,
            reason='Special access for reports'
        )
        assert permission.id is not None
        assert permission.user == user
        assert permission.is_allowed is True
    
    def test_user_permission_with_expiry(self, user, admin):
        """Test user permission with expiry date"""
        expiry_date = timezone.now() + timedelta(days=30)
        permission = UserPermission.objects.create(
            user=user,
            module='reports',
            action='view',
            is_allowed=True,
            granted_by=admin,
            expires_at=expiry_date
        )
        assert permission.is_expired() is False
    
    def test_expired_permission(self, user, admin):
        """Test expired permission"""
        expiry_date = timezone.now() - timedelta(days=1)
        permission = UserPermission.objects.create(
            user=user,
            module='reports',
            action='view',
            is_allowed=True,
            granted_by=admin,
            expires_at=expiry_date
        )
        assert permission.is_expired() is True


@pytest.mark.django_db
class TestUserAccessLog:
    """Test cases for UserAccessLog model"""
    
    @pytest.fixture
    def branch(self):
        """Create a test branch"""
        return Branch.objects.create(
            name='Main Branch',
            code='MAIN'
        )
    
    @pytest.fixture
    def user(self, branch):
        """Create a test user"""
        return User.objects.create_user(
            username='testuser',
            phone_number='+254700000001',
            role='loan_officer',
            branch=branch
        )
    
    def test_access_log_creation(self, user):
        """Test access log is created successfully"""
        log = UserAccessLog.objects.create(
            user=user,
            action='view',
            module='loans',
            object_type='Loan',
            object_id='123',
            description='Viewed loan details',
            ip_address='127.0.0.1'
        )
        assert log.id is not None
        assert log.user == user
        assert log.action == 'view'
    
    def test_log_access_method(self, user):
        """Test user's log_access method"""
        user.log_access(
            action='create',
            module='loans',
            object_type='Loan',
            object_id='456',
            description='Created new loan'
        )
        
        logs = UserAccessLog.objects.filter(user=user)
        assert logs.count() == 1
        assert logs.first().action == 'create'
