from django.shortcuts import render
from datetime import datetime, timedelta, date
from calendar import monthrange
from iching.utils import bz, lunar_calendar, bz24
import pytz
from django.utils import timezone
from .models import TongshuCalendar
from bazi.models import Person


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:
        # month_str = f"润{month_str}"
        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}

def get_month_calendar(year, month):
    """Generate calendar data for the specified month."""
    # Get the first day of the month and number of days
    first_day = date(year, month, 1)
    _, num_days = monthrange(year, month)
    
    # Get the weekday of the first day (0 = Monday, 6 = Sunday)
    first_weekday = first_day.weekday()
    
    # Calculate all dates we need to process
    all_dates = []
    
    # Previous month's spillover days
    if first_weekday > 0:
        prev_month = first_day - timedelta(days=1)
        _, prev_num_days = monthrange(prev_month.year, prev_month.month)
        for i in range(first_weekday):
            day = prev_num_days - first_weekday + i + 1
            all_dates.append((date(prev_month.year, prev_month.month, day), True))

    # Current month's days
    for day in range(1, num_days + 1):
        all_dates.append((date(year, month, day), False))
    
    # Next month's spillover days
    total_days = first_weekday + num_days
    remaining_days = 42 - total_days  # 6 rows × 7 days = 42
    if remaining_days > 0:
        next_month = first_day + timedelta(days=32)  # Jump to next month
        next_month = date(next_month.year, next_month.month, 1)
        for day in range(1, remaining_days + 1):
            all_dates.append((date(next_month.year, next_month.month, day), True))

    today = datetime.now().date()
    
    # Get solar terms for the current year
    cycles = bz24.calc24CycleAdjustedYear(year, groupByMonth=True)
    
    # For December, January, or when we have spillover days from previous year,
    # we need to get the previous year's cycles as well
    prev_year_cycles = None
    if month == 1 or month == 12 or (first_weekday > 0 and month == 1):
        prev_year_cycles = bz24.calc24CycleAdjustedYear(year - 1, groupByMonth=True)
    
    # Earth branches for months (starting from 子)
    earth_branches = ['子', '丑', '寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥']
    
    # Elements mapping
    element_map = {
        '寅': 'wood', '卯': 'wood',
        '巳': 'fire', '午': 'fire',
        '辰': 'earth', '未': 'earth', '戌': 'earth', '丑': 'earth',
        '申': 'metal', '酉': 'metal',
        '亥': 'water', '子': 'water'
    }

    # Create a map of solar term start dates to their indices
    solar_term_dates = {}
    month_periods = {}  # Store the period ranges for each month
    
    # Process previous year's cycles if available
    if prev_year_cycles:
        for month_idx, cycle in enumerate(prev_year_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()] = month_idx * 2
            
            # 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()] = month_idx * 2 + 1
            
            # Store the period range for this month
            month_periods[f"{year-1}-{month_idx}"] = (first_start.date(), second_end.date())
    
    # Process current year's cycles
    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()] = month_idx * 2
        
        # 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()] = month_idx * 2 + 1
        
        # Store the period range for this month
        month_periods[f"{year}-{month_idx}"] = (first_start.date(), second_end.date())

    processed_dates = []
    for date_obj, is_adjacent in all_dates:
        # Find which solar term period this date falls into
        current_month_index = None
        solar_term_index = None
        year_str = str(year)  # Initialize with current year by default
        
        # Check if this date is a solar term start date
        if date_obj in solar_term_dates:
            solar_term_index = solar_term_dates[date_obj]
            current_month_index = solar_term_index // 2
        else:
            # Find which month period this date falls into
            for period_key, (period_start, period_end) in month_periods.items():
                if period_start <= date_obj < period_end:
                    year_str, month_idx = period_key.split('-')
                    current_month_index = int(month_idx)
                    solar_term_index = current_month_index * 2
                    break
        
        # Get month's earth branch and element
        if current_month_index is not None:
            month_earth = earth_branches[(current_month_index + 2) % 12]  # Shift by 2
            month_element = element_map.get(month_earth, '')
            # print(str(date_obj) + ' month index: ' + str(current_month_index) + ' month earth: ' + month_earth + ' 5e: ' + month_element)
        else:
            month_earth = ''
            month_element = ''
        
        # Calculate BaZi using 22:59:59 to match YBP calculation expectations
        bazi = 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 = format_lunar_date(lunar)
        lunar_data_formatted = format_lunar_date(lunar, "data")
        
        # Calculate Jian Chu 12 Gods
        jianchu_index = bz.calcJianChu12God(date_obj.year, date_obj.month, date_obj.day, bazi)
        jianchu = bz.gJianChu12God[jianchu_index]

        # Calculate Yellow/Black Path
        yellow_black_path = bz.calcYellowBlackPath(date_obj.year, date_obj.month, date_obj.day, bazi_data=bazi)

        # Check solar terms
        solar_term = None
        solar_term_time = None
        has_solar_term = False

        # Check if this day is the start of a solar term
        cycles_to_check = [cycles]
        if prev_year_cycles and (date_obj.year == year - 1 or date_obj.month == 1 or date_obj.month == 2):
            cycles_to_check.append(prev_year_cycles)
        
        for cycle_list in cycles_to_check:
            for cycle in cycle_list:
                for term in ['first', 'second']:
                    term_data = cycle[term]
                    term_date = datetime.fromisoformat(term_data['d'].replace('Z', '+00:00')) + timedelta(hours=8)
                    if term_date.date() == date_obj:
                        solar_term = term_data['c']
                        solar_term_time = term_date.strftime('%H:%M')
                        has_solar_term = True
                        break
                if has_solar_term:
                    break
            if has_solar_term:
                break
        
        processed_dates.append({
            'date': date_obj,
            'day': date_obj.day,
            'is_adjacent_month': is_adjacent,
            'is_today': date_obj == today,
            'bazi': {
                'year': {'god': bz.gGodstem[bazi['year']['g']], 'earth': bz.gEarthstem[bazi['year']['e']]},
                'month': {'god': bz.gGodstem[bazi['month']['g']], 'earth': bz.gEarthstem[bazi['month']['e']]},
                'day': {'god': bz.gGodstem[bazi['day']['g']], 'earth': bz.gEarthstem[bazi['day']['e']]},
                'hour': {'god': bz.gGodstem[bazi['hour']['g']], 'earth': bz.gEarthstem[bazi['hour']['e']]}
            },
            'lunar': lunar,
            'lunar_formatted': lunar_formatted if not solar_term else f"{solar_term} {solar_term_time}",
            'lunar_title': lunar_data_formatted['m'] if not solar_term else solar_term,
            'lunar_title_3char': True if not solar_term and len(lunar_data_formatted['m']) >= 3 else False,
            'lunar_desc': lunar_data_formatted['d'] if not solar_term else solar_term_time,
            'lunar_leap': lunar_data_formatted['l'],
            'jianchu': jianchu,
            'yellow_black_path': yellow_black_path,
            'has_solar_term': has_solar_term,
            'solar_term_index': solar_term_index,
            'month_earth': month_earth,
            'month_element': month_element,
        })
    
    # Split into weeks
    return [processed_dates[i:i + 7] for i in range(0, len(processed_dates), 7)]

