from django.test import TestCase
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 liuyao.models import liuyao
from iching.utils.liuyao import recalculate_liuyao_data
from django.forms.models import model_to_dict
import json

User = get_user_model()


class LiuyaoDataStructureBugFixTestCase(TestCase):
    """
    Test case specifically for the data structure bug fix.
    
    This test ensures that the POST /api/liuyao/calc/ endpoint saves data
    in the correct format that is compatible with recalculate_liuyao_data function.
    
    Bug: Previously, the endpoint was saving the complete API response structure
    with keys ['gua', 'question', 'yao'], but the recalculate_liuyao_data function
    expects only the inner 'gua' object content with keys ['question', 'bz', 'god6', 'ly', 'rel'].
    
    Fix: Changed `data=result` to `data=result['gua']` in the POST method.
    """

    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.test_data = {
            'question': 'Bug fix verification test',
            '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_bug_fix_data_structure_saved_correctly(self):
        """
        Test that the bug fix ensures data is saved in the correct structure.
        
        This is the main test for the bug fix. It verifies that:
        1. The saved data has the correct keys (inner gua object content)
        2. The saved data does NOT have the outer wrapper keys
        3. The saved data is compatible with recalculate_liuyao_data function
        """
        token = self.get_jwt_token(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
        
        # Make POST request
        response = self.client.post(
            self.url,
            data=json.dumps(self.test_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'])
        
        # CRITICAL TEST: Verify the data structure is correct
        # The bug was that we were saving the full API response instead of just the 'gua' content
        
        # 1. Check that it has the CORRECT keys (inner gua object content)
        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"REGRESSION: Missing expected key '{key}' in saved data. "
                         f"This indicates the bug fix has been broken.")
        
        # 2. Check that it does NOT have the INCORRECT keys (outer wrapper)
        incorrect_keys = ['gua', 'yao']  # These were present in the buggy version
        for key in incorrect_keys:
            self.assertNotIn(key, actual_keys,
                           f"REGRESSION: Found incorrect outer wrapper key '{key}' in saved data. "
                           f"This indicates the bug has returned.")
        
        # 3. Verify the structure is deep enough (not just surface level)
        self.assertIsInstance(saved_record.data['bz'], dict, 
                            "BaZi data should be a dictionary")
        self.assertIn('hour', saved_record.data['bz'], 
                     "BaZi should contain hour pillar")
        self.assertIn('day', saved_record.data['bz'], 
                     "BaZi should contain day pillar")
        self.assertIn('month', saved_record.data['bz'], 
                     "BaZi should contain month pillar")
        self.assertIn('year', saved_record.data['bz'], 
                     "BaZi should contain year pillar")
        
        self.assertIsInstance(saved_record.data['ly'], dict, 
                            "Liuyao data should be a dictionary")
        self.assertIn('ogua', saved_record.data['ly'], 
                     "Liuyao should contain original gua")

    def test_bug_fix_compatibility_with_recalculate_function(self):
        """
        Test that the bug fix ensures compatibility with recalculate_liuyao_data function.
        
        This was the original error that led to discovering the bug:
        "Either date must be provided or item_data must contain BaZi data"
        
        The recalculate_liuyao_data function expects the data to have 'bz' key directly,
        not nested under 'gua'.
        """
        token = self.get_jwt_token(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
        
        # Create a record using the POST endpoint
        response = self.client.post(
            self.url,
            data=json.dumps(self.test_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'])
        
        # CRITICAL TEST: Verify that recalculate_liuyao_data can process the saved record
        # This was failing before the bug fix
        try:
            data = model_to_dict(saved_record)
            result = recalculate_liuyao_data(data, saved_record.qdate)
            
            # If we reach here, the function succeeded
            self.assertIsInstance(result, dict, 
                                "recalculate_liuyao_data should return a dictionary")
            self.assertIn('data', result, 
                         "recalculate_liuyao_data result should contain 'data' key")
            
        except ValueError as e:
            if "Either date must be provided or item_data must contain BaZi data" in str(e):
                self.fail(
                    "REGRESSION: The original bug has returned! "
                    "recalculate_liuyao_data cannot find BaZi data in the saved record. "
                    "This means the data structure is incorrect."
                )
            else:
                # Some other ValueError, re-raise it
                raise
        except Exception as e:
            self.fail(f"Unexpected error in recalculate_liuyao_data: {str(e)}")

    def test_bug_fix_api_response_still_contains_full_structure(self):
        """
        Test that the API response still contains the full structure for clients.
        
        The bug fix should only affect what gets SAVED to the database,
        not what gets RETURNED to the API client.
        """
        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.test_data),
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        response_data = response.json()
        
        # The API response should still contain the full structure
        self.assertIn('gua', response_data, 
                     "API response should still contain 'gua' for client compatibility")
        self.assertIn('question', response_data, 
                     "API response should still contain 'question' for client compatibility")
        self.assertIn('yao', response_data, 
                     "API response should still contain 'yao' for client compatibility")
        
        # And also the save-related fields for authenticated users
        self.assertIn('id', response_data, 
                     "API response should contain 'id' for authenticated users")
        self.assertIn('message', response_data, 
                     "API response should contain 'message' for authenticated users")

    def test_bug_fix_comparison_with_working_record(self):
        """
        Test that new records have the same data structure as existing working records.
        
        This ensures consistency across all records in the database.
        """
        token = self.get_jwt_token(self.user)
        self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {token}')
        
        # Create a new record using the fixed POST endpoint
        response = self.client.post(
            self.url,
            data=json.dumps(self.test_data),
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        
        # Get the new record
        new_record = liuyao.objects.get(id=response.json()['id'])
        
        # Check if there are any existing records to compare with
        existing_records = liuyao.objects.exclude(id=new_record.id)
        
        if existing_records.exists():
            # Compare with an existing record
            existing_record = existing_records.first()
            
            # Both should have the same top-level keys
            new_keys = set(new_record.data.keys())
            existing_keys = set(existing_record.data.keys())
            
            # They should have the same structure
            expected_keys = {'question', 'bz', 'god6', 'ly', 'rel'}
            
            self.assertTrue(expected_keys.issubset(new_keys),
                          f"New record missing expected keys. Has: {new_keys}")
            self.assertTrue(expected_keys.issubset(existing_keys),
                          f"Existing record missing expected keys. Has: {existing_keys}")
        
        # Regardless of existing records, verify the new record has correct structure
        expected_keys = ['question', 'bz', 'god6', 'ly', 'rel']
        actual_keys = list(new_record.data.keys())
        
        for key in expected_keys:
            self.assertIn(key, actual_keys,
                         f"New record missing expected key '{key}'") 