"""Accounts Tests

This module contains comprehensive tests for the accounts app.
Includes tests for models, views, forms, serializers, and utilities.
"""

import json
from datetime import datetime, timedelta
from django.test import TestCase, Client
from django.contrib.auth import get_user_model
from django.urls import reverse
from django.core.exceptions import ValidationError
from django.core import mail
from django.utils import timezone
from rest_framework.test import APITestCase
from rest_framework import status
from unittest.mock import patch, Mock

from .models import User, UserProfile, UserSession
from .forms import (
    CustomAuthenticationForm,
    UserRegistrationForm,
    UserProfileForm,
    UserProfileExtendedForm,
    CustomPasswordChangeForm,
    ContactForm
)
from .serializers import (
    UserSerializer,
    UserRegistrationSerializer,
    LoginSerializer,
    UserProfileSerializer
)
from .validators import (
    CustomPasswordValidator,
    UsernameValidator,
    PhoneNumberValidator,
    DisposableEmailValidator
)
from .utils import (
    generate_username,
    generate_secure_token,
    get_client_ip,
    validate_password_strength,
    cleanup_expired_sessions
)


User = get_user_model()


class UserModelTest(TestCase):
    """Test cases for User model."""
    
    def setUp(self):
        """Set up test data."""
        self.user_data = {
            'username': 'testuser',
            'email': 'test@example.com',
            'first_name': 'Test',
            'last_name': 'User',
            'password': 'TestPass123!'
        }
    
    def test_create_user(self):
        """Test user creation."""
        user = User.objects.create_user(**self.user_data)
        
        self.assertEqual(user.username, 'testuser')
        self.assertEqual(user.email, 'test@example.com')
        self.assertTrue(user.check_password('TestPass123!'))
        self.assertTrue(user.is_active)
        self.assertFalse(user.is_staff)
        self.assertFalse(user.is_superuser)
    
    def test_create_superuser(self):
        """Test superuser creation."""
        user = User.objects.create_superuser(
            username='admin',
            email='admin@example.com',
            password='AdminPass123!'
        )
        
        self.assertTrue(user.is_staff)
        self.assertTrue(user.is_superuser)
        self.assertTrue(user.is_active)
    
    def test_user_str_representation(self):
        """Test user string representation."""
        user = User.objects.create_user(**self.user_data)
        self.assertEqual(str(user), 'testuser')
    
    def test_user_email_unique(self):
        """Test email uniqueness constraint."""
        User.objects.create_user(**self.user_data)
        
        with self.assertRaises(Exception):
            User.objects.create_user(
                username='testuser2',
                email='test@example.com',  # Same email
                password='TestPass123!'
            )
    
    def test_user_profile_creation(self):
        """Test automatic user profile creation."""
        user = User.objects.create_user(**self.user_data)
        
        # Profile should be created automatically via signal
        self.assertTrue(hasattr(user, 'profile'))
        self.assertIsInstance(user.profile, UserProfile)


class UserProfileModelTest(TestCase):
    """Test cases for UserProfile model."""
    
    def setUp(self):
        """Set up test data."""
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='TestPass123!'
        )
    
    def test_profile_creation(self):
        """Test profile creation and default values."""
        profile = self.user.profile
        
        self.assertEqual(profile.user, self.user)
        self.assertEqual(profile.job_title, '')
        self.assertEqual(profile.company, '')
        self.assertIsNotNone(profile.created_at)
        self.assertIsNotNone(profile.updated_at)
    
    def test_profile_str_representation(self):
        """Test profile string representation."""
        profile = self.user.profile
        expected = f"Profile for {self.user.username}"
        self.assertEqual(str(profile), expected)
    
    def test_profile_update(self):
        """Test profile update."""
        profile = self.user.profile
        profile.job_title = 'Software Developer'
        profile.company = 'Tech Corp'
        profile.save()
        
        profile.refresh_from_db()
        self.assertEqual(profile.job_title, 'Software Developer')
        self.assertEqual(profile.company, 'Tech Corp')


class UserSessionModelTest(TestCase):
    """Test cases for UserSession model."""
    
    def setUp(self):
        """Set up test data."""
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='TestPass123!'
        )
    
    def test_session_creation(self):
        """Test session creation."""
        session = UserSession.objects.create(
            user=self.user,
            session_key='test_session_key',
            ip_address='127.0.0.1',
            user_agent='Test Browser',
            expires_at=timezone.now() + timedelta(hours=1)
        )
        
        self.assertEqual(session.user, self.user)
        self.assertEqual(session.session_key, 'test_session_key')
        self.assertTrue(session.is_active)
    
    def test_session_str_representation(self):
        """Test session string representation."""
        session = UserSession.objects.create(
            user=self.user,
            session_key='test_session_key',
            ip_address='127.0.0.1'
        )
        
        expected = f"Session for {self.user.username} from 127.0.0.1"
        self.assertEqual(str(session), expected)
    
    def test_session_expiry(self):
        """Test session expiry logic."""
        # Create expired session
        expired_session = UserSession.objects.create(
            user=self.user,
            session_key='expired_session',
            ip_address='127.0.0.1',
            expires_at=timezone.now() - timedelta(hours=1)
        )
        
        # Create active session
        active_session = UserSession.objects.create(
            user=self.user,
            session_key='active_session',
            ip_address='127.0.0.1',
            expires_at=timezone.now() + timedelta(hours=1)
        )
        
        # Test manager methods
        active_sessions = UserSession.objects.active()
        expired_sessions = UserSession.objects.expired()
        
        self.assertIn(active_session, active_sessions)
        self.assertIn(expired_session, expired_sessions)