def get_solar_terms(year, month):
    """Get solar terms for the given month."""
    # For January, we need to look at the previous year's data
    if month == 1:
        cycles = bz24.calc24CycleAdjustedYear(year - 1, groupByMonth=True)
        return cycles[11]  # January is the last month (index 11)
    else:
        cycles = bz24.calc24CycleAdjustedYear(year, groupByMonth=True)
        return cycles[month - 2]  # February starts at index 0

def get_month_earth_branch(solar_term_index):
    """
    Convert solar term index to month's earth branch.
    Solar terms come in pairs for each month:
    0,1 = 寅 (first month)
    2,3 = 卯 (second month)
    ...and so on
    """
    if solar_term_index is None:
        return ''
        
    try:
        # Calculate month index (0-11) from solar term index
        month_index = int(solar_term_index) // 2
        
        # Map to earth branches starting from 寅
        earth_branches = ['寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥', '子', '丑']
        return earth_branches[month_index % 12]
    except (ValueError, TypeError):
        return ''

def get_month_element(earth_branch):
    """Convert earth branch to its corresponding element."""
    # Earth branch to index mapping
    earth_to_index = {
        '子': 0, '丑': 1, '寅': 2, '卯': 3, '辰': 4, '巳': 5,
        '午': 6, '未': 7, '申': 8, '酉': 9, '戌': 10, '亥': 11
    }
    
    # Get the index for the earth branch
    index = earth_to_index.get(earth_branch)
    if index is None:
        return ''
    
    # Map earth branch index to element
    # 寅卯 (2,3) -> wood
    # 巳午 (5,6) -> fire
    # 辰未戌丑 (4,7,10,1) -> earth
    # 申酉 (8,9) -> metal
    # 亥子 (11,0) -> water
    if index in [2, 3]:
        return 'wood'
    elif index in [5, 6]:
        return 'fire'
    elif index in [4, 7, 10, 1]:
        return 'earth'
    elif index in [8, 9]:
        return 'metal'
    elif index in [11, 0]:
        return 'water'
    return ''

