"""
Mobile Auth (OTP) — Tests.
تست‌های واحد و API برای احراز هویت موبایل.
"""
import pytest
from unittest.mock import patch, MagicMock

pytestmark = [pytest.mark.django_db]


# ─── OTP Model Tests ────────────────────────────────────────────────────


class TestOTPModel:
    def test_generate_otp(self):
        from apps.core.auth.otp_models import OTPCode
        instance, plain_code = OTPCode.generate(phone='+989121234567')
        assert instance is not None
        assert len(plain_code) == 5
        assert instance.phone == '+989121234567'
        assert not instance.is_used

    def test_verify_otp_success(self):
        from apps.core.auth.otp_models import OTPCode
        instance, plain_code = OTPCode.generate(phone='+989121234567')
        result = instance.verify(plain_code)
        assert result is True
        instance.refresh_from_db()
        assert instance.is_used is True

    def test_verify_otp_wrong_code(self):
        from apps.core.auth.otp_models import OTPCode
        instance, plain_code = OTPCode.generate(phone='+989121234567')
        result = instance.verify('00000')
        assert result is False
        instance.refresh_from_db()
        assert instance.attempts == 1
        assert instance.is_used is False

    def test_verify_otp_max_attempts(self):
        from apps.core.auth.otp_models import OTPCode
        instance, plain_code = OTPCode.generate(
            phone='+989121234567',
            max_attempts=2,
        )
        instance.verify('00000')
        instance.verify('00000')
        # Now max attempts reached
        result = instance.verify(plain_code)
        assert result is False

    def test_generate_invalidates_previous(self):
        from apps.core.auth.otp_models import OTPCode
        first, _ = OTPCode.generate(phone='+989121234567')
        second, _ = OTPCode.generate(phone='+989121234567')
        first.refresh_from_db()
        assert first.is_used is True  # Invalidated by second generation


# ─── OTP Service Tests ──────────────────────────────────────────────────


class TestOTPService:
    def test_normalize_phone_iranian(self):
        from apps.core.auth.otp_service import OTPService
        assert OTPService.normalize_phone('09121234567') == '+989121234567'
        assert OTPService.normalize_phone('+989121234567') == '+989121234567'
        assert OTPService.normalize_phone('9121234567') == '+989121234567'

    def test_normalize_phone_persian_digits(self):
        from apps.core.auth.otp_service import OTPService
        assert OTPService.normalize_phone('۰۹۱۲۱۲۳۴۵۶۷') == '+989121234567'

    @patch('apps.core.auth.otp_service.cache')
    def test_send_otp_success(self, mock_cache):
        from apps.core.auth.otp_service import OTPService

        mock_cache.get.return_value = 0

        with patch('apps.services.notification.providers.sms.SMSProvider') as mock_sms_class:
            mock_response = MagicMock()
            mock_response.success = True
            mock_sms_instance = MagicMock()
            mock_sms_instance.send.return_value = mock_response
            mock_sms_class.return_value = mock_sms_instance

            result = OTPService.send_otp(
                phone='09121234567',
                ip_address='127.0.0.1',
            )
            assert result['success'] is True

    @patch('apps.core.auth.otp_service.cache')
    def test_send_otp_rate_limited(self, mock_cache):
        from apps.core.auth.otp_service import OTPService

        mock_cache.get.return_value = 100  # Over limit
        result = OTPService.send_otp(
            phone='09121234567',
            ip_address='127.0.0.1',
        )
        assert result['success'] is False
        assert 'مجاز' in result['message'] or 'محدود' in result['message']


# ─── GeoIP Service Tests ────────────────────────────────────────────────


class TestGeoIPService:
    def test_detect_country_fallback(self):
        from apps.core.auth.geoip_service import detect_country_from_ip
        # With no DB file, should return None gracefully
        result = detect_country_from_ip('127.0.0.1')
        # Either returns something or None (no crash)
        assert result is None or isinstance(result, dict)


# ─── Auth API Tests ──────────────────────────────────────────────────────


class TestOTPAuthAPI:
    def test_send_otp_endpoint(self, api_client):
        with patch('apps.core.auth.views.OTPService') as mock_svc:
            mock_svc.send_otp.return_value = {
                'success': True,
                'message': 'کد تأیید ارسال شد',
                'retry_after': 60,
                'expires_in': 120,
            }
            response = api_client.post('/api/v1/auth/otp/send/', {
                'phone': '09121234567',
            })
            assert response.status_code == 200
            data = response.json()
            assert 'data' in data
            assert data['data']['retry_after'] == 60

    def test_verify_otp_endpoint_creates_user(self, api_client):
        with patch('apps.core.auth.views.OTPService') as mock_svc:
            mock_svc.normalize_phone.return_value = '+989121234567'
            mock_svc.verify_otp.return_value = {'success': True}

            response = api_client.post('/api/v1/auth/otp/verify/', {
                'phone': '09121234567',
                'code': '12345',
            })
            assert response.status_code == 200
            data = response.json()
            assert 'data' in data
            assert 'tokens' in data['data']
            assert data['data']['is_new_user'] is True

    def test_auth_settings_endpoint(self, api_client):
        response = api_client.get('/api/v1/auth/settings/')
        assert response.status_code == 200
        data = response.json()
        assert 'data' in data
        assert 'allow_mobile_auth' in data['data']

    def test_detect_country_endpoint(self, api_client):
        response = api_client.get('/api/v1/auth/detect-country/')
        assert response.status_code == 200
        data = response.json()
        assert 'data' in data


# ─── Profile API Tests ──────────────────────────────────────────────────


class TestProfileAPI:
    def test_get_profile(self, authenticated_client):
        client, user = authenticated_client
        response = client.get('/api/v1/auth/profile/')
        assert response.status_code == 200
        data = response.json()
        assert 'data' in data
        assert data['data']['email'] == user.email

    def test_update_profile(self, authenticated_client):
        client, user = authenticated_client
        response = client.patch('/api/v1/auth/profile/', {
            'first_name': 'علی',
            'display_name': 'علی تست',
        })
        assert response.status_code == 200
        data = response.json()
        assert data['data']['first_name'] == 'علی'

    def test_avatar_upload_no_file(self, authenticated_client):
        client, user = authenticated_client
        response = client.post('/api/v1/auth/profile/avatar/')
        assert response.status_code == 400

    def test_link_phone_requires_auth(self, api_client):
        response = api_client.post('/api/v1/auth/phone/link/', {
            'phone': '09121234567',
            'code': '12345',
        })
        assert response.status_code in [401, 403]

    def test_profile_history(self, authenticated_client):
        client, user = authenticated_client
        response = client.get('/api/v1/auth/profile/history/')
        assert response.status_code == 200
        data = response.json()
        assert 'data' in data
