#!/usr/bin/env python
"""
Comprehensive unit tests for the new temporary user creation endpoint.
Tests creation without parameters, with parameters, security restrictions,
and temp user behaviors like logout, redirects, and migration.
"""
import os
import django
from django.test import TestCase, Client
from rest_framework.test import APIClient
from rest_framework import status
from django.urls import reverse
from django.contrib.auth import get_user_model
from rest_framework_simplejwt.tokens import UntypedToken
from rest_framework_simplejwt.exceptions import InvalidToken, TokenError
import json

# Setup Django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'iching.settings')
os.environ.setdefault('DJANGO_ENV', 'development')

from main.models import User
from accounts.models import UserProfile
from bazi.models import Person


class TempUserCreateEndpointTestCase(TestCase):
    """Base test case for temporary user creation endpoint tests"""
    
    def setUp(self):
        """Set up test data"""
        self.client = APIClient()
        
        # Generate unique phone for regular user
        class_name = self.__class__.__name__
        phone_suffix = str(hash(class_name))[-8:]
        self.test_phone = f'99{phone_suffix}'
        
        # Create a regular user for migration tests
        self.regular_user = User.objects.create_user(
            phone=self.test_phone,
            email=f'regular{phone_suffix}@example.com',
            password='testpass123',
            first_name='Regular',
            last_name='User'
        )
        
        # Valid test data
        self.valid_profile_data = {
            'name': 'Test User',
            'gender': 'M',
            'birth_date': '1990-01-01',
            'birth_time': '14:30:00',
            'birth_city': 'Beijing',
            'timezone': 'Asia/Shanghai'
        }