class UserFormsTest(TestCase):
    """Test cases for user forms."""
    
    def test_user_registration_form_valid(self):
        """Test valid user registration form."""
        form_data = {
            'username': 'newuser',
            'email': 'newuser@example.com',
            'first_name': 'New',
            'last_name': 'User',
            'password1': 'NewPass123!',
            'password2': 'NewPass123!'
        }
        
        form = UserRegistrationForm(data=form_data)
        self.assertTrue(form.is_valid())
    
    def test_user_registration_form_password_mismatch(self):
        """Test registration form with password mismatch."""
        form_data = {
            'username': 'newuser',
            'email': 'newuser@example.com',
            'password1': 'NewPass123!',
            'password2': 'DifferentPass123!'
        }
        
        form = UserRegistrationForm(data=form_data)
        self.assertFalse(form.is_valid())
        self.assertIn('password2', form.errors)
    
    def test_authentication_form_valid(self):
        """Test valid authentication form."""
        user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='TestPass123!'
        )
        
        form_data = {
            'username': 'test@example.com',  # Using email
            'password': 'TestPass123!'
        }
        
        form = CustomAuthenticationForm(data=form_data)
        self.assertTrue(form.is_valid())
    
    def test_profile_form_valid(self):
        """Test valid profile form."""
        user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='TestPass123!'
        )
        
        form_data = {
            'first_name': 'Updated',
            'last_name': 'Name',
            'email': 'updated@example.com',
            'phone_number': '+1234567890',
            'bio': 'This is my updated bio.'
        }
        
        form = UserProfileForm(data=form_data, instance=user)
        self.assertTrue(form.is_valid())
    
    def test_contact_form_valid(self):
        """Test valid contact form."""
        form_data = {
            'name': 'John Doe',
            'email': 'john@example.com',
            'subject': 'Test Subject',
            'message': 'This is a test message.'
        }
        
        form = ContactForm(data=form_data)
        self.assertTrue(form.is_valid())


class UserValidatorsTest(TestCase):
    """Test cases for custom validators."""
    
    def test_password_validator_valid(self):
        """Test password validator with valid password."""
        validator = CustomPasswordValidator()
        
        # Should not raise exception
        try:
            validator.validate('ValidPass123!')
        except ValidationError:
            self.fail('Valid password raised ValidationError')
    
    def test_password_validator_invalid(self):
        """Test password validator with invalid passwords."""
        validator = CustomPasswordValidator()
        
        invalid_passwords = [
            'short',  # Too short
            'nouppercase123!',  # No uppercase
            'NOLOWERCASE123!',  # No lowercase
            'NoDigits!',  # No digits
            'NoSpecialChars123',  # No special chars
            'password123!'  # Common pattern
        ]
        
        for password in invalid_passwords:
            with self.assertRaises(ValidationError):
                validator.validate(password)
    
    def test_username_validator_valid(self):
        """Test username validator with valid usernames."""
        validator = UsernameValidator()
        
        valid_usernames = ['user123', 'test_user', 'user.name', 'user-name']
        
        for username in valid_usernames:
            try:
                validator(username)
            except ValidationError:
                self.fail(f'Valid username {username} raised ValidationError')
    
    def test_username_validator_invalid(self):
        """Test username validator with invalid usernames."""
        validator = UsernameValidator()
        
        invalid_usernames = [
            'ab',  # Too short
            'user name',  # Contains space
            'user@name',  # Invalid character
            'admin',  # Reserved
        ]
        
        for username in invalid_usernames:
            with self.assertRaises(ValidationError):
                validator(username)
    
    def test_phone_validator_valid(self):
        """Test phone number validator with valid numbers."""
        validator = PhoneNumberValidator()
        
        valid_numbers = ['+1234567890', '1234567890', '+123456789012345']
        
        for number in valid_numbers:
            try:
                validator(number)
            except ValidationError:
                self.fail(f'Valid phone number {number} raised ValidationError')
    
    def test_disposable_email_validator(self):
        """Test disposable email validator."""
        validator = DisposableEmailValidator()
        
        # Valid email
        try:
            validator('user@gmail.com')
        except ValidationError:
            self.fail('Valid email raised ValidationError')
        
        # Disposable email
        with self.assertRaises(ValidationError):
            validator('user@10minutemail.com')


