from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth import get_user_model
from bazi.models import Person
from accounts.models import UserProfile
from unittest.mock import patch, MagicMock
from rest_framework import status
from django.utils import timezone
from datetime import date, time, datetime, timedelta
import json

User = get_user_model()

class NumberAnalysisTests(TestCase):
    """Test suite for the number_analysis endpoint."""
    
    def setUp(self):
        """Set up test data."""
        # Create test users
        self.regular_user = User.objects.create_user(
            phone='1234567890',
            email='regular@example.com', 
            password='testpassword123'
        )
        
        self.regen_user = User.objects.create_user(
            phone='2345678901',
            email='regen@example.com', 
            password='testpassword123'
        )
        
        self.staff_user = User.objects.create_user(
            phone='3456789012',
            email='staff@example.com', 
            password='testpassword123',
            is_staff=True
        )
        
        # Update user profiles (automatically created by signals)
        self.regular_user.profile.can_regenerate_ai = False
        self.regular_user.profile.save()
        
        self.regen_user.profile.can_regenerate_ai = True
        self.regen_user.profile.save()
        
        # Create test persons
        self.person_regular = Person.objects.create(
            name='Regular User Person',
            gender='M',
            birth_date=date(1990, 1, 1),
            birth_time=time(12, 0),
            created_by=self.regular_user,
            twin_type=0,
            father_dob=date(1960, 1, 1),
            mother_dob=date(1965, 1, 1)
        )
        
        self.person_regen = Person.objects.create(
            name='Regen User Person',
            gender='F',
            birth_date=date(1991, 2, 2),
            birth_time=time(13, 0),
            created_by=self.regen_user,
            twin_type=0
        )
        
        self.person_staff = Person.objects.create(
            name='Staff User Person',
            gender='N',
            birth_date=date(1992, 3, 3),
            birth_time=time(14, 0),
            created_by=self.staff_user,
            twin_type=0
        )
        
        self.person_public = Person.objects.create(
            name='Public Person',
            gender='M',
            birth_date=date(1993, 4, 4),
            birth_time=time(15, 0),
            created_by=None,
            twin_type=0
        )
        
        # Set up the client
        self.client = Client()
        
        # Mock analysis data
        self.mock_analysis_data = {
            "number_analysis": {
                "personal_number_meaning": {
                    "title": "个人数字意义",
                    "content": "您的个人数字是4，代表成功和稳定。"
                },
                "character_strengths": {
                    "title": "性格优势",
                    "content": "作为数字4的人，您具有出色的组织能力和严谨的逻辑思维。"
                }
            },
            "provider": "test_provider",
            "model": "test_model",
            "prompt": "Test prompt",
            "think": "Test thinking process"
        }
        
        # Add analysis to one person
        self.person_regular.number_ai_analysis = self.mock_analysis_data
        self.person_regular.number_analysis_status = 'completed'
        self.person_regular.number_analysis_timestamp = timezone.now()
        self.person_regular.save()
        
        # URL for testing
        self.url_template = '/api/number/{}/analysis/'
    
    # ----- Normal Parameter Tests -----
    
    def test_get_existing_analysis(self):
        """Test GET request to retrieve existing analysis."""
        url = self.url_template.format(self.person_regular.id)
        self.client.login(phone='1234567890', password='testpassword123')
        
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['status'], 'completed')
        self.assertEqual(response.data['analysis'], self.mock_analysis_data)
    
    def test_get_nonexistent_analysis(self):
        """Test GET request for person without analysis."""
        url = self.url_template.format(self.person_regen.id)
        self.client.login(phone='2345678901', password='testpassword123')
        
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['status'], 'not_found')
    
    @patch('api.views.analyze_number')
    def test_post_generate_analysis(self, mock_analyze):
        """Test POST request to generate a new analysis."""
        url = self.url_template.format(self.person_regen.id)
        self.client.login(phone='2345678901', password='testpassword123')
        
        # Configure the mock to return our mock analysis data
        mock_analyze.return_value = self.mock_analysis_data
        
        response = self.client.post(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['status'], 'completed')
        self.assertEqual(response.data['analysis'], self.mock_analysis_data)
        mock_analyze.assert_called_once()
    
    @patch('api.views.analyze_number')
    def test_post_with_provider_model_params(self, mock_analyze):
        """Test POST request with provider and model parameters."""
        url = self.url_template.format(self.person_regen.id)
        self.client.login(phone='2345678901', password='testpassword123')
        
        # Configure the mock to return our mock analysis data
        mock_analyze.return_value = self.mock_analysis_data
        
        # Use JSON content type
        response = self.client.post(
            url, 
            data={'provider': 'custom_provider', 'model': 'custom_model'},
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        # Check that analyze_number was called with the correct parameters
        mock_analyze.assert_called_once()
        args, kwargs = mock_analyze.call_args
        self.assertEqual(args[0], self.person_regen)  # First arg should be person
        self.assertEqual(args[1], 'custom_model')     # Second arg should be model
        self.assertEqual(args[2], 'custom_provider')  # Third arg should be provider
    
    # ----- Error Parameter Tests -----
    
    def test_nonexistent_person(self):
        """Test request for a non-existent person."""
        url = self.url_template.format(9999)  # Assuming ID 9999 doesn't exist
        self.client.login(phone='1234567890', password='testpassword123')
        
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
        self.assertEqual(response.data['error'], 'Person not found')
    
    @patch('api.views.analyze_number')
    def test_analysis_failure(self, mock_analyze):
        """Test handling of analysis generation failure."""
        url = self.url_template.format(self.person_regen.id)
        self.client.login(phone='2345678901', password='testpassword123')
        
        # Configure the mock to raise an exception
        mock_analyze.side_effect = Exception("Analysis generation failed")
        
        response = self.client.post(url)
        
        self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
        self.assertIn('error', response.data)
        self.assertIn('detail', response.data)
    
    # ----- Permission and Authentication Tests -----
    
    @patch('api.views.analyze_number')
    def test_regenerate_without_authentication(self, mock_analyze):
        """Test regeneration attempt without authentication."""
        url = self.url_template.format(self.person_regular.id)
        
        # Configure the mock to return our mock analysis data
        mock_analyze.return_value = self.mock_analysis_data
        
        response = self.client.post(
            url, 
            data={'regenerate': True},
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertIn('error', response.data)
        mock_analyze.assert_not_called()
    
    @patch('api.views.analyze_number')
    def test_regenerate_without_permission(self, mock_analyze):
        """Test regeneration attempt by user without regeneration permission."""
        url = self.url_template.format(self.person_regular.id)
        self.client.login(phone='1234567890', password='testpassword123')
        
        # Configure the mock to return our mock analysis data
        mock_analyze.return_value = self.mock_analysis_data
        
        response = self.client.post(
            url, 
            data={'regenerate': True},
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertIn('error', response.data)
        mock_analyze.assert_not_called()
    
    @patch('api.views.analyze_number')
    def test_regenerate_with_permission(self, mock_analyze):
        """Test regeneration by user with permission."""
        url = self.url_template.format(self.person_regen.id)
        self.client.login(phone='2345678901', password='testpassword123')
        
        # Configure the mock to return our mock analysis data
        mock_analyze.return_value = self.mock_analysis_data
        
        response = self.client.post(
            url, 
            data={'regenerate': True},
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        mock_analyze.assert_called_once()
    
    @patch('api.views.analyze_number')
    def test_regenerate_by_staff(self, mock_analyze):
        """Test regeneration by staff user (should override permission check)."""
        url = self.url_template.format(self.person_staff.id)
        self.client.login(phone='3456789012', password='testpassword123')
        
        # Configure the mock to return our mock analysis data
        mock_analyze.return_value = self.mock_analysis_data
        
        response = self.client.post(
            url, 
            data={'regenerate': True},
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        mock_analyze.assert_called_once()
    
    def test_user_accessing_other_users_person(self):
        """Test user attempting to access another user's person."""
        url = self.url_template.format(self.person_staff.id)
        self.client.login(phone='1234567890', password='testpassword123')
        
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
        self.assertIn('error', response.data)
    
    def test_anonymous_access_to_public_person(self):
        """Test anonymous access to a public person."""
        url = self.url_template.format(self.person_public.id)
        
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
    
    # ----- Edge Cases -----
    
    def test_boolean_regenerate_param(self):
        """Test handling of boolean regenerate parameter."""
        url = self.url_template.format(self.person_regen.id)
        self.client.login(phone='2345678901', password='testpassword123')
        
        with patch('api.views.analyze_number') as mock_analyze:
            mock_analyze.return_value = self.mock_analysis_data
            
            # Test with boolean true
            response = self.client.post(
                url, 
                data={'regenerate': True},
                content_type='application/json'
            )
            
            self.assertEqual(response.status_code, status.HTTP_200_OK)
            mock_analyze.assert_called_once()
    
    def test_various_regenerate_string_values(self):
        """Test handling of different string values for regenerate parameter."""
        url = self.url_template.format(self.person_regen.id)
        self.client.login(phone='2345678901', password='testpassword123')
        
        with patch('api.views.analyze_number') as mock_analyze:
            mock_analyze.return_value = self.mock_analysis_data
            
            # Test with string 'true'
            response = self.client.post(
                url, 
                data={'regenerate': 'true'},
                content_type='application/json'
            )
            
            self.assertEqual(response.status_code, status.HTTP_200_OK)
            mock_analyze.assert_called_once()
            mock_analyze.reset_mock()
            
            # Test with 'yes'
            response = self.client.post(
                url, 
                data={'regenerate': 'yes'},
                content_type='application/json'
            )
            
            self.assertEqual(response.status_code, status.HTTP_200_OK)
            mock_analyze.assert_called_once()
            mock_analyze.reset_mock()
            
            # Test with '1'
            response = self.client.post(
                url, 
                data={'regenerate': '1'},
                content_type='application/json'
            )
            
            self.assertEqual(response.status_code, status.HTTP_200_OK)
            mock_analyze.assert_called_once()
            mock_analyze.reset_mock()
            
            # Test with 't'
            response = self.client.post(
                url, 
                data={'regenerate': 't'},
                content_type='application/json'
            )
            
            self.assertEqual(response.status_code, status.HTTP_200_OK)
            mock_analyze.assert_called_once()
            
    @patch('api.views.analyze_number')
    def test_empty_results_handling(self, mock_analyze):
        """Test handling of empty analysis results."""
        url = self.url_template.format(self.person_regen.id)
        self.client.login(phone='2345678901', password='testpassword123')
        
        # Configure the mock to return empty results
        mock_analyze.return_value = {}
        
        response = self.client.post(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['analysis'], {})
        
    def test_person_without_created_by(self):
        """Test access to a person without a created_by field."""
        url = self.url_template.format(self.person_public.id)
        
        # Try access with different users
        self.client.login(phone='1234567890', password='testpassword123')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        self.client.login(phone='2345678901', password='testpassword123')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        self.client.login(phone='3456789012', password='testpassword123')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # Try with anonymous user
        self.client.logout()
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
    def test_staff_access_to_any_person(self):
        """Test that staff can access any person."""
        # Staff accessing regular user's person
        url = self.url_template.format(self.person_regular.id)
        self.client.login(phone='3456789012', password='testpassword123')
        
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # Staff accessing regen user's person
        url = self.url_template.format(self.person_regen.id)
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