def get_day_data(date_obj, is_adjacent_month=False):
    """Get all the data for a specific day."""
    # Check if this is the current day
    today = datetime.now().date()
    is_today = date_obj == today

    # Get BaZi data using 22:59:59 to match YBP calculation expectations
    bazi = bz.getCalendar10kGodEarthStem(date_obj.year, date_obj.month, date_obj.day, 22, 59, 59)
    
    # Map the indices to actual values
    mapped_bazi = {
        'year': {
            'god': bz.gGodstem[bazi['year']['g']],
            'earth': bz.gEarthstem[bazi['year']['e']]
        },
        'month': {
            'god': bz.gGodstem[bazi['month']['g']],
            'earth': bz.gEarthstem[bazi['month']['e']]
        },
        'day': {
            'god': bz.gGodstem[bazi['day']['g']],
            'earth': bz.gEarthstem[bazi['day']['e']]
        },
        'hour': {
            'god': bz.gGodstem[bazi['hour']['g']],
            'earth': bz.gEarthstem[bazi['hour']['e']]
        }
    }
    
    # Get lunar calendar data
    lunar = lunar_calendar.convertLunar(datetime(date_obj.year, date_obj.month, date_obj.day))
    lunar_formatted = format_lunar_date(lunar)
    lunar_data_formatted = format_lunar_date(lunar, format='data')
    
    # Calculate Jian Chu 12 Gods and map to actual values
    jianchu_index = bz.calcJianChu12God(date_obj.year, date_obj.month, date_obj.day, bazi)
    jianchu = bz.gJianChu12God[jianchu_index]

    # Calculate Yellow/Black Path
    yellow_black_path = bz.calcYellowBlackPath(date_obj.year, date_obj.month, date_obj.day, bazi_data=bazi)

    # Get solar terms for this month
    solar_terms = get_solar_terms(date_obj.year, date_obj.month)
    solar_term = None
    solar_term_time = None

    # Get system timezone
    system_tz = timezone.get_current_timezone()

    # Check if this day is a solar term
    for term in ['first', 'second']:
        if term in solar_terms:
            # Parse UTC time
            utc_time = datetime.fromisoformat(solar_terms[term]['d'].replace('Z', '+00:00'))
            # Convert to system timezone
            # local_time = utc_time.astimezone(system_tz)
            local_time = utc_time + timedelta(hours=8)
            
            # Check if the date matches (comparing dates in local timezone)
            if local_time.date() == date_obj:
                solar_term = solar_terms[term]['c']
                # Format time as HH:MM
                solar_term_time = local_time.strftime('%H:%M')
                break
    
    # Get solar term index and calculate month's earth branch and element
    solar_term_index = None  # This should be calculated or passed differently
    month_earth = get_month_earth_branch(solar_term_index)
    month_element = get_month_element(month_earth)
    
    return {
        'date': date_obj,
        'day': date_obj.day,
        'is_adjacent_month': is_adjacent_month,
        'is_today': is_today,
        'bazi': mapped_bazi,
        'lunar': lunar,
        'lunar_formatted': lunar_formatted if not solar_term else f"{solar_term} {solar_term_time}",
        'lunar_title': lunar_data_formatted['m'] if not solar_term else solar_term,
        'lunar_title_3char': lunar_data_formatted['m'] if not solar_term and len(lunar_data_formatted['m']) >= 3 else solar_term if solar_term and len(solar_term) >= 3 else None,
        'lunar_desc': lunar_data_formatted['d'] if not solar_term else solar_term_time,
        'lunar_leap': lunar_data_formatted['l'],
        'jianchu': jianchu,
        'yellow_black_path': yellow_black_path,
        'has_solar_term': solar_term is not None,
        'solar_term_index': solar_term_index,
        'month_earth': month_earth,
        'month_element': month_element,
    }

def month_view(request, year=None, month=None):
    """Display the tongshu calendar for a specific month using the optimized TongshuCalendar model."""
    if year is None or month is None:
        today = datetime.now()
        year = today.year
        month = today.month
    
    # Calculate the first day of the month and its weekday
    first_day = date(year, month, 1)
    first_weekday = first_day.weekday()  # 0 = Monday, 6 = Sunday
    
    # Calculate the last day of the month and its weekday
    _, num_days = monthrange(year, month)
    last_day = date(year, month, num_days)
    last_weekday = last_day.weekday()
    
    # Calculate exact number of days needed before (to fill first row)
    days_before = first_weekday
    
    # Calculate exact number of days needed after
    days_after = 6 - last_weekday  # Days needed to complete the last week
    
    # If there are 3 or fewer days after, add an additional week (7 more days)
    if days_after <= 3:
        days_after += 7
    
    # Get calendar data using our optimized model with exact padding
    calendar_data = TongshuCalendar.get_month_data(year, month, extra_days=max(days_before, days_after))
    
    # Unpack the array
    year, month, month_bazi, month_element_index, days_data, prev_month, next_month = calendar_data
    
    # Transform each day's data for the template
    transformed_days = []
    
    # Process only the days we need, filtering out extra days
    current_month_days = []
    prev_month_days = []
    next_month_days = []
    
    for day in days_data:
        date_str = day[0]
        day_parts = date_str.split('-')
        day_year = int(day_parts[0])
        day_month = int(day_parts[1])
        day_day = int(day_parts[2])
        
        transformed_day = transform_day_data(day, year, month)
        
        # Separate days by month
        if day_month == month and day_year == year:
            current_month_days.append(transformed_day)
        elif (day_year < year) or (day_year == year and day_month < month):
            prev_month_days.append(transformed_day)
        else:
            next_month_days.append(transformed_day)
    
    # Sort days by date
    prev_month_days.sort(key=lambda x: x['date'])
    current_month_days.sort(key=lambda x: x['date'])
    next_month_days.sort(key=lambda x: x['date'])
    
    # Take only the days we need from previous month
    prev_month_days = prev_month_days[-days_before:] if days_before > 0 else []
    
    # Take only the days we need from next month
    next_month_days = next_month_days[:days_after] if days_after > 0 else []
    
    # Mark previous month days as adjacent
    for day in prev_month_days:
        day['is_adjacent_month'] = True
    
    # Mark next month days as adjacent
    for day in next_month_days:
        day['is_adjacent_month'] = True
    
    # Combine all days in the right order
    all_days = prev_month_days + current_month_days + next_month_days
    
    # Group days by weeks (7 days per row)
    weeks = [all_days[i:i+7] for i in range(0, len(all_days), 7)]
    
    # Get previous and next month links
    prev_year, prev_month_num = prev_month
    next_year, next_month_num = next_month
    
    # Generate a range of years for the dropdown (1900 to 2100)
    year_range = range(1900, 2100)
    
    context = {
        'weeks': weeks,
        'year': year,
        'month': month,
        'prev_year': prev_year,
        'prev_month': prev_month_num,
        'next_year': next_year,
        'next_month': next_month_num,
        'month_name': datetime(year, month, 1).strftime('%B'),
        'year_range': year_range,
    }

    # Include logged-in user's BaZi
    try:
        if request.user.is_authenticated:
            person = Person.objects.filter(created_by=request.user, owner=True).order_by('-created_at').first()
            if person and person.bazi_result:
                def pill(key):
                    p = person.bazi_result.get(key)
                    if not p:
                        return {'g': '-', 'e': '-'}
                    g = p.get('god')
                    e = p.get('earth')
                    return {'g': bz.gGodstem.get(g, '-') if g is not None else '-',
                            'e': bz.gEarthstem.get(e, '-') if e is not None else '-',
                            'gi': g, 'ei': e}
                pillars = {
                    'year': pill('year'),
                    'month': pill('month'),
                    'day': pill('day'),
                }
                if 'hour' in person.bazi_result:
                    pillars['hour'] = pill('hour')
                # Determine day element for coloring
                day_g_index = pillars['day'].get('gi')
                day_element = None
                if day_g_index is not None:
                    fe = bz.findGodstem5E(day_g_index)
                    if fe:
                        day_element = fe.get('e')
                context['user_bazi'] = {
                    'name': person.name,
                    'pillars': pillars,
                    'day_element': day_element,
                }
    except Exception:
        pass
    
    return render(request, 'tongshu/month.html', context)