class UserUtilsTest(TestCase):
    """Test cases for utility functions."""
    
    def test_generate_username(self):
        """Test username generation."""
        username = generate_username('John', 'Doe')
        self.assertTrue(username.startswith('john.doe'))
        self.assertTrue(len(username) > len('john.doe'))
    
    def test_generate_secure_token(self):
        """Test secure token generation."""
        token = generate_secure_token()
        self.assertEqual(len(token), 64)  # Default length
        
        token_custom = generate_secure_token(32)
        self.assertEqual(len(token_custom), 64)  # Hex encoding doubles length
    
    def test_validate_password_strength(self):
        """Test password strength validation."""
        weak_password = 'weak'
        strong_password = 'StrongPass123!'
        
        weak_result = validate_password_strength(weak_password)
        strong_result = validate_password_strength(strong_password)
        
        self.assertFalse(weak_result['is_strong'])
        self.assertTrue(strong_result['is_strong'])
        self.assertGreater(strong_result['score'], weak_result['score'])
    
    @patch('apps.accounts.utils.get_client_ip')
    def test_get_client_ip(self, mock_get_ip):
        """Test client IP extraction."""
        mock_request = Mock()
        mock_request.META = {'REMOTE_ADDR': '127.0.0.1'}
        
        mock_get_ip.return_value = '127.0.0.1'
        ip = get_client_ip(mock_request)
        
        self.assertEqual(ip, '127.0.0.1')


class UserViewsTest(TestCase):
    """Test cases for user views."""
    
    def setUp(self):
        """Set up test data."""
        self.client = Client()
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='TestPass123!'
        )
    
    def test_login_view_get(self):
        """Test login view GET request."""
        response = self.client.get(reverse('accounts:login'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'login')
    
    def test_login_view_post_valid(self):
        """Test login view with valid credentials."""
        response = self.client.post(reverse('accounts:login'), {
            'username': 'test@example.com',
            'password': 'TestPass123!'
        })
        
        # Should redirect after successful login
        self.assertEqual(response.status_code, 302)
    
    def test_login_view_post_invalid(self):
        """Test login view with invalid credentials."""
        response = self.client.post(reverse('accounts:login'), {
            'username': 'test@example.com',
            'password': 'WrongPassword'
        })
        
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'error')
    
    def test_register_view_get(self):
        """Test register view GET request."""
        response = self.client.get(reverse('accounts:register'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'register')
    
    def test_register_view_post_valid(self):
        """Test register view with valid data."""
        response = self.client.post(reverse('accounts:register'), {
            'username': 'newuser',
            'email': 'newuser@example.com',
            'first_name': 'New',
            'last_name': 'User',
            'password1': 'NewPass123!',
            'password2': 'NewPass123!'
        })
        
        # Should redirect after successful registration
        self.assertEqual(response.status_code, 302)
        self.assertTrue(User.objects.filter(username='newuser').exists())
    
    def test_dashboard_view_authenticated(self):
        """Test dashboard view for authenticated user."""
        self.client.login(username='test@example.com', password='TestPass123!')
        response = self.client.get(reverse('accounts:dashboard'))
        
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'dashboard')
    
    def test_dashboard_view_anonymous(self):
        """Test dashboard view for anonymous user."""
        response = self.client.get(reverse('accounts:dashboard'))
        
        # Should redirect to login
        self.assertEqual(response.status_code, 302)
    
    def test_profile_view_authenticated(self):
        """Test profile view for authenticated user."""
        self.client.login(username='test@example.com', password='TestPass123!')
        response = self.client.get(reverse('accounts:profile'))
        
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'profile')


class UserAPITest(APITestCase):
    """Test cases for user API endpoints."""
    
    def setUp(self):
        """Set up test data."""
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='TestPass123!'
        )
    
    def test_user_profile_api_authenticated(self):
        """Test user profile API for authenticated user."""
        self.client.force_authenticate(user=self.user)
        response = self.client.get(reverse('accounts:api-profile'))
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['username'], 'testuser')
    
    def test_user_profile_api_anonymous(self):
        """Test user profile API for anonymous user."""
        response = self.client.get(reverse('accounts:api-profile'))
        
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
    
    def test_user_sessions_api(self):
        """Test user sessions API."""
        # Create a session
        UserSession.objects.create(
            user=self.user,
            session_key='test_session',
            ip_address='127.0.0.1'
        )
        
        self.client.force_authenticate(user=self.user)
        response = self.client.get(reverse('accounts:api-sessions'))
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)


