"""
Unit tests for enhanced person relations API endpoint.

Tests verify that the /api/bazi/person-relations/ endpoint correctly includes
simplified BaZi data for p1_data and p2_data in the expected format.
"""

from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
from datetime import date, time

from bazi.models import Person
from bazi.models_group import GroupRelation

User = get_user_model()


class PersonRelationsEnhancedAPITest(TestCase):
    """Test enhanced person relations API with simplified BaZi data."""

    def setUp(self):
        """Set up test data."""
        self.client = APIClient()
        self.user = User.objects.create_user(
            phone='13900000001',
            email='test@example.com',
            password='testpass123'
        )
        self.client.force_authenticate(user=self.user)
        
        # Create owner person (required for group relations)
        self.owner = Person.objects.create(
            name='Owner',
            gender='M',
            birth_date=date(1980, 1, 1),
            birth_time=time(12, 0),
            created_by=self.user,
            owner=True
        )
        self.owner.calculate_bazi()
        self.owner.save()
        
        # Create two persons for group relation
        self.person1 = Person.objects.create(
            name='Person One',
            gender='F',
            birth_date=date(1985, 5, 15),
            birth_time=time(8, 30),
            created_by=self.user,
            owner=False
        )
        self.person1.calculate_bazi()
        self.person1.save()
        
        self.person2 = Person.objects.create(
            name='Person Two',
            gender='M',
            birth_date=date(1990, 10, 20),
            birth_time=time(14, 45),
            created_by=self.user,
            owner=False
        )
        self.person2.calculate_bazi()
        self.person2.save()
        
        # Create a group relation
        self.group_relation = GroupRelation.objects.create(
            owner_user=self.user,
            person1=self.person1,
            person2=self.person2,
            relation_type='sanhe',
            by=[
                ["o", "d", "e", 2],
                ["p1", "d", "e", 6],
                ["p2", "d", "e", 10]
            ]
        )
        
        # Set user state to completed so API returns results
        self.user.group_relations_state = 'completed'
        self.user.save()

    def test_person_relations_includes_simplified_bazi_data(self):
        """Test that API response includes simplified BaZi data for p1_data and p2_data."""
        url = reverse('api:bazi-person-relations')
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        data = response.json()
        
        # Verify basic response structure
        self.assertEqual(data['status'], 'ready')
        self.assertIn('results', data)
        self.assertGreater(len(data['results']), 0)
        
        # Get first relation
        relation = data['results'][0]
        
        # Verify relation has required fields
        self.assertIn('t', relation)
        self.assertIn('p1', relation)
        self.assertIn('p2', relation)
        self.assertIn('by', relation)
        self.assertIn('p1_data', relation)
        self.assertIn('p2_data', relation)
        
        # Test p1_data structure
        p1_data = relation['p1_data']
        self.assertIn('name', p1_data)
        self.assertIn('gender', p1_data)
        self.assertIn('birth_date', p1_data)
        self.assertIn('birth_time', p1_data)
        self.assertIn('bazi', p1_data)
        
        # Test p2_data structure
        p2_data = relation['p2_data']
        self.assertIn('name', p2_data)
        self.assertIn('gender', p2_data)
        self.assertIn('birth_date', p2_data)
        self.assertIn('birth_time', p2_data)
        self.assertIn('bazi', p2_data)

    def test_bazi_data_has_correct_format(self):
        """Test that BaZi data follows the {yg, ye, mg, me, dg, de, hg, he} format."""
        url = reverse('api:bazi-person-relations')
        response = self.client.get(url)
        
        data = response.json()
        relation = data['results'][0]
        
        # Test p1_data BaZi format
        p1_bazi = relation['p1_data']['bazi']
        expected_keys = ['yg', 'ye', 'mg', 'me', 'dg', 'de', 'hg', 'he']
        
        for key in expected_keys:
            self.assertIn(key, p1_bazi, f"Missing key '{key}' in p1_data bazi")
            self.assertIsInstance(p1_bazi[key], int, f"Key '{key}' should be integer")
            self.assertGreaterEqual(p1_bazi[key], 0, f"Key '{key}' should be non-negative")
        
        # Test p2_data BaZi format
        p2_bazi = relation['p2_data']['bazi']
        
        for key in expected_keys:
            self.assertIn(key, p2_bazi, f"Missing key '{key}' in p2_data bazi")
            self.assertIsInstance(p2_bazi[key], int, f"Key '{key}' should be integer")
            self.assertGreaterEqual(p2_bazi[key], 0, f"Key '{key}' should be non-negative")

    def test_bazi_indices_within_valid_ranges(self):
        """Test that BaZi god/earth indices are within valid ranges."""
        url = reverse('api:bazi-person-relations')
        response = self.client.get(url)
        
        data = response.json()
        relation = data['results'][0]
        
        p1_bazi = relation['p1_data']['bazi']
        p2_bazi = relation['p2_data']['bazi']
        
        # God indices should be 0-9 (10 heavenly stems)
        god_keys = ['yg', 'mg', 'dg', 'hg']
        for key in god_keys:
            self.assertGreaterEqual(p1_bazi[key], 0, f"P1 {key} should be >= 0")
            self.assertLessEqual(p1_bazi[key], 9, f"P1 {key} should be <= 9")
            self.assertGreaterEqual(p2_bazi[key], 0, f"P2 {key} should be >= 0")
            self.assertLessEqual(p2_bazi[key], 9, f"P2 {key} should be <= 9")
        
        # Earth indices should be 0-11 (12 earthly branches)
        earth_keys = ['ye', 'me', 'de', 'he']
        for key in earth_keys:
            self.assertGreaterEqual(p1_bazi[key], 0, f"P1 {key} should be >= 0")
            self.assertLessEqual(p1_bazi[key], 11, f"P1 {key} should be <= 11")
            self.assertGreaterEqual(p2_bazi[key], 0, f"P2 {key} should be >= 0")
            self.assertLessEqual(p2_bazi[key], 11, f"P2 {key} should be <= 11")

    def test_person_data_matches_database_records(self):
        """Test that person data in API response matches actual database records."""
        url = reverse('api:bazi-person-relations')
        response = self.client.get(url)
        
        data = response.json()
        relation = data['results'][0]
        
        # Get person IDs from relation
        p1_id = relation['p1']
        p2_id = relation['p2']
        
        # Get actual persons from database
        person1_db = Person.objects.get(id=p1_id)
        person2_db = Person.objects.get(id=p2_id)
        
        # Verify p1_data matches database
        p1_data = relation['p1_data']
        self.assertEqual(p1_data['name'], person1_db.name)
        self.assertEqual(p1_data['gender'], person1_db.gender)
        self.assertEqual(p1_data['birth_date'], str(person1_db.birth_date))
        self.assertEqual(p1_data['birth_time'], str(person1_db.birth_time))
        
        # Verify p2_data matches database
        p2_data = relation['p2_data']
        self.assertEqual(p2_data['name'], person2_db.name)
        self.assertEqual(p2_data['gender'], person2_db.gender)
        self.assertEqual(p2_data['birth_date'], str(person2_db.birth_date))
        self.assertEqual(p2_data['birth_time'], str(person2_db.birth_time))

    def test_bazi_data_matches_calculated_results(self):
        """Test that BaZi data in response matches calculated BaZi results."""
        url = reverse('api:bazi-person-relations')
        response = self.client.get(url)
        
        data = response.json()
        relation = data['results'][0]
        
        # Get person IDs and fetch from database
        p1_id = relation['p1']
        p2_id = relation['p2']
        person1_db = Person.objects.get(id=p1_id)
        person2_db = Person.objects.get(id=p2_id)
        
        # Verify p1 BaZi data
        p1_bazi = relation['p1_data']['bazi']
        p1_result = person1_db.bazi_result
        
        self.assertEqual(p1_bazi['yg'], p1_result['year']['god'])
        self.assertEqual(p1_bazi['ye'], p1_result['year']['earth'])
        self.assertEqual(p1_bazi['mg'], p1_result['month']['god'])
        self.assertEqual(p1_bazi['me'], p1_result['month']['earth'])
        self.assertEqual(p1_bazi['dg'], p1_result['day']['god'])
        self.assertEqual(p1_bazi['de'], p1_result['day']['earth'])
        self.assertEqual(p1_bazi['hg'], p1_result['hour']['god'])
        self.assertEqual(p1_bazi['he'], p1_result['hour']['earth'])
        
        # Verify p2 BaZi data
        p2_bazi = relation['p2_data']['bazi']
        p2_result = person2_db.bazi_result
        
        self.assertEqual(p2_bazi['yg'], p2_result['year']['god'])
        self.assertEqual(p2_bazi['ye'], p2_result['year']['earth'])
        self.assertEqual(p2_bazi['mg'], p2_result['month']['god'])
        self.assertEqual(p2_bazi['me'], p2_result['month']['earth'])
        self.assertEqual(p2_bazi['dg'], p2_result['day']['god'])
        self.assertEqual(p2_bazi['de'], p2_result['day']['earth'])
        self.assertEqual(p2_bazi['hg'], p2_result['hour']['god'])
        self.assertEqual(p2_bazi['he'], p2_result['hour']['earth'])

    def test_no_unnecessary_fields_in_person_data(self):
        """Test that person data only contains essential fields."""
        url = reverse('api:bazi-person-relations')
        response = self.client.get(url)
        
        data = response.json()
        relation = data['results'][0]
        
        # Expected fields only
        expected_fields = {'name', 'gender', 'birth_date', 'birth_time', 'bazi'}
        
        # Test p1_data
        p1_data = relation['p1_data']
        p1_fields = set(p1_data.keys())
        self.assertEqual(p1_fields, expected_fields, 
                        f"P1 data has unexpected fields: {p1_fields - expected_fields}")
        
        # Test p2_data
        p2_data = relation['p2_data']
        p2_fields = set(p2_data.keys())
        self.assertEqual(p2_fields, expected_fields,
                        f"P2 data has unexpected fields: {p2_fields - expected_fields}")

    def test_multiple_relations_all_have_person_data(self):
        """Test that all relations in response include person data."""
        # Create another group relation to test multiple results
        person3 = Person.objects.create(
            name='Person Three',
            gender='F',
            birth_date=date(1992, 3, 8),
            birth_time=time(16, 20),
            created_by=self.user,
            owner=False
        )
        person3.calculate_bazi()
        person3.save()
        
        GroupRelation.objects.create(
            owner_user=self.user,
            person1=self.person1,
            person2=person3,
            relation_type='sanxing',
            by=[
                ["o", "d", "e", 2],
                ["p1", "d", "e", 5],
                ["p2", "d", "e", 8]
            ]
        )
        
        url = reverse('api:bazi-person-relations')
        response = self.client.get(url)
        
        data = response.json()
        self.assertGreaterEqual(len(data['results']), 2)
        
        # Check that all relations have person data
        for i, relation in enumerate(data['results']):
            self.assertIn('p1_data', relation, f"Relation {i} missing p1_data")
            self.assertIn('p2_data', relation, f"Relation {i} missing p2_data")
            self.assertIn('bazi', relation['p1_data'], f"Relation {i} p1_data missing bazi")
            self.assertIn('bazi', relation['p2_data'], f"Relation {i} p2_data missing bazi")

    def test_api_handles_missing_person_gracefully(self):
        """Test that API handles missing persons gracefully."""
        # Delete one of the persons referenced in the relation
        deleted_person_id = self.person1.id
        self.person1.delete()
        
        url = reverse('api:bazi-person-relations')
        response = self.client.get(url)
        
        # API should still return 200, but the relation with missing person
        # should either be excluded or handle the missing data gracefully
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        data = response.json()
        
        # Check if any relations remain and verify they have complete data
        for relation in data['results']:
            if 'p1_data' in relation:
                self.assertIn('bazi', relation['p1_data'])
            if 'p2_data' in relation:
                self.assertIn('bazi', relation['p2_data'])

    def test_unauthorized_access_denied(self):
        """Test that unauthorized users cannot access the endpoint."""
        self.client.force_authenticate(user=None)
        
        url = reverse('api:bazi-person-relations')
        response = self.client.get(url)
        
        # API may return either 401 or 403 for unauthorized access
        self.assertIn(response.status_code, [status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN])

    def test_user_can_only_see_own_relations(self):
        """Test that users can only see their own group relations."""
        # Create another user with their own data
        other_user = User.objects.create_user(
            phone='13900000002',
            email='other@example.com',
            password='otherpass123'
        )
        
        other_owner = Person.objects.create(
            name='Other Owner',
            gender='M',
            birth_date=date(1975, 6, 10),
            birth_time=time(10, 0),
            created_by=other_user,
            owner=True
        )
        
        other_person = Person.objects.create(
            name='Other Person',
            gender='F',
            birth_date=date(1980, 8, 25),
            birth_time=time(18, 30),
            created_by=other_user,
            owner=False
        )
        
        GroupRelation.objects.create(
            owner_user=other_user,
            person1=other_owner,
            person2=other_person,
            relation_type='sanhe',
            by=[["o", "d", "e", 1], ["p1", "d", "e", 5], ["p2", "d", "e", 9]]
        )
        
        # Test with original user - should only see their own relations
        url = reverse('api:bazi-person-relations')
        response = self.client.get(url)
        
        data = response.json()
        
        # Verify all returned relations belong to the authenticated user
        for relation in data['results']:
            p1_id = relation['p1']
            p2_id = relation['p2']
            person1 = Person.objects.get(id=p1_id)
            person2 = Person.objects.get(id=p2_id)
            
            self.assertEqual(person1.created_by, self.user)
            self.assertEqual(person2.created_by, self.user)