# New function to transform the compact array data to template-compatible format
def transform_day_data(day_array, current_year, current_month):
    """Transform a day array from the TongshuCalendar model into a dictionary for the template.
    
    Args:
        day_array: [date, day, isToday, bazi, lunar, lunarFormatted (optional), jianchu, solar, ybp_index]
        current_year: The year currently being viewed
        current_month: The month currently being viewed
        
    Returns:
        dict: Dictionary with keys matching the template requirements
    """
    # Handle both formats: with and without lunar_formatted
    if len(day_array) == 9:  # With lunar_formatted
        date_str, day_num, is_today, bazi, lunar, lunar_formatted, jianchu, solar, ybp_index = day_array
    else:  # Without lunar_formatted (length 8)
        date_str, day_num, is_today, bazi, lunar, jianchu, solar, ybp_index = day_array
        lunar_formatted = None
    
    # Convert date string to date object
    date_parts = date_str.split('-')
    date_obj = date(int(date_parts[0]), int(date_parts[1]), int(date_parts[2]))
    
    # Determine if this is an adjacent month (not the current month being viewed)
    is_adjacent_month = int(date_parts[1]) != current_month or int(date_parts[0]) != current_year
    
    # Extract solar term information
    solar_term_index = solar[0]
    has_solar_term = solar[1] == 1
    solar_element_index = solar[2]
    
    # Map the elements to their names
    elements = ["wood", "fire", "earth", "metal", "water"]
    month_element = elements[solar_element_index] if solar_element_index is not None else ""
    
    # Map BaZi indices to their Chinese characters
    mapped_bazi = {
        'year': {
            'god': bz.gGodstem[bazi[0]],
            'earth': bz.gEarthstem[bazi[1]]
        },
        'month': {
            'god': bz.gGodstem[bazi[2]],
            'earth': bz.gEarthstem[bazi[3]]
        },
        'day': {
            'god': bz.gGodstem[bazi[4]],
            'earth': bz.gEarthstem[bazi[5]]
        },
        'hour': {
            'god': bz.gGodstem[0],  # Default to first stem
            'earth': bz.gEarthstem[0]  # Default to first branch
        }
    }
    
    # Map lunar data
    lunar_dict = {
        'd': lunar[0],
        'm': lunar[1],
        'y': lunar[2],
        'leap': lunar[3] == 1
    }
    
    # Get Jianchu name
    jianchu_name = bz.gJianChu12God[jianchu]
    
    # Get Yellow/Black Path data using the index
    ybp_god_name = bz.getYellowBlackPathGod(ybp_index)
    ybp_is_good = bz.isYellowBlackPathGood(ybp_god_name)
    yellow_black_path = {
        'god': ybp_god_name,
        'position': ybp_index + 1,  # Convert from 0-11 to 1-12
        'type': 'good' if ybp_is_good == 'good' else 'bad'
    }
    
    # Handle lunar formatted data
    if lunar_formatted:
        formatted_data = {
            'lunar_formatted': lunar_formatted[0],
            'lunar_title': lunar_formatted[1],
            'lunar_title_3char': lunar_formatted[2] == 1,
            'lunar_desc': lunar_formatted[3],
        }
    else:
        # Generate lunar formatted data if not provided
        from .models import TongshuCalendar
        lunar_data_obj = {'d': lunar[0], 'm': lunar[1], 'y': lunar[2], 'leap': lunar[3] == 1}
        lunar_formatted_str = TongshuCalendar.format_lunar_date(lunar_data_obj)
        lunar_data_formatted = TongshuCalendar.format_lunar_date(lunar_data_obj, format="data")
        
        formatted_data = {
            'lunar_formatted': lunar_formatted_str,
            'lunar_title': lunar_data_formatted['m'],
            'lunar_title_3char': len(lunar_data_formatted['m']) >= 3,
            'lunar_desc': lunar_data_formatted['d'],
        }
    
    # Determine month earth branch based on solar term
    earth_branches = ['寅', '卯', '辰', '巳', '午', '未', '申', '酉', '戌', '亥', '子', '丑']
    month_earth = earth_branches[(solar_term_index // 2 + 2) % 12] if solar_term_index is not None else ''
    
    return {
        'date': date_obj,
        'day': day_num,
        'is_adjacent_month': is_adjacent_month,
        'is_today': is_today == 1,
        'bazi': mapped_bazi,
        'lunar': lunar_dict,
        'lunar_leap': '闰' if lunar_dict.get('leap') else '',
        'jianchu': jianchu_name,
        'yellow_black_path': yellow_black_path,
        'has_solar_term': has_solar_term,
        'solar_term_index': solar_term_index,
        'month_earth': month_earth,
        'month_element': month_element,
        **formatted_data
    }