class UserSerializersTest(TestCase):
    """Test cases for user serializers."""
    
    def setUp(self):
        """Set up test data."""
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='TestPass123!'
        )
    
    def test_user_serializer(self):
        """Test user serializer."""
        serializer = UserSerializer(instance=self.user)
        data = serializer.data
        
        self.assertEqual(data['username'], 'testuser')
        self.assertEqual(data['email'], 'test@example.com')
        self.assertNotIn('password', data)  # Password should not be serialized
    
    def test_user_registration_serializer_valid(self):
        """Test user registration serializer with valid data."""
        data = {
            'username': 'newuser',
            'email': 'newuser@example.com',
            'password': 'NewPass123!',
            'password_confirm': 'NewPass123!',
            'first_name': 'New',
            'last_name': 'User'
        }
        
        serializer = UserRegistrationSerializer(data=data)
        self.assertTrue(serializer.is_valid())
    
    def test_user_registration_serializer_password_mismatch(self):
        """Test registration serializer with password mismatch."""
        data = {
            'username': 'newuser',
            'email': 'newuser@example.com',
            'password': 'NewPass123!',
            'password_confirm': 'DifferentPass123!'
        }
        
        serializer = UserRegistrationSerializer(data=data)
        self.assertFalse(serializer.is_valid())
        self.assertIn('password_confirm', serializer.errors)
    
    def test_login_serializer_valid(self):
        """Test login serializer with valid credentials."""
        data = {
            'username': 'test@example.com',
            'password': 'TestPass123!'
        }
        
        serializer = LoginSerializer(data=data)
        self.assertTrue(serializer.is_valid())
    
    def test_user_profile_serializer(self):
        """Test user profile serializer."""
        serializer = UserProfileSerializer(instance=self.user.profile)
        data = serializer.data
        
        self.assertIn('job_title', data)
        self.assertIn('company', data)
        self.assertIn('created_at', data)


class UserSignalsTest(TestCase):
    """Test cases for user signals."""
    
    def test_user_profile_creation_signal(self):
        """Test that user profile is created automatically."""
        user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='TestPass123!'
        )
        
        # Profile should be created automatically
        self.assertTrue(UserProfile.objects.filter(user=user).exists())
    
    @patch('apps.accounts.signals.send_welcome_email')
    def test_welcome_email_signal(self, mock_send_email):
        """Test welcome email signal."""
        User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='TestPass123!'
        )
        
        # Welcome email should be sent
        mock_send_email.assert_called_once()


class UserIntegrationTest(TestCase):
    """Integration tests for user functionality."""
    
    def test_complete_user_registration_flow(self):
        """Test complete user registration and login flow."""
        # Register user
        response = self.client.post(reverse('accounts:register'), {
            'username': 'newuser',
            'email': 'newuser@example.com',
            'first_name': 'New',
            'last_name': 'User',
            'password1': 'NewPass123!',
            'password2': 'NewPass123!'
        })
        
        # Should redirect after registration
        self.assertEqual(response.status_code, 302)
        
        # User should exist
        user = User.objects.get(username='newuser')
        self.assertEqual(user.email, 'newuser@example.com')
        
        # Profile should be created
        self.assertTrue(hasattr(user, 'profile'))
        
        # Login with new user
        login_response = self.client.post(reverse('accounts:login'), {
            'username': 'newuser@example.com',
            'password': 'NewPass123!'
        })
        
        # Should redirect after login
        self.assertEqual(login_response.status_code, 302)
        
        # Access dashboard
        dashboard_response = self.client.get(reverse('accounts:dashboard'))
        self.assertEqual(dashboard_response.status_code, 200)
    
    def test_password_change_flow(self):
        """Test password change flow."""
        user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='OldPass123!'
        )
        
        # Login
        self.client.login(username='test@example.com', password='OldPass123!')
        
        # Change password
        response = self.client.post(reverse('accounts:password_change'), {
            'old_password': 'OldPass123!',
            'new_password1': 'NewPass123!',
            'new_password2': 'NewPass123!'
        })
        
        # Should redirect after password change
        self.assertEqual(response.status_code, 302)
        
        # Verify password changed
        user.refresh_from_db()
        self.assertTrue(user.check_password('NewPass123!'))
        self.assertFalse(user.check_password('OldPass123!'))
    
    def test_session_management(self):
        """Test user session management."""
        user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='TestPass123!'
        )
        
        # Login should create session
        self.client.login(username='test@example.com', password='TestPass123!')
        
        # Check if session was created (this would depend on signal implementation)
        # For now, just verify user can access protected views
        response = self.client.get(reverse('accounts:dashboard'))
        self.assertEqual(response.status_code, 200)
        
        # Logout
        self.client.logout()
        
        # Should not be able to access protected views
        response = self.client.get(reverse('accounts:dashboard'))
        self.assertEqual(response.status_code, 302)  # Redirect to login