from django.db import models
from datetime import datetime, timedelta, date
from calendar import monthrange
from iching.utils import bz, lunar_calendar, bz24
from django.utils import timezone


class TongshuCalendar(models.Model):
    class Meta:
        managed = False  # This model doesn't create a database table

    @staticmethod
    def get_month_data(year=None, month=None, extra_days=10, include_lunar_title_desc=True):
        """
        Generate calendar data for the specified month.
        If no year/month provided, uses current month.
        
        Args:
            year: Year (defaults to current year)
            month: Month (1-12, defaults to current month)
            extra_days: Number of days to include before and after month (default 10)
            include_lunar_title_desc: Whether to include lunar formatted strings in output (default True)
        """
        # Use current date if no year/month specified
        if year is None or month is None:
            today = datetime.now()
            year = year or today.year
            month = month or today.month

        # Get the first day of the month and number of days
        first_day = date(year, month, 1)
        _, num_days = monthrange(year, month)
        last_day = date(year, month, num_days)
        
        # Calculate the date range to process
        start_date = first_day - timedelta(days=extra_days)
        end_date = last_day + timedelta(days=extra_days)
        
        # Calculate all dates we need to process
        all_dates = []
        current_date = start_date
        while current_date <= end_date:
            is_adjacent = current_date.month != month or current_date.year != year
            all_dates.append((current_date, is_adjacent))
            current_date = current_date + timedelta(days=1)

        today = datetime.now().date()
        
        # Determine which years we need solar term data for
        required_years = set()
        for date_obj, _ in all_dates:
            required_years.add(date_obj.year)
            
        # Always include previous year if our date range includes January or early February
        # This ensures we have solar terms data for these months
        for date_obj, _ in all_dates:
            # If any date is before February 5th, include previous year
            if date_obj.month == 1 or (date_obj.month == 2 and date_obj.day < 5):
                required_years.add(date_obj.year - 1)
                break
        
        # Get solar terms for all required years
        cycles_by_year = {}
        for req_year in required_years:
            cycles_by_year[req_year] = bz24.calc24CycleAdjustedYear(req_year, groupByMonth=True)

        # Calculate month's bazi using the 15th day
        month_bazi = bz.calcBazi(year, month, 15)
        month_earth = bz.gEarthstem[month_bazi['month']['e']]
        month_element_index = bz.findElementI(bz.getEarthElement(month_bazi['month']['e']))

        # Create a map of solar term start dates to their indices
        solar_term_dates = {}
        
        # Process all years' cycles
        for year_val, cycles in cycles_by_year.items():
            for month_idx, cycle in enumerate(cycles):
                # First solar term of the month
                first_start = datetime.fromisoformat(cycle['first']['d'].replace('Z', '+00:00')) + timedelta(hours=8)
                first_end = datetime.fromisoformat(cycle['first']['next'].replace('Z', '+00:00')) + timedelta(hours=8)
                solar_term_dates[first_start.date()] = {
                    'index': month_idx * 2,
                    'name': cycle['first']['c'],
                    'time': first_start.strftime('%H:%M'),
                    'date': first_start,
                    'year': year_val
                }
                
                # Second solar term of the month
                second_start = datetime.fromisoformat(cycle['second']['d'].replace('Z', '+00:00')) + timedelta(hours=8)
                second_end = datetime.fromisoformat(cycle['second']['next'].replace('Z', '+00:00')) + timedelta(hours=8)
                solar_term_dates[second_start.date()] = {
                    'index': month_idx * 2 + 1,
                    'name': cycle['second']['c'],
                    'time': second_start.strftime('%H:%M'),
                    'date': second_start,
                    'year': year_val
                }

        # Create a sorted list of solar term dates for determining which period a day belongs to
        sorted_solar_terms = []
        for date_obj, term_data in solar_term_dates.items():
            sorted_solar_terms.append((date_obj, term_data['index'], term_data['name']))
        sorted_solar_terms.sort()  # Sort by date
        
        # Function to find which solar term period a date belongs to
        def get_solar_term_period(date_obj):
            # Find the latest solar term that started before or on this date
            current_term_index = 23  # Default to the last term of previous year if nothing found
            
            for term_date, term_index, term_name in sorted_solar_terms:
                if term_date > date_obj:
                    break
                current_term_index = term_index
            
            return current_term_index

        # Process days data
        days_data = []
        for date_obj, is_adjacent in all_dates:
            # Calculate BaZi using 22:59:59 to match YBP calculation expectations
            bazi_data = bz.getCalendar10kGodEarthStem(date_obj.year, date_obj.month, date_obj.day, 22, 59, 59)
            
            # Get lunar calendar data
            lunar = lunar_calendar.convertLunar(datetime(date_obj.year, date_obj.month, date_obj.day))
            lunar_formatted = TongshuCalendar.format_lunar_date(lunar)
            lunar_data_formatted = TongshuCalendar.format_lunar_date(lunar, format="data")
            
            # Calculate Jian Chu 12 Gods
            jianchu_index = bz.calcJianChu12God(date_obj.year, date_obj.month, date_obj.day, bazi_data)
            
            # Calculate Yellow/Black Path
            yellow_black_path = bz.calcYellowBlackPath(date_obj.year, date_obj.month, date_obj.day, bazi_data=bazi_data)
            
            # For TongshuCalendar model, we only store the index (0-11)
            ybp_index = yellow_black_path['position'] - 1  # Convert from 1-12 to 0-11

            # Check solar terms
            solar_term = None
            solar_term_time = None
            has_solar_term = False
            solar_term_index = None
            
            # Check if this day is the start of a solar term
            if date_obj in solar_term_dates:
                term_data = solar_term_dates[date_obj]
                solar_term = term_data['name']
                solar_term_time = term_data['time']
                solar_term_index = term_data['index']
                has_solar_term = True
            
            # Create day data - compact array format
            day_data = [
                date_obj.strftime('%Y-%m-%d'),  # date
                date_obj.day,                   # day
                1 if date_obj == today else 0,  # isToday (1=true, 0=false)
                [                               # bazi (flattened)
                    bazi_data['year']['g'], 
                    bazi_data['year']['e'],
                    bazi_data['month']['g'], 
                    bazi_data['month']['e'],
                    bazi_data['day']['g'], 
                    bazi_data['day']['e']
                ],
                [                               # lunar
                    lunar['d'],                 # day
                    lunar['m'],                 # month
                    lunar['y'],                 # year
                    1 if lunar.get('leap', False) else 0  # isLeap (1=true, 0=false)
                ],
            ]

            # Conditionally include lunar_title_desc
            if include_lunar_title_desc:
                day_data.append([                               # lunar formatted strings
                    lunar_formatted if not solar_term else f"{solar_term} {solar_term_time}", # f1: full formatted lunar date
                    lunar_data_formatted['m'] if not solar_term else solar_term,              # f2: lunar month
                    1 if not solar_term and len(lunar_data_formatted['m']) >= 3 else 0,       # f3: is title 3 char (1=true, 0=false)
                    lunar_data_formatted['d'] if not solar_term else solar_term_time          # f4: lunar day
                ])
            
            # Add remaining elements
            day_data.extend([
                jianchu_index,                  # jianchu
                # Conditionally include solar_term_time as 4th item only when has_solar_term is True
                ([                               # solar term
                    get_solar_term_period(date_obj),  # current solar term period (0-23)
                    1 if has_solar_term else 0,       # is solar term day (1=true, 0=false)
                    bz24.findSolar5ElementI(get_solar_term_period(date_obj)),  # solar term element index (0-4)
                    solar_term_time             # add solar term time as 4th item when has_solar_term is True
                ] if has_solar_term else [      # otherwise use the original 3 items only
                    get_solar_term_period(date_obj),
                    0,  # is_solar_term = false
                    bz24.findSolar5ElementI(get_solar_term_period(date_obj))
                ]),
                ybp_index                       # yellow/black path index (moved to last position)
            ])
            days_data.append(day_data)
        
        # Find solar terms in the date range
        solar_terms_list = []
        for date_key, term_data in solar_term_dates.items():
            # Include solar terms that fall within our date range
            if start_date <= date_key <= end_date:
                # Format the term data for the response
                solar_terms_list.append({
                    'name': term_data['name'],
                    'date': term_data['date'].isoformat(),
                    'index': term_data['index'],
                    'year': term_data['year']  # Include year information for clarity
                })
        
        # Sort solar terms by date for chronological order
        solar_terms_list.sort(key=lambda x: x['date'])
        
        # Calculate previous and next month links
        if month == 1:
            prev_month = {'year': year - 1, 'month': 12}
        else:
            prev_month = {'year': year, 'month': month - 1}
            
        if month == 12:
            next_month = {'year': year + 1, 'month': 1}
        else:
            next_month = {'year': year, 'month': month + 1}
        
        # Package the complete response as a positional array
        # [year, month, month_bazi[y_g,y_e,m_g,m_e], month_element_index, days, prev[year,month], next[year,month]]
        return [
            year,
            month,
            [month_bazi['year']['g'], month_bazi['year']['e'], month_bazi['month']['g'], month_bazi['month']['e']],
            month_element_index,
            days_data,
            [prev_month['year'], prev_month['month']],
            [next_month['year'], next_month['month']]
        ]

    @staticmethod
    def format_lunar_date(lunar_data, format="1line"):
        """Format lunar date in Chinese characters."""
        # Chinese numbers for 1-12
        chinese_numbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
        
        month = lunar_data['m']
        day = lunar_data['d']
        is_leap = lunar_data.get('leap', False)
        
        # Format month
        if month == 1:
            month_str = '正月'
        elif 1 <= month <= 12:
            month_str = f"{chinese_numbers[month-1]}月"
        else:
            month_str = '月'  # Fallback just in case
        
        # Add '润' prefix for leap months
        is_leap_str = ''
        if is_leap:
            is_leap_str = "闰"
        
        # Format day
        if 1 <= day <= 10:
            day_str = f"初{chinese_numbers[day-1]}"
        elif 11 <= day <= 19:
            day_str = f"十{chinese_numbers[day-11]}"
        elif day == 20:
            day_str = '二十'
        elif 21 <= day <= 29:
            day_str = f"廿{chinese_numbers[day-21]}"
        elif day == 30:
            day_str = '三十'
        else:
            day_str = str(day)  # Fallback just in case
        
        if format == '1line':
            return f"{month_str}{day_str}"
        else:  # data by month (m) and day (d)
            return {'m': month_str, 'd': day_str, 'l': is_leap_str}
