from __future__ import annotations

from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from datetime import date, time
import json

from bazi.models import Person


class BaziUpdateAPITests(TestCase):
    """Test PATCH and PUT operations for bazi records with owner=True/False."""
    
    def setUp(self):
        User = get_user_model()
        self.user = User.objects.create_user(
            phone='13900000001', password='testpass123', email='test@example.com'
        )
        self.client.force_login(self.user)
        
        # Create owner record (owner=True)
        self.owner = Person.objects.create(
            name='Owner Person', 
            gender='M', 
            birth_date=date(1985, 3, 15),
            birth_time=time(9, 30), 
            created_by=self.user, 
            owner=True,
            notes='Original owner note'
        )
        self.owner.calculate_bazi()
        self.owner.save()
        
        # Create non-owner record (owner=False)
        self.non_owner = Person.objects.create(
            name='Non-Owner Person', 
            gender='F', 
            birth_date=date(1990, 7, 22),
            birth_time=time(15, 45), 
            created_by=self.user, 
            owner=False,
            notes='Original non-owner note'
        )
        self.non_owner.calculate_bazi()
        self.non_owner.save()

    def test_patch_owner_true_record(self):
        """Test PATCH request updates owner=True record correctly."""
        url = reverse('api:bazi-detail', kwargs={'pk': self.owner.id})
        
        # Record original values
        original_birth_date = self.owner.birth_date
        original_updated_at = self.owner.updated_at
        
        # PATCH request to update birth_date
        new_birth_date = '1987-06-10'
        patch_data = {
            'birth_date': new_birth_date
        }
        
        response = self.client.patch(
            url, 
            data=json.dumps(patch_data),
            content_type='application/json'
        )
        
        # Check response
        self.assertEqual(response.status_code, 200)
        response_data = response.json()
        
        # Verify response contains updated birth_date
        self.assertEqual(response_data['birth_date'], new_birth_date)
        self.assertEqual(response_data['id'], self.owner.id)
        self.assertEqual(response_data['owner'], True)
        
        # Verify database was actually updated
        self.owner.refresh_from_db()
        self.assertEqual(str(self.owner.birth_date), new_birth_date)
        self.assertNotEqual(original_birth_date, self.owner.birth_date)
        self.assertNotEqual(original_updated_at, self.owner.updated_at)
        
        # Verify bazi calculation was updated
        self.assertIsNotNone(response_data['result'])
        self.assertIn('year', response_data['result'])
        self.assertIn('month', response_data['result'])
        self.assertIn('day', response_data['result'])
        self.assertIn('hour', response_data['result'])

    def test_patch_owner_false_record(self):
        """Test PATCH request updates owner=False record correctly."""
        url = reverse('api:bazi-detail', kwargs={'pk': self.non_owner.id})
        
        # Record original values
        original_birth_date = self.non_owner.birth_date
        original_updated_at = self.non_owner.updated_at
        
        # PATCH request to update birth_date
        new_birth_date = '1992-12-05'
        patch_data = {
            'birth_date': new_birth_date
        }
        
        response = self.client.patch(
            url, 
            data=json.dumps(patch_data),
            content_type='application/json'
        )
        
        # Check response
        self.assertEqual(response.status_code, 200)
        response_data = response.json()
        
        # Verify response contains updated birth_date
        self.assertEqual(response_data['birth_date'], new_birth_date)
        self.assertEqual(response_data['id'], self.non_owner.id)
        self.assertEqual(response_data['owner'], False)
        
        # Verify database was actually updated
        self.non_owner.refresh_from_db()
        self.assertEqual(str(self.non_owner.birth_date), new_birth_date)
        self.assertNotEqual(original_birth_date, self.non_owner.birth_date)
        self.assertNotEqual(original_updated_at, self.non_owner.updated_at)
        
        # Verify bazi calculation was updated
        self.assertIsNotNone(response_data['result'])
        self.assertIn('year', response_data['result'])
        self.assertIn('month', response_data['result'])
        self.assertIn('day', response_data['result'])
        self.assertIn('hour', response_data['result'])

    def test_put_owner_true_record(self):
        """Test PUT request updates owner=True record correctly."""
        url = reverse('api:bazi-detail', kwargs={'pk': self.owner.id})
        
        # Record original values
        original_birth_date = self.owner.birth_date
        original_updated_at = self.owner.updated_at
        
        # PUT request with complete data
        new_birth_date = '1986-11-28'
        new_name = 'Updated Owner Name'
        put_data = {
            'name': new_name,
            'gender': 'M',
            'birth_date': new_birth_date,
            'birth_time': '14:20:00',
            'twin_type': 0,
            'father_dob': None,
            'mother_dob': None,
            'notes': 'Updated owner note'
        }
        
        response = self.client.put(
            url, 
            data=json.dumps(put_data),
            content_type='application/json'
        )
        
        # Check response
        self.assertEqual(response.status_code, 200)
        response_data = response.json()
        
        # Verify response contains updated data
        self.assertEqual(response_data['birth_date'], new_birth_date)
        self.assertEqual(response_data['name'], new_name)
        self.assertEqual(response_data['id'], self.owner.id)
        self.assertEqual(response_data['owner'], True)
        
        # Verify database was actually updated
        self.owner.refresh_from_db()
        self.assertEqual(str(self.owner.birth_date), new_birth_date)
        self.assertEqual(self.owner.name, new_name)
        self.assertNotEqual(original_birth_date, self.owner.birth_date)
        self.assertNotEqual(original_updated_at, self.owner.updated_at)
        
        # Verify bazi calculation was updated
        self.assertIsNotNone(response_data['result'])
        self.assertIn('year', response_data['result'])
        self.assertIn('month', response_data['result'])
        self.assertIn('day', response_data['result'])
        self.assertIn('hour', response_data['result'])

    def test_put_owner_false_record(self):
        """Test PUT request updates owner=False record correctly."""
        url = reverse('api:bazi-detail', kwargs={'pk': self.non_owner.id})
        
        # Record original values
        original_birth_date = self.non_owner.birth_date
        original_updated_at = self.non_owner.updated_at
        
        # PUT request with complete data
        new_birth_date = '1991-04-18'
        new_name = 'Updated Non-Owner Name'
        put_data = {
            'name': new_name,
            'gender': 'F',
            'birth_date': new_birth_date,
            'birth_time': '08:15:00',
            'twin_type': 0,
            'father_dob': None,
            'mother_dob': None,
            'notes': 'Updated non-owner note'
        }
        
        response = self.client.put(
            url, 
            data=json.dumps(put_data),
            content_type='application/json'
        )
        
        # Check response
        self.assertEqual(response.status_code, 200)
        response_data = response.json()
        
        # Verify response contains updated data
        self.assertEqual(response_data['birth_date'], new_birth_date)
        self.assertEqual(response_data['name'], new_name)
        self.assertEqual(response_data['id'], self.non_owner.id)
        self.assertEqual(response_data['owner'], False)
        
        # Verify database was actually updated
        self.non_owner.refresh_from_db()
        self.assertEqual(str(self.non_owner.birth_date), new_birth_date)
        self.assertEqual(self.non_owner.name, new_name)
        self.assertNotEqual(original_birth_date, self.non_owner.birth_date)
        self.assertNotEqual(original_updated_at, self.non_owner.updated_at)
        
        # Verify bazi calculation was updated
        self.assertIsNotNone(response_data['result'])
        self.assertIn('year', response_data['result'])
        self.assertIn('month', response_data['result'])
        self.assertIn('day', response_data['result'])
        self.assertIn('hour', response_data['result'])

    def test_patch_multiple_fields_owner_true(self):
        """Test PATCH request with multiple fields on owner=True record."""
        url = reverse('api:bazi-detail', kwargs={'pk': self.owner.id})
        
        # PATCH request to update multiple fields
        patch_data = {
            'birth_date': '1988-09-14',
            'birth_time': '16:30:00',
            'notes': 'Multiple fields updated',
            'gender': 'F'
        }
        
        response = self.client.patch(
            url, 
            data=json.dumps(patch_data),
            content_type='application/json'
        )
        
        # Check response
        self.assertEqual(response.status_code, 200)
        response_data = response.json()
        
        # Verify all updated fields
        self.assertEqual(response_data['birth_date'], patch_data['birth_date'])
        self.assertEqual(response_data['birth_time'], patch_data['birth_time'])
        self.assertEqual(response_data['notes'], patch_data['notes'])
        self.assertEqual(response_data['gender'], patch_data['gender'])
        self.assertEqual(response_data['owner'], True)
        
        # Verify database updates
        self.owner.refresh_from_db()
        self.assertEqual(str(self.owner.birth_date), patch_data['birth_date'])
        self.assertEqual(str(self.owner.birth_time), patch_data['birth_time'])
        self.assertEqual(self.owner.notes, patch_data['notes'])
        self.assertEqual(self.owner.gender, patch_data['gender'])

    def test_patch_multiple_fields_owner_false(self):
        """Test PATCH request with multiple fields on owner=False record."""
        url = reverse('api:bazi-detail', kwargs={'pk': self.non_owner.id})
        
        # PATCH request to update multiple fields
        patch_data = {
            'birth_date': '1993-02-28',
            'birth_time': '11:45:00',
            'notes': 'Non-owner multiple fields updated',
            'name': 'Patched Non-Owner'
        }
        
        response = self.client.patch(
            url, 
            data=json.dumps(patch_data),
            content_type='application/json'
        )
        
        # Check response
        self.assertEqual(response.status_code, 200)
        response_data = response.json()
        
        # Verify all updated fields
        self.assertEqual(response_data['birth_date'], patch_data['birth_date'])
        self.assertEqual(response_data['birth_time'], patch_data['birth_time'])
        self.assertEqual(response_data['notes'], patch_data['notes'])
        self.assertEqual(response_data['name'], patch_data['name'])
        self.assertEqual(response_data['owner'], False)
        
        # Verify database updates
        self.non_owner.refresh_from_db()
        self.assertEqual(str(self.non_owner.birth_date), patch_data['birth_date'])
        self.assertEqual(str(self.non_owner.birth_time), patch_data['birth_time'])
        self.assertEqual(self.non_owner.notes, patch_data['notes'])
        self.assertEqual(self.non_owner.name, patch_data['name'])

    def test_bazi_calculation_after_update(self):
        """Test that bazi calculations are properly updated after API changes."""
        url = reverse('api:bazi-detail', kwargs={'pk': self.owner.id})
        
        # Get original bazi calculation
        original_year_god = self.owner.bazi_result['year']['god']
        
        # PATCH with birth date that will change year pillar
        new_birth_date = '2000-01-01'  # Different year to change calculation
        patch_data = {
            'birth_date': new_birth_date
        }
        
        response = self.client.patch(
            url, 
            data=json.dumps(patch_data),
            content_type='application/json'
        )
        
        # Check response
        self.assertEqual(response.status_code, 200)
        response_data = response.json()
        
        # Verify bazi was recalculated
        new_year_god = response_data['result']['year']['god_idx']
        self.assertIsNotNone(new_year_god)
        
        # The year pillar should be different for year 2000 vs 1985
        self.assertNotEqual(original_year_god, new_year_god)
        
        # Verify all pillar fields are populated
        for pillar in ['year', 'month', 'day', 'hour']:
            pillar_data = response_data['result'][pillar]
            self.assertIsNotNone(pillar_data)
            self.assertIn('god', pillar_data)
            self.assertIn('earth', pillar_data)
            self.assertIn('god_idx', pillar_data)
            self.assertIn('earth_idx', pillar_data)
            self.assertIsNotNone(pillar_data['god'])
            self.assertIsNotNone(pillar_data['earth'])
            self.assertIsNotNone(pillar_data['god_idx'])
            self.assertIsNotNone(pillar_data['earth_idx'])

    def test_relation_calculation_skipped_during_api_update(self):
        """Test that relation calculation is skipped during API updates to prevent interference."""
        url = reverse('api:bazi-detail', kwargs={'pk': self.owner.id})
        
        # This test ensures that the _skip_relation_calculation flag works
        # We can't directly test the flag, but we can verify updates work
        # consistently without the processing delays we saw before the fix
        
        patch_data = {
            'birth_date': '1989-05-25'
        }
        
        response = self.client.patch(
            url, 
            data=json.dumps(patch_data),
            content_type='application/json'
        )
        
        # Check response comes back quickly without processing interference
        self.assertEqual(response.status_code, 200)
        response_data = response.json()
        
        # Verify the update actually took effect
        self.assertEqual(response_data['birth_date'], patch_data['birth_date'])
        
        # Verify in database
        self.owner.refresh_from_db()
        self.assertEqual(str(self.owner.birth_date), patch_data['birth_date'])

    def test_unauthorized_update_fails(self):
        """Test that unauthorized users cannot update bazi records."""
        # Create another user
        User = get_user_model()
        other_user = User.objects.create_user(
            phone='13900000002', password='testpass123', email='other@example.com'
        )
        
        # Create a record for the other user
        other_person = Person.objects.create(
            name='Other Person', 
            gender='M', 
            birth_date=date(1995, 1, 1),
            created_by=other_user, 
            owner=True
        )
        
        # Try to update other user's record with current user
        url = reverse('api:bazi-detail', kwargs={'pk': other_person.id})
        patch_data = {
            'birth_date': '2000-01-01'
        }
        
        response = self.client.patch(
            url, 
            data=json.dumps(patch_data),
            content_type='application/json'
        )
        
        # Should fail (404 because queryset is filtered by user)
        self.assertEqual(response.status_code, 404)

    def test_invalid_data_validation(self):
        """Test that invalid data is properly validated and rejected."""
        url = reverse('api:bazi-detail', kwargs={'pk': self.owner.id})
        
        # Test invalid birth date
        patch_data = {
            'birth_date': 'invalid-date'
        }
        
        response = self.client.patch(
            url, 
            data=json.dumps(patch_data),
            content_type='application/json'
        )
        
        self.assertEqual(response.status_code, 400)
        
        # Test invalid gender
        patch_data = {
            'gender': 'X'  # Invalid choice
        }
        
        response = self.client.patch(
            url, 
            data=json.dumps(patch_data),
            content_type='application/json'
        )
        
        # This might be 400 or 200 depending on validation, but record shouldn't change
        self.owner.refresh_from_db()
        self.assertNotEqual(self.owner.gender, 'X')


