from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth import get_user_model
from django.utils import timezone
from liuyao.models import liuyao
import json

User = get_user_model()


class LiuyaoCalculatorPostAPITestCase(TestCase):
    """Test cases for POST /api/liuyao/calc/ endpoint."""

    def setUp(self):
        """Set up test data."""
        self.client = APIClient()
        self.url = '/api/liuyao/calc/'
        
        # Create test user
        self.user = User.objects.create_user(
            phone='1234567890',
            email='test@example.com',
            first_name='Test',
            last_name='User'
        )
        
        # Test data for Liuyao calculation
        self.valid_data = {
            'question': 'Test question for POST endpoint',
            'usecur': False,
            'year': 2025,
            'month': 3,
            'day': 8,
            'time': '15:30',
            'y1': '1',
            'y2': '0',
            'y3': '1',
            'y4': '000',
            'y5': '1',
            'y6': '0'
        }

    def get_jwt_token(self, user):
        """Generate JWT token for user."""
        refresh = RefreshToken.for_user(user)
        return str(refresh.access_token)

    def test_post_unauthenticated_user_calculation_only(self):
        """Test POST request without authentication should only perform calculation."""
        initial_count = liuyao.objects.count()
        
        response = self.client.post(
            self.url,
            data=json.dumps(self.valid_data),
            content_type='application/json'
        )
        
        # Without middleware, unauthenticated users get 200 (calculation only, no saving)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        
        # Check response structure - calculation fields should be present
        response_data = response.json()
        self.assertIn('gua', response_data)
        self.assertIn('question', response_data)
        self.assertIn('yao', response_data)
        
        # Verify record was NOT saved (no middleware to create temp user)
        self.assertEqual(liuyao.objects.count(), initial_count)
        
        # Verify save-related fields are NOT in response (no record was saved)
        self.assertNotIn('id', response_data)
        self.assertNotIn('created_by', response_data)
        self.assertNotIn('uuid', response_data)
        self.assertNotIn('created_at', response_data)
        self.assertNotIn('updated_at', response_data)
        # Message may still be present indicating calculation was done but not saved

    def test_post_authenticated_user_calculation_and_save(self):
        """Test POST request with authentication calculates and saves record."""
        token = self.get_jwt_token(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
        
        initial_count = liuyao.objects.count()
        
        response = self.client.post(
            self.url,
            data=json.dumps(self.valid_data),
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # Check response structure
        response_data = response.json()
        self.assertIn('gua', response_data)
        self.assertIn('question', response_data)
        self.assertIn('yao', response_data)
        self.assertIn('id', response_data)
        self.assertIn('created_by', response_data)
        self.assertIn('message', response_data)
        
        # Verify record was saved
        self.assertEqual(liuyao.objects.count(), initial_count + 1)
        
        # Verify the saved record
        saved_record = liuyao.objects.get(id=response_data['id'])
        self.assertEqual(saved_record.user, self.user)
        self.assertEqual(saved_record.question, self.valid_data['question'])
        self.assertEqual(saved_record.y1, self.valid_data['y1'])
        self.assertEqual(saved_record.y2, self.valid_data['y2'])
        self.assertEqual(saved_record.y3, self.valid_data['y3'])
        self.assertEqual(saved_record.y4, self.valid_data['y4'])
        self.assertEqual(saved_record.y5, self.valid_data['y5'])
        self.assertEqual(saved_record.y6, self.valid_data['y6'])

    def test_post_correct_data_structure_saved(self):
        """Test that the saved record has the correct data structure (bug fix verification)."""
        token = self.get_jwt_token(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
        
        response = self.client.post(
            self.url,
            data=json.dumps(self.valid_data),
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # Get the saved record
        saved_record = liuyao.objects.get(id=response.json()['id'])
        
        # Verify the data field has the correct structure
        # It should contain the inner 'gua' object content, not the full API response
        expected_keys = ['question', 'bz', 'god6', 'ly', 'rel']
        actual_keys = list(saved_record.data.keys())
        
        for key in expected_keys:
            self.assertIn(key, actual_keys, f"Missing expected key '{key}' in saved data")
        
        # Verify it does NOT have the outer wrapper keys
        unexpected_keys = ['gua', 'yao']  # These should NOT be in the saved data
        for key in unexpected_keys:
            self.assertNotIn(key, actual_keys, f"Unexpected outer wrapper key '{key}' found in saved data")
        
        # Verify specific data structure
        self.assertIsInstance(saved_record.data['bz'], dict)
        self.assertIn('hour', saved_record.data['bz'])
        self.assertIn('day', saved_record.data['bz'])
        self.assertIn('month', saved_record.data['bz'])
        self.assertIn('year', saved_record.data['bz'])
        self.assertIn('date', saved_record.data['bz'])
        self.assertIn('empty', saved_record.data['bz'])
        
        self.assertIsInstance(saved_record.data['ly'], dict)
        self.assertIn('ogua', saved_record.data['ly'])

    def test_post_saved_record_compatible_with_recalculate_function(self):
        """Test that saved records are compatible with recalculate_liuyao_data function."""
        from iching.utils.liuyao import recalculate_liuyao_data
        from django.forms.models import model_to_dict
        
        token = self.get_jwt_token(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
        
        response = self.client.post(
            self.url,
            data=json.dumps(self.valid_data),
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # Get the saved record
        saved_record = liuyao.objects.get(id=response.json()['id'])
        
        # Test that recalculate_liuyao_data can process it without errors
        try:
            data = model_to_dict(saved_record)
            result = recalculate_liuyao_data(data, saved_record.qdate)
            
            # Should not raise an exception
            self.assertIsInstance(result, dict)
            self.assertIn('data', result)
            
        except Exception as e:
            self.fail(f"recalculate_liuyao_data failed with saved record: {str(e)}")

    def test_post_invalid_data_returns_error(self):
        """Test POST request with invalid data returns appropriate error."""
        token = self.get_jwt_token(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
        
        invalid_data = self.valid_data.copy()
        invalid_data['y1'] = 'invalid'  # Invalid yao value
        
        response = self.client.post(
            self.url,
            data=json.dumps(invalid_data),
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('error', response.json())

    def test_post_missing_required_fields_returns_error(self):
        """Test POST request with missing required fields returns appropriate error."""
        token = self.get_jwt_token(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
        
        incomplete_data = {
            'question': 'Test question',
            # Missing other required fields
        }
        
        response = self.client.post(
            self.url,
            data=json.dumps(incomplete_data),
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn('error', response.json())

    def test_post_response_contains_correct_fields_for_authenticated_user(self):
        """Test that authenticated user response contains all expected fields."""
        token = self.get_jwt_token(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
        
        response = self.client.post(
            self.url,
            data=json.dumps(self.valid_data),
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        response_data = response.json()
        
        # Check all expected fields are present
        expected_fields = [
            'gua', 'question', 'yao',  # Calculation result fields
            'id', 'uuid', 'created_by', 'created_at', 'updated_at', 'message'  # Saved record fields
        ]
        
        for field in expected_fields:
            self.assertIn(field, response_data, f"Missing field '{field}' in authenticated user response")
        
        # Verify field types and values
        self.assertIsInstance(response_data['id'], int)
        self.assertEqual(response_data['created_by'], self.user.id)
        self.assertIn('saved', response_data['message'].lower())

    def test_post_response_contains_correct_fields_for_public_user(self):
        """Test that public user response contains calculation fields only (no saving without auth)."""
        response = self.client.post(
            self.url,
            data=json.dumps(self.valid_data),
            content_type='application/json'
        )
        
        # Without middleware, public users get 200 (calculation only, no saving)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        response_data = response.json()
        
        # Check calculation fields are present
        calculation_fields = ['gua', 'question', 'yao']
        for field in calculation_fields:
            self.assertIn(field, response_data, f"Missing calculation field '{field}' in public user response")
        
        # Check that save-related fields are NOT present (no middleware to save records)
        save_fields = ['id', 'uuid', 'created_by', 'created_at', 'updated_at']
        for field in save_fields:
            self.assertNotIn(field, response_data, f"Unexpected save field '{field}' in public user response (no saving without auth)")

    def test_post_date_handling(self):
        """Test that different date formats are handled correctly."""
        token = self.get_jwt_token(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
        
        # Test with current date flag
        current_date_data = self.valid_data.copy()
        current_date_data['usecur'] = True
        current_date_data.pop('year', None)
        current_date_data.pop('month', None)
        current_date_data.pop('day', None)
        current_date_data.pop('time', None)
        
        response = self.client.post(
            self.url,
            data=json.dumps(current_date_data),
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # Verify the saved record has a valid date
        saved_record = liuyao.objects.get(id=response.json()['id'])
        self.assertIsNotNone(saved_record.qdate)

    def test_post_uuid_generation(self):
        """Test that UUID is properly generated and assigned."""
        token = self.get_jwt_token(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
        
        response = self.client.post(
            self.url,
            data=json.dumps(self.valid_data),
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # Verify UUID in response and saved record
        response_data = response.json()
        self.assertIn('uuid', response_data)
        
        saved_record = liuyao.objects.get(id=response_data['id'])
        self.assertIsNotNone(saved_record.uuid)
        self.assertEqual(str(saved_record.uuid), response_data['uuid'])

    def test_post_multiple_records_same_user(self):
        """Test that multiple records can be created for the same user."""
        token = self.get_jwt_token(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
        
        initial_count = liuyao.objects.filter(user=self.user).count()
        
        # Create first record
        response1 = self.client.post(
            self.url,
            data=json.dumps(self.valid_data),
            content_type='application/json'
        )
        self.assertEqual(response1.status_code, status.HTTP_201_CREATED)
        
        # Create second record with different question
        second_data = self.valid_data.copy()
        second_data['question'] = 'Second test question'
        
        response2 = self.client.post(
            self.url,
            data=json.dumps(second_data),
            content_type='application/json'
        )
        self.assertEqual(response2.status_code, status.HTTP_201_CREATED)
        
        # Verify both records were created
        final_count = liuyao.objects.filter(user=self.user).count()
        self.assertEqual(final_count, initial_count + 2)
        
        # Verify they have different IDs
        self.assertNotEqual(response1.json()['id'], response2.json()['id']) 