from unittest.mock import patch

from django.contrib.auth.models import User
from django.core import mail
from django.test import override_settings
from rest_framework import status
from rest_framework.test import APITestCase

from .google_auth import GoogleAuthenticationError, verify_google_credential


@override_settings(
    EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend',
    DEFAULT_FROM_EMAIL='TheStarFX <test@thestarfx.example>',
    GOOGLE_OAUTH_CLIENT_ID='test-google-client.apps.googleusercontent.com',
)
class AuthenticationFlowTests(APITestCase):
    password = 'StrongPass123!'

    def register(self, email='new.member@example.com'):
        return self.client.post('/api/auth/register/', {
            'name': 'New Member',
            'email': email,
            'password': self.password,
            'password2': self.password,
        }, format='json')

    def test_email_registration_sends_one_welcome_email_and_login_sends_none(self):
        registration = self.register()

        self.assertEqual(registration.status_code, status.HTTP_201_CREATED)
        self.assertTrue(registration.data['registration_email_sent'])
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].subject, 'Registration Successful')
        self.assertEqual(mail.outbox[0].to, ['new.member@example.com'])

        login = self.client.post('/api/auth/login/', {
            'username': 'new.member@example.com',
            'password': self.password,
        }, format='json')

        self.assertEqual(login.status_code, status.HTTP_200_OK)
        self.assertIn('access', login.data)
        self.assertIn('refresh', login.data)
        self.assertEqual(len(mail.outbox), 1)

        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {login.data['access']}")
        profile = self.client.get('/api/auth/me/')
        self.assertEqual(profile.status_code, status.HTTP_200_OK)
        self.assertEqual(profile.data['email'], 'new.member@example.com')

    def test_duplicate_email_registration_does_not_send_another_email(self):
        self.assertEqual(self.register().status_code, status.HTTP_201_CREATED)
        duplicate = self.register(email='NEW.MEMBER@example.com')

        self.assertEqual(duplicate.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(User.objects.filter(email__iexact='new.member@example.com').count(), 1)
        self.assertEqual(len(mail.outbox), 1)

    @patch('base.views.verify_google_credential')
    def test_google_registration_emails_once_then_returns_normal_jwt_pair(self, verifier):
        verifier.return_value = {
            'subject': 'google-subject-123',
            'email': 'google.member@example.com',
            'first_name': 'Google',
            'last_name': 'Member',
        }

        first = self.client.post('/api/auth/google/', {'credential': 'valid-token'}, format='json')
        second = self.client.post('/api/auth/google/', {'credential': 'valid-token'}, format='json')

        self.assertEqual(first.status_code, status.HTTP_200_OK)
        self.assertTrue(first.data['is_new_user'])
        self.assertIn('access', first.data)
        self.assertIn('refresh', first.data)
        self.assertEqual(second.status_code, status.HTTP_200_OK)
        self.assertFalse(second.data['is_new_user'])
        self.assertEqual(User.objects.filter(email='google.member@example.com').count(), 1)
        self.assertFalse(User.objects.get(email='google.member@example.com').has_usable_password())
        self.assertEqual(len(mail.outbox), 1)

    @patch('base.views.verify_google_credential')
    def test_verified_google_email_can_sign_in_existing_user_without_email(self, verifier):
        User.objects.create_user(
            username='existing.member@example.com',
            email='existing.member@example.com',
            password=self.password,
        )
        verifier.return_value = {
            'subject': 'google-subject-existing',
            'email': 'existing.member@example.com',
            'first_name': 'Existing',
            'last_name': 'Member',
        }

        response = self.client.post('/api/auth/google/', {'credential': 'valid-token'}, format='json')

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertFalse(response.data['is_new_user'])
        self.assertEqual(len(mail.outbox), 0)

    @patch('base.views.verify_google_credential')
    def test_invalid_google_credential_is_rejected(self, verifier):
        verifier.side_effect = GoogleAuthenticationError('The Google credential is invalid or expired.')

        response = self.client.post('/api/auth/google/', {'credential': 'bad-token'}, format='json')

        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(User.objects.count(), 0)

    @patch('base.views.verify_google_credential')
    def test_google_login_does_not_bypass_inactive_account(self, verifier):
        User.objects.create_user(
            username='inactive@example.com',
            email='inactive@example.com',
            password=self.password,
            is_active=False,
        )
        verifier.return_value = {
            'subject': 'google-subject-inactive',
            'email': 'inactive@example.com',
            'first_name': 'Inactive',
            'last_name': 'Member',
        }

        response = self.client.post('/api/auth/google/', {'credential': 'valid-token'}, format='json')

        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertNotIn('access', response.data)
        self.assertEqual(len(mail.outbox), 0)

    def test_logout_blacklists_refresh_token(self):
        User.objects.create_user(
            username='logout@example.com',
            email='logout@example.com',
            password=self.password,
        )
        login = self.client.post('/api/auth/login/', {
            'username': 'logout@example.com',
            'password': self.password,
        }, format='json')
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {login.data['access']}")

        logout = self.client.post('/api/auth/logout/', {
            'refresh': login.data['refresh'],
        }, format='json')
        refresh = self.client.post('/api/auth/refresh/', {
            'refresh': login.data['refresh'],
        }, format='json')

        self.assertEqual(logout.status_code, status.HTTP_204_NO_CONTENT)
        self.assertEqual(refresh.status_code, status.HTTP_401_UNAUTHORIZED)

    @patch('base.google_auth.id_token.verify_oauth2_token')
    def test_google_verifier_requires_verified_email(self, verify_token):
        verify_token.return_value = {
            'iss': 'https://accounts.google.com',
            'sub': 'google-subject-unverified',
            'email': 'unverified@example.com',
            'email_verified': False,
        }

        with self.assertRaisesMessage(
            GoogleAuthenticationError,
            'Google must provide a verified email address.',
        ):
            verify_google_credential('unverified-token')