class TempUserCreateBasicTests(TempUserCreateEndpointTestCase):
    """Test basic functionality of temp user creation endpoint"""
    
    def test_create_temp_user_no_parameters(self):
        """Test creating temporary user without any parameters"""
        # Ensure no temp users exist initially
        temp_users_before = User.objects.filter(is_temporary_user=True).count()
        self.assertEqual(temp_users_before, 0)
        
        # Call the endpoint with empty data
        response = self.client.post(
            reverse('api:temp_user_create'),
            {},
            format='json'
        )
        
        # Check response
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        response_data = response.json()
        
        # Verify response structure
        required_fields = ['user_id', 'access', 'refresh', 'is_temporary', 'message', 'expires_in']
        for field in required_fields:
            self.assertIn(field, response_data, f"Missing field: {field}")
        
        # Verify response values
        self.assertTrue(response_data['is_temporary'])
        self.assertIsInstance(response_data['user_id'], int)
        self.assertEqual(response_data['message'], 'Temporary user created successfully')
        
        # Verify temp user was created in database
        temp_user = User.objects.get(id=response_data['user_id'])
        self.assertTrue(temp_user.is_temporary_user)
        self.assertTrue(temp_user.phone.startswith('temp_'))
        
        # Verify UserProfile was created
        profile = UserProfile.objects.filter(user=temp_user).first()
        self.assertIsNotNone(profile)
    
    def test_create_temp_user_with_valid_parameters(self):
        """Test creating temporary user with valid profile parameters"""
        response = self.client.post(
            reverse('api:temp_user_create'),
            self.valid_profile_data,
            format='json'
        )
        
        # Check response
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        response_data = response.json()
        
        # Verify temp user was created
        temp_user = User.objects.get(id=response_data['user_id'])
        self.assertTrue(temp_user.is_temporary_user)
        
        # Verify profile data was saved
        profile = UserProfile.objects.filter(user=temp_user).first()
        self.assertIsNotNone(profile)
        
        # Check that name was split into first/last name
        name_parts = self.valid_profile_data['name'].split(' ', 1)
        self.assertEqual(temp_user.first_name, name_parts[0])
        if len(name_parts) > 1:
            self.assertEqual(temp_user.last_name, name_parts[1])
    
    def test_reject_phone_parameter(self):
        """Test that providing phone parameter returns bad request"""
        invalid_data = {
            'name': 'Test User',
            'phone': '1234567890',  # Should be rejected
            'gender': 'M'
        }
        
        response = self.client.post(
            reverse('api:temp_user_create'),
            invalid_data,
            format='json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        response_data = response.json()
        self.assertIn('phone', str(response_data).lower())
    
    def test_reject_email_parameter(self):
        """Test that providing email parameter returns bad request"""
        invalid_data = {
            'name': 'Test User',
            'email': 'test@example.com',  # Should be rejected
            'gender': 'F'
        }
        
        response = self.client.post(
            reverse('api:temp_user_create'),
            invalid_data,
            format='json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        response_data = response.json()
        self.assertIn('email', str(response_data).lower())


class TempUserJWTTokenTests(TempUserCreateEndpointTestCase):
    """Test JWT token functionality for created temporary users"""
    
    def _create_temp_user_with_tokens(self):
        """Helper to create temp user and return tokens"""
        response = self.client.post(
            reverse('api:temp_user_create'),
            self.valid_profile_data,
            format='json'
        )
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        return response.json()
    
    def test_jwt_tokens_are_valid(self):
        """Test that returned JWT tokens are valid"""
        response_data = self._create_temp_user_with_tokens()
        
        access_token = response_data['access']
        refresh_token = response_data['refresh']
        
        # Test access token validity
        try:
            UntypedToken(access_token)
            access_valid = True
        except (InvalidToken, TokenError):
            access_valid = False
        
        # Test refresh token validity
        try:
            UntypedToken(refresh_token)
            refresh_valid = True
        except (InvalidToken, TokenError):
            refresh_valid = False
        
        self.assertTrue(access_valid, "Access token should be valid")
        self.assertTrue(refresh_valid, "Refresh token should be valid")
    
    def test_jwt_authentication_works(self):
        """Test using JWT tokens for authenticated requests"""
        response_data = self._create_temp_user_with_tokens()
        access_token = response_data['access']
        
        # Use access token for authenticated request
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {access_token}")
        
        # Test accessing user profile endpoint
        response = self.client.get(reverse('api:user_profile'))
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # Verify it returns the temp user's data
        profile_data = response.json()
        self.assertEqual(profile_data['id'], response_data['user_id'])
        self.assertTrue(profile_data['is_temporary_user'])


class TempUserNeverLogoutTests(TempUserCreateEndpointTestCase):
    """Test that temp users created by this endpoint never logout"""
    
    def test_temp_user_never_logs_out(self):
        """Test that temp users have permanent tokens that never expire"""
        response_data = self.client.post(
            reverse('api:temp_user_create'),
            {},
            format='json'
        ).json()
        
        temp_user = User.objects.get(id=response_data['user_id'])
        
        # Temp users should have no logout expiry
        self.assertTrue(temp_user.is_temporary_user)
        
        # Response should indicate permanent session
        self.assertIsNone(response_data['expires_in'], "Temporary users should have no expiry")
        self.assertTrue(response_data['is_permanent_session'], "Temporary users should have permanent sessions")
        
        # Test that the JWT tokens have very long expiry (permanent)
        access_token = response_data['access']
        refresh_token = response_data['refresh']
        
        # Decode tokens to check expiry times
        from rest_framework_simplejwt.tokens import UntypedToken
        from datetime import datetime, timezone as dt_timezone
        
        access_decoded = UntypedToken(access_token)
        refresh_decoded = UntypedToken(refresh_token)
        
        # Check that expiry is far in the future (more than 50 years from now)
        now = datetime.now(dt_timezone.utc)
        access_exp = datetime.fromtimestamp(access_decoded['exp'], dt_timezone.utc)
        refresh_exp = datetime.fromtimestamp(refresh_decoded['exp'], dt_timezone.utc)
        
        years_diff_access = (access_exp - now).days / 365
        years_diff_refresh = (refresh_exp - now).days / 365
        
        self.assertGreater(years_diff_access, 50, "Access token should expire far in the future (permanent)")
        self.assertGreater(years_diff_refresh, 50, "Refresh token should expire far in the future (permanent)")
        
        # Test that the user can make requests with these permanent tokens
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {access_token}")
        
        # Multiple requests should work without logout
        for i in range(5):
            response = self.client.get(reverse('api:user_profile'))
            self.assertEqual(response.status_code, status.HTTP_200_OK)
    
    def test_regular_users_get_normal_tokens(self):
        """Test that regular users still get normal (non-permanent) tokens"""
        from main.tokens import create_tokens_for_user
        
        # Create a regular user
        regular_user = User.objects.create_user(
            phone='9876543210',
            email='regular@example.com',
            password='testpass123',
            is_temporary_user=False
        )
        
        # Get tokens for regular user
        token_data = create_tokens_for_user(regular_user)
        
        # Regular users should get normal tokens with expiry
        self.assertIsNotNone(token_data['expires_in'], "Regular users should have token expiry")
        self.assertFalse(token_data['is_permanent'], "Regular users should not have permanent sessions")
        self.assertEqual(token_data['expires_in'], 86400, "Regular users should have 1-day token expiry")


class TempUserBehaviorTests(TempUserCreateEndpointTestCase):
    """Test temp user behavior like registration and data migration"""
    
    def _create_authenticated_temp_user(self):
        """Helper to create temp user and return authenticated client"""
        response_data = self.client.post(
            reverse('api:temp_user_create'),
            {},
            format='json'
        ).json()
        
        access_token = response_data['access']
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {access_token}")
        
        return response_data, User.objects.get(id=response_data['user_id'])
    
    def test_temp_user_can_access_temp_register(self):
        """Test that temp users can access temp registration endpoint"""
        response_data, temp_user = self._create_authenticated_temp_user()
        
        # Test temp registration endpoint access
        register_data = {
            'phone': '9876543210',
            'email': 'newuser@example.com',
            'password': 'newpassword123',
            'first_name': 'New',
            'last_name': 'User'
        }
        
        response = self.client.post(
            reverse('api:temp_register'),
            register_data,
            format='json'
        )
        
        # Should be able to register and migrate data
        self.assertEqual(response.status_code, status.HTTP_200_OK)
    
    def test_temp_user_data_creation_via_apis(self):
        """Test that temp users can create data via other APIs"""
        response_data, temp_user = self._create_authenticated_temp_user()
        
        # Test BaZi API with temp user
        bazi_data = {
            'name': 'Test Person',
            'gender': 'M',
            'birth_date': '1990-01-01',
            'birth_time': '12:00:00',
            'notes': 'Test notes'
        }
        
        bazi_response = self.client.post(
            reverse('api:bazi-list'),
            bazi_data,
            format='json'
        )
        
        self.assertEqual(bazi_response.status_code, status.HTTP_201_CREATED)
        
        # Verify BaZi record was created for temp user
        bazi_records = Person.objects.filter(created_by=temp_user)
        self.assertEqual(bazi_records.count(), 1)
        self.assertEqual(bazi_records.first().name, 'Test Person')


class TempUserSecurityTests(TempUserCreateEndpointTestCase):
    """Test security aspects of temp user creation"""
    
    def test_temp_users_are_isolated(self):
        """Test that different temp users cannot access each other's data"""
        # Create first temp user
        response1 = self.client.post(reverse('api:temp_user_create'), {'name': 'User 1'}, format='json')
        user1_data = response1.data
        
        # Create second temp user  
        response2 = self.client.post(reverse('api:temp_user_create'), {'name': 'User 2'}, format='json')
        user2_data = response2.data
        
        # Verify they are different users
        self.assertNotEqual(user1_data['user_id'], user2_data['user_id'])
        
        # Test that user1 cannot access user2's data
        self.client.credentials(HTTP_AUTHORIZATION=f"Bearer {user1_data['access']}")
        user1 = User.objects.get(id=user1_data['user_id'])
        user2 = User.objects.get(id=user2_data['user_id'])
        
        # Both users should only see their own data
        self.assertNotEqual(user1.id, user2.id)
        self.assertTrue(user1.is_temporary_user)
        self.assertTrue(user2.is_temporary_user)


class TempUserJWTToSessionTests(TempUserCreateEndpointTestCase):
    """Test JWT to session conversion functionality"""
    
    def test_jwt_to_session_conversion(self):
        """Test converting JWT token to Django session authentication"""
        # Create temporary user
        response = self.client.post(reverse('api:temp_user_create'), {'name': 'Test User'}, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        temp_user_data = response.data
        access_token = temp_user_data['access']
        
        # Test JWT-to-session conversion
        conversion_response = self.client.post('/api/user/jwt-to-session/', {
            'access_token': access_token
        }, format='json')
        
        self.assertEqual(conversion_response.status_code, status.HTTP_200_OK)
        self.assertTrue(conversion_response.data['success'])
        self.assertEqual(conversion_response.data['user_id'], temp_user_data['user_id'])
        self.assertTrue(conversion_response.data['is_temporary'])
        
        # Verify session was created
        self.assertIn('sessionid', self.client.cookies)
        
        # Test that user is now authenticated via session
        user = User.objects.get(id=temp_user_data['user_id'])
        self.assertTrue(user.is_temporary_user)
    
    def test_jwt_to_session_invalid_token(self):
        """Test JWT-to-session conversion with invalid token"""
        response = self.client.post('/api/user/jwt-to-session/', {
            'access_token': 'invalid_token'
        }, format='json')
        
        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
        self.assertIn('error', response.data)
    
    def test_jwt_to_session_missing_token(self):
        """Test JWT-to-session conversion without providing token"""
        response = self.client.post('/api/user/jwt-to-session/', {}, format='json')
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertEqual(response.data['error'], 'Access token is required')
    
    def test_complete_temp_user_web_flow(self):
        """Test complete temporary user flow for web applications"""
        # Step 1: Create temporary user (simulating JS call)
        response = self.client.post(reverse('api:temp_user_create'), {'name': 'Web User'}, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        temp_user_data = response.data
        access_token = temp_user_data['access']
        
        # Step 2: Convert JWT to session (simulating JS utility call)
        conversion_response = self.client.post('/api/user/jwt-to-session/', {
            'access_token': access_token
        }, format='json')
        self.assertEqual(conversion_response.status_code, status.HTTP_200_OK)
        
        # Step 3: User should now be able to use session-based endpoints
        # (This would be like calling /api/liuyao/calc/ after session conversion)
        
        # Verify the user is the same and properly authenticated
        user = User.objects.get(id=temp_user_data['user_id'])
        self.assertTrue(user.is_temporary_user)
        self.assertEqual(user.first_name, 'Web')  # Name gets split into first/last
        self.assertEqual(user.last_name, 'User')


if __name__ == '__main__':
    import unittest
    unittest.main() 