"""
Unit tests for tongshu views YBP (Yellow/Black Path) functionality.

These tests verify that the tongshu calendar views correctly classify 
Yellow/Black Path gods as either good (黄道) or bad (黑道).

Good gods (黄道): 青龙, 明堂, 金匮, 天德, 玉堂, 司命
Bad gods (黑道): 天刑, 朱雀, 白虎, 天牢, 玄武, 勾陈

Tests cover:
- transform_day_data function classification
- get_day_data function classification  
- month_view response data
- specific known date classifications
- consistency across different calculation methods
- all 12 gods have correct classification

Usage:
    python manage.py test tongshu.tests.TongshuYBPViewsTestCase
    python manage.py test_ybp_views  # custom management command
"""

from django.test import TestCase, Client
from django.urls import reverse
from datetime import date, time
from tongshu.views import transform_day_data, get_day_data
from tongshu.models import TongshuCalendar
from iching.utils import bz
from iching.utils.utils import test_safe_print
from django.contrib.auth import get_user_model
from bazi.models import Person


class TongshuYBPViewsTestCase(TestCase):
    """Test cases for YBP (Yellow/Black Path) functionality in tongshu views."""

    def setUp(self):
        """Set up test fixtures."""
        self.client = Client()
        
        # Define expected good and bad gods
        self.good_gods = ['青龙', '明堂', '金匮', '天德', '玉堂', '司命']
        self.bad_gods = ['天刑', '朱雀', '白虎', '天牢', '玄武', '勾陈']
        self.all_gods = self.good_gods + self.bad_gods
        
        # Test dates that should give us a variety of gods
        self.test_dates = [
            (2024, 1, 1),   # 金匮 (good)
            (2024, 1, 6),   # 玉堂 (good) 
            (2024, 6, 5),   # 金匮 (good)
            (2024, 12, 25), # 朱雀 (bad)
            (2024, 3, 15),  # Various god
            (2024, 7, 20),  # Various god
            (2024, 9, 10),  # Various god
        ]

    def test_transform_day_data_ybp_classification(self):
        """Test that transform_day_data correctly classifies YBP gods as good or bad."""
        
        # Get sample data from TongshuCalendar
        calendar_data = TongshuCalendar.get_month_data(2024, 1)
        sample_days = calendar_data[4][:20]  # Get 20 days for good coverage
        
        good_count = 0
        bad_count = 0
        
        for day_array in sample_days:
            # Transform the day data
            transformed = transform_day_data(day_array, 2024, 1)
            
            # Check that YBP data exists and has required fields
            self.assertIn('yellow_black_path', transformed)
            ybp_data = transformed['yellow_black_path']
            
            self.assertIn('god', ybp_data)
            self.assertIn('type', ybp_data)
            self.assertIn('position', ybp_data)
            
            # Verify the god name is valid
            god_name = ybp_data['god']
            self.assertIn(god_name, self.all_gods, f"Unknown YBP god: {god_name}")
            
            # Verify the classification matches the expected classification
            ybp_type = ybp_data['type']
            self.assertIn(ybp_type, ['good', 'bad'], f"Invalid YBP type: {ybp_type}")
            
            # Verify the classification is correct for the god
            if god_name in self.good_gods:
                self.assertEqual(ybp_type, 'good', 
                    f"Good god {god_name} should be classified as 'good', got '{ybp_type}'")
                good_count += 1
            elif god_name in self.bad_gods:
                self.assertEqual(ybp_type, 'bad', 
                    f"Bad god {god_name} should be classified as 'bad', got '{ybp_type}'")
                bad_count += 1
            
            # Verify position is valid (1-12)
            position = ybp_data['position']
            self.assertGreaterEqual(position, 1, "YBP position should be >= 1")
            self.assertLessEqual(position, 12, "YBP position should be <= 12")
        
        # Ensure we tested both good and bad gods
        self.assertGreater(good_count, 0, "Should have tested at least one good god")
        self.assertGreater(bad_count, 0, "Should have tested at least one bad god")
        
        test_safe_print(f"✅ Tested {good_count} good gods and {bad_count} bad gods in transform_day_data")

    def test_get_day_data_ybp_classification(self):
        """Test that get_day_data correctly classifies YBP gods as good or bad."""
        
        good_count = 0
        bad_count = 0
        
        for year, month, day in self.test_dates:
            date_obj = date(year, month, day)
            day_data = get_day_data(date_obj)
            
            # Check that YBP data exists and has required fields
            self.assertIn('yellow_black_path', day_data)
            ybp_data = day_data['yellow_black_path']
            
            self.assertIn('god', ybp_data)
            self.assertIn('type', ybp_data)
            self.assertIn('position', ybp_data)
            
            # Verify the god name is valid
            god_name = ybp_data['god']
            self.assertIn(god_name, self.all_gods, f"Unknown YBP god: {god_name}")
            
            # Verify the classification matches the expected classification
            ybp_type = ybp_data['type']
            self.assertIn(ybp_type, ['good', 'bad'], f"Invalid YBP type: {ybp_type}")
            
            # Verify the classification is correct for the god
            if god_name in self.good_gods:
                self.assertEqual(ybp_type, 'good', 
                    f"Good god {god_name} should be classified as 'good', got '{ybp_type}' for {date_obj}")
                good_count += 1
            elif god_name in self.bad_gods:
                self.assertEqual(ybp_type, 'bad', 
                    f"Bad god {god_name} should be classified as 'bad', got '{ybp_type}' for {date_obj}")
                bad_count += 1
        
        # Ensure we tested both good and bad gods
        self.assertGreater(good_count, 0, "Should have tested at least one good god")
        self.assertGreater(bad_count, 0, "Should have tested at least one bad god")
        
        test_safe_print(f"✅ Tested {good_count} good gods and {bad_count} bad gods in get_day_data")

    def test_month_view_response_contains_ybp_data(self):
        """Test that the month view response contains properly classified YBP data."""
        
        # Test the month view for January 2024
        url = reverse('tongshu:month_view', kwargs={'year': 2024, 'month': 1})
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, 200)
        self.assertIn('weeks', response.context)
        
        weeks = response.context['weeks']
        self.assertGreater(len(weeks), 0, "Should have at least one week")
        
        good_count = 0
        bad_count = 0
        total_days = 0
        
        for week in weeks:
            for day in week:
                total_days += 1
                
                # Check that each day has YBP data
                self.assertIn('yellow_black_path', day)
                ybp_data = day['yellow_black_path']
                
                self.assertIn('god', ybp_data)
                self.assertIn('type', ybp_data)
                self.assertIn('position', ybp_data)
                
                # Verify the god name and classification
                god_name = ybp_data['god']
                ybp_type = ybp_data['type']
                
                self.assertIn(god_name, self.all_gods, f"Unknown YBP god: {god_name}")
                self.assertIn(ybp_type, ['good', 'bad'], f"Invalid YBP type: {ybp_type}")
                
                # Count good and bad classifications
                if ybp_type == 'good':
                    good_count += 1
                    self.assertIn(god_name, self.good_gods, 
                        f"God {god_name} classified as 'good' but not in good_gods list")
                elif ybp_type == 'bad':
                    bad_count += 1
                    self.assertIn(god_name, self.bad_gods, 
                        f"God {god_name} classified as 'bad' but not in bad_gods list")
        
        # Ensure we have a reasonable distribution and tested both types
        self.assertGreater(total_days, 30, "Should have tested at least 30 days")
        self.assertGreater(good_count, 0, "Should have at least one good day")
        self.assertGreater(bad_count, 0, "Should have at least one bad day")
        
        test_safe_print(f"✅ Month view: Tested {total_days} days with {good_count} good and {bad_count} bad classifications")

    def test_specific_known_ybp_classifications(self):
        """Test specific dates with known YBP classifications."""
        
        # Test cases with known expected results (from our previous debugging)
        known_cases = [
            (date(2024, 1, 1), '金匮', 'good'),
            (date(2024, 1, 6), '玉堂', 'good'),
            (date(2024, 6, 5), '金匮', 'good'),
            (date(2024, 12, 25), '朱雀', 'bad'),
        ]
        
        for test_date, expected_god, expected_type in known_cases:
            with self.subTest(date=test_date, expected_god=expected_god, expected_type=expected_type):
                # Test with get_day_data
                day_data = get_day_data(test_date)
                ybp_data = day_data['yellow_black_path']
                
                self.assertEqual(ybp_data['god'], expected_god, 
                    f"Expected {expected_god} for {test_date}, got {ybp_data['god']}")
                self.assertEqual(ybp_data['type'], expected_type, 
                    f"Expected {expected_type} for {expected_god} on {test_date}, got {ybp_data['type']}")
                
                # Also verify against the calculation function directly
                calc_result = bz.calcYellowBlackPath(test_date.year, test_date.month, test_date.day)
                self.assertIsNotNone(calc_result)
                self.assertEqual(calc_result['god'], expected_god)
                self.assertEqual(calc_result['type'], expected_type)

    def test_ybp_consistency_across_functions(self):
        """Test that YBP calculations are consistent across different functions."""
        
        test_date = date(2024, 6, 15)
        
        # Get result from direct calculation
        calc_result = bz.calcYellowBlackPath(test_date.year, test_date.month, test_date.day)
        
        # Get result from get_day_data view function
        day_data = get_day_data(test_date)
        view_ybp = day_data['yellow_black_path']
        
        # Get result from TongshuCalendar model and transform_day_data
        calendar_data = TongshuCalendar.get_month_data(test_date.year, test_date.month)
        
        # Find the specific day in the calendar data
        target_day = None
        date_str = test_date.strftime('%Y-%m-%d')
        for day_array in calendar_data[4]:
            if day_array[0] == date_str:
                target_day = day_array
                break
        
        self.assertIsNotNone(target_day, f"Could not find {date_str} in calendar data")
        
        transformed = transform_day_data(target_day, test_date.year, test_date.month)
        transform_ybp = transformed['yellow_black_path']
        
        # All three methods should give the same result
        self.assertEqual(calc_result['god'], view_ybp['god'], 
            "Direct calculation and view function should return same god")
        self.assertEqual(calc_result['type'], view_ybp['type'], 
            "Direct calculation and view function should return same type")
        
        self.assertEqual(view_ybp['god'], transform_ybp['god'], 
            "View function and transform function should return same god")
        self.assertEqual(view_ybp['type'], transform_ybp['type'], 
            "View function and transform function should return same type")
        
        test_safe_print(f"✅ Consistency test passed for {test_date}: {calc_result['god']} ({calc_result['type']})")

    def test_all_twelve_gods_have_correct_classification(self):
        """Test that all 12 YBP gods have the correct classification when they appear."""
        
        # Track which gods we've seen
        gods_seen = set()
        classifications_tested = {}
        
        # Test multiple months to increase chance of seeing all gods
        test_months = [(2024, month) for month in range(1, 13)]
        
        for year, month in test_months:
            calendar_data = TongshuCalendar.get_month_data(year, month)
            
            for day_array in calendar_data[4]:
                transformed = transform_day_data(day_array, year, month)
                ybp_data = transformed['yellow_black_path']
                
                god_name = ybp_data['god']
                ybp_type = ybp_data['type']
                
                gods_seen.add(god_name)
                
                # Record the classification we see for this god
                if god_name not in classifications_tested:
                    classifications_tested[god_name] = ybp_type
                else:
                    # Ensure consistent classification
                    self.assertEqual(classifications_tested[god_name], ybp_type, 
                        f"Inconsistent classification for {god_name}: saw both {classifications_tested[god_name]} and {ybp_type}")
        
        # Verify we've seen a good variety of gods
        self.assertGreaterEqual(len(gods_seen), 8, 
            f"Should have seen at least 8 different gods, only saw: {sorted(gods_seen)}")
        
        # Verify that all gods we've seen have the correct classification
        for god_name, observed_type in classifications_tested.items():
            if god_name in self.good_gods:
                self.assertEqual(observed_type, 'good', 
                    f"Good god {god_name} was classified as '{observed_type}'")
            elif god_name in self.bad_gods:
                self.assertEqual(observed_type, 'bad', 
                    f"Bad god {god_name} was classified as '{observed_type}'")
            else:
                self.fail(f"Unknown god {god_name} encountered")
        
        good_seen = len([g for g in gods_seen if g in self.good_gods])
        bad_seen = len([g for g in gods_seen if g in self.bad_gods])
        
        test_safe_print(f"✅ Tested {len(gods_seen)} different gods: {good_seen} good, {bad_seen} bad")
        test_safe_print(f"   Good gods seen: {sorted([g for g in gods_seen if g in self.good_gods])}")
        test_safe_print(f"   Bad gods seen: {sorted([g for g in gods_seen if g in self.bad_gods])}")


class TongshuUserBaZiHeaderTests(TestCase):
    """Tests for the UI header that shows the logged-in user's BaZi in tongshu month view."""

    def setUp(self):
        self.client = Client()
        User = get_user_model()
        self.user = User.objects.create_user(phone='10000000001', password='p1', email='u1@example.com', first_name='U1')

    def _create_owner_person(self, has_hour=True):
        # Construct a deterministic BaZi result
        # Choose distinct gods to simplify order checks: hour=甲(0), day=丙(2, fire), month=戊(4), year=辛(7)
        bazi_result = {
            'day': {'god': 2, 'earth': 6},   # 丙, 午
            'month': {'god': 4, 'earth': 5}, # 戊, 巳
            'year': {'god': 7, 'earth': 8},  # 辛, 申
        }
        if has_hour:
            bazi_result['hour'] = {'god': 0, 'earth': 1}  # 甲, 丑
        p = Person(
            name='Tester',
            birth_date=date(1981, 1, 11),
            birth_time=time(22, 30),
            created_by=self.user,
            owner=True,
            bazi_result=bazi_result,
        )
        # Skip auto-recalculation to preserve our indices
        p._skip_bazi_calculation = True
        p.save()
        return p

    def _get_header_html(self, response_text):
        # Extract only the header portion to avoid duplicates in the calendar grid
        start = response_text.find('我的八字')
        self.assertNotEqual(start, -1, 'Header label 我的八字 should exist')
        # Anchor that follows header in template
        end_anchor = '<!-- Year dropdown'
        end = response_text.find(end_anchor, start)
        if end == -1:
            end = start + 800  # fallback window
        return response_text[start:end]

    def test_header_shows_label_pillars_and_color(self):
        self._create_owner_person(has_hour=True)
        self.client.force_login(self.user)
        url = reverse('tongshu:month_view', kwargs={'year': 2025, 'month': 8})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        html = response.content.decode('utf-8')
        # Has label and bazi.css
        self.assertIn('我的八字', html)
        self.assertIn('css/bazi.css', html)
        header = self._get_header_html(html)
        # Day stem should be colored by element-fire (丙)
        self.assertIn('element-fire', header)
        # Pillars order: Hour | Day | Month | Year
        hour_char = bz.gGodstem[0]  # 甲
        day_char = bz.gGodstem[2]   # 丙
        month_char = bz.gGodstem[4] # 戊
        year_char = bz.gGodstem[7]  # 辛
        idx_hour = header.find(hour_char)
        idx_day = header.find(day_char)
        idx_month = header.find(month_char)
        idx_year = header.find(year_char)
        self.assertTrue(idx_hour != -1 and idx_day != -1 and idx_month != -1 and idx_year != -1,
                        'All four pillar god characters should be present in header')
        self.assertTrue(idx_hour < idx_day < idx_month < idx_year,
                        'Pillars should be ordered Hour < Day < Month < Year')
        # DOB/time should not be present
        self.assertNotIn('1981-01-11', html)
        self.assertNotIn('22:30', html)

    def test_header_without_hour_shows_three_pillars_day_first(self):
        self._create_owner_person(has_hour=False)
        self.client.force_login(self.user)
        url = reverse('tongshu:month_view', kwargs={'year': 2025, 'month': 8})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        html = response.content.decode('utf-8')
        header = self._get_header_html(html)
        # Pillars order: Day | Month | Year (no hour block)
        day_char = bz.gGodstem[2]
        month_char = bz.gGodstem[4]
        year_char = bz.gGodstem[7]
        idx_day = header.find(day_char)
        idx_month = header.find(month_char)
        idx_year = header.find(year_char)
        self.assertTrue(idx_day != -1 and idx_month != -1 and idx_year != -1)
        self.assertTrue(idx_day < idx_month < idx_year,
                        'Without hour, order should be Day < Month < Year')

    def test_header_not_shown_when_no_person(self):
        self.client.force_login(self.user)
        url = reverse('tongshu:month_view', kwargs={'year': 2025, 'month': 8})
        response = self.client.get(url)
        html = response.content.decode('utf-8')
        self.assertNotIn('我的八字', html)
