#!/usr/bin/env python
"""
Binary Calendar Viewer

A command-line tool to view calendar data from binary files.
Supports filtering by year, month, day, and various display options.

Usage:
    python calendar_viewer.py --year 2025
    python calendar_viewer.py --year 2025 --month 3
    python calendar_viewer.py --date 2025-03-05
    python calendar_viewer.py --year 2025 --solar-terms
    python calendar_viewer.py --list-years
"""

import os
import sys
import argparse
from datetime import datetime, date, timedelta
from typing import List, Dict, Optional

# Add Django project to path
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))

import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'iching.settings')
django.setup()

from calendar10k.scripts.binary_reader import OptimizedBinaryCalendarReader, PositionalBinaryCalendarReader

# Add YBP support
from iching.utils import bz


class CalendarViewer:
    """Calendar viewer with various display and filtering options."""
    
    def __init__(self, data_dir: str = "calendar10k/data"):
        # Try positional format first (v3.0.0+ with YBP), fall back to older formats
        self.positional_reader = PositionalBinaryCalendarReader(data_dir)
        self.optimized_reader = OptimizedBinaryCalendarReader(data_dir)
        self.data_dir = data_dir
    
    def get_reader_for_year(self, year):
        """Get the appropriate reader for a year, checking format version."""
        try:
            # Try positional format first (v3.0.0+ includes YBP, v4.0.0+ includes HH:MM:SS)
            version, _, _ = self.positional_reader.get_positional_year_data(year)
            return self.positional_reader, 'positional', version
        except (FileNotFoundError, ValueError):
            try:
                # Fall back to optimized format (no YBP, minute precision only)
                version, _ = self.optimized_reader.get_year_data(year)
                return self.optimized_reader, 'optimized', version
            except (FileNotFoundError, ValueError):
                return None, None, None

    def list_available_years(self):
        """List all available years."""
        years = self.positional_reader.list_available_years()
        
        if not years:
            print("No calendar data files found.")
            print(f"Data directory: {self.data_dir}")
            return
        
        print(f"Available years ({len(years)} files):")
        print(f"Data directory: {self.data_dir}")
        print()
        
        # Group years for better display
        year_ranges = []
        start = years[0]
        end = years[0]
        
        for i in range(1, len(years)):
            if years[i] == years[i-1] + 1:
                end = years[i]
            else:
                if start == end:
                    year_ranges.append(f"{start}")
                else:
                    year_ranges.append(f"{start}-{end}")
                start = end = years[i]
        
        # Add the last range
        if start == end:
            year_ranges.append(f"{start}")
        else:
            year_ranges.append(f"{start}-{end}")
        
        print("Year ranges: " + ", ".join(year_ranges))
        
        # Show file sizes and formats
        total_size = 0
        positional_count = 0
        optimized_count = 0
        
        for year in years[:5]:  # Show info for first 5 files
            reader, format_type, version = self.get_reader_for_year(year)
            if reader:
                file_info = reader.get_file_info(year)
                if file_info and 'error' not in file_info:
                    total_size += file_info['file_size']
                    format_info = f" (v{version}, {format_type})"
                    print(f"  {year}.bin: {file_info['file_size']:,} bytes, {file_info['days_count']} days{format_info}")
                    
                    if format_type == 'positional':
                        positional_count += 1
                    else:
                        optimized_count += 1
        
        if len(years) > 5:
            print(f"  ... and {len(years) - 5} more files")
            # Count remaining files
            for year in years[5:]:
                reader, format_type, version = self.get_reader_for_year(year)
                if reader:
                    file_info = reader.get_file_info(year)
                    if file_info and 'error' not in file_info:
                        total_size += file_info['file_size']
                        if format_type == 'positional':
                            positional_count += 1
                        else:
                            optimized_count += 1
        
        # Estimate total size
        if years:
            print(f"\nTotal size: {total_size:,} bytes ({total_size/1024/1024:.1f} MB)")
            if positional_count > 0:
                print(f"Positional format (v3.0.0+ with YBP): {positional_count} files")
            if optimized_count > 0:
                print(f"Optimized format (v1.0.0): {optimized_count} files")
    
    def view_date(self, target_date: date, verbose: bool = False):
        """View calendar information for a specific date."""
        print(f"Calendar information for {target_date}:")
        print("=" * 50)
        
        # Try positional format first
        reader, format_type, version = self.get_reader_for_year(target_date.year)
        
        if not reader:
            print(f"No data found for {target_date}")
            print(f"Supported date range: 1550-01-01 to 2648-12-31")
            return
        
        # Get date info based on format
        if format_type == 'positional':
            date_info = reader.get_date_info_positional(target_date)
        else:
            date_info = reader.get_date_info(target_date)
        
        if not date_info:
            print(f"No data found for {target_date}")
            return
        
        # Basic information
        print(f"Gregorian Date: {date_info['date']}")
        print(f"Lunar Date:     {date_info['lunar_date']} {'(Leap Month)' if date_info['leap_flag'] else ''}")
        print()
        
        # Solar term information
        solar_term_name = reader.get_solar_term_name(date_info['jq_index'])
        print(f"Solar Term:     {solar_term_name} (#{date_info['jq_index']})")
        if date_info['is_solar_term']:
            # Check if this has full seconds precision (v4.0.0+)
            major_version = int(version.split('.')[0]) if version else 0
            precision_note = " (HH:MM:SS precision)" if major_version >= 4 else " (minute precision)"
            print(f"*** Solar term starts at {date_info['jq_time']}{precision_note} ***")
        else:
            print(f"                (starts at 00:00:00)")
        print()
        
        # Jian Chu information
        jc_name = reader.get_jian_chu_name(date_info['jc_index'])
        print(f"Jian Chu:       {jc_name} (#{date_info['jc_index']})")
        print()
        
        # Yellow/Black Path information (if available in v3.0.0+)
        if 'ybp_index' in date_info and format_type == 'positional' and version and (version.startswith('3.') or version.startswith('4.')):
            ybp_name = bz.getYellowBlackPathGod(date_info['ybp_index'])
            ybp_classification = bz.isYellowBlackPathGood(ybp_name)
            ybp_type_chinese = '黄道' if ybp_classification == 'good' else '黑道'
            print(f"Yellow/Black:   {ybp_name} (#{date_info['ybp_index']}, {ybp_type_chinese})")
            print()
        
        if verbose:
            print("Technical details:")
            print(f"  File version:   {version} ({format_type} format)")
            if format_type == 'positional':
                print(f"  Day index:      {date_info.get('day_index', 'N/A')} (within year)")
                print(f"  Lunar day diff: {date_info.get('lunar_day_diff', 'N/A')}")
            else:
                print(f"  Days since base: {date_info.get('normal_days', 'N/A')} (normal), {date_info.get('lunar_days', 'N/A')} (lunar)")
            print(f"  Binary flags:   leap={date_info['leap_flag']}, jc={date_info['jc_index']}, jq={date_info['jq_index']}")
    
    def view_month(self, year: int, month: int, show_lunar: bool = False, show_solar_terms: bool = False, show_ybp: bool = None):
        """View calendar information for a specific month."""
        print(f"Calendar for {year}-{month:02d}:")
        print("=" * 70)
        
        # Get month data
        start_date = date(year, month, 1)
        
        # Calculate last day of month
        if month == 12:
            end_date = date(year + 1, 1, 1) - timedelta(days=1)
        else:
            end_date = date(year, month + 1, 1) - timedelta(days=1)
        
        # Get appropriate reader
        reader, format_type, version = self.get_reader_for_year(year)
        
        if not reader:
            print(f"No data found for {year}-{month:02d}")
            return
        
        # Check if YBP is available and set default behavior (v3.0.0+)
        has_ybp = format_type == 'positional' and version and (version.startswith('3.') or version.startswith('4.'))
        
        # If show_ybp is None (default), enable YBP display if available
        if show_ybp is None:
            show_ybp = has_ybp
        elif show_ybp and not has_ybp:
            print(f"Warning: YBP data not available for {year} (requires v3.0.0+ positional format)")
            show_ybp = False
        
        # Get month data based on format
        if format_type == 'positional':
            month_data = reader.get_date_range_positional(start_date, end_date)
        else:
            month_data = reader.get_date_range(start_date, end_date)
        
        if not month_data:
            print(f"No data found for {year}-{month:02d}")
            return
        
        # Header
        header = "Day  Date       "
        if show_lunar:
            header += "Lunar      "
        header += "Solar Term          Jian Chu"
        if show_ybp:
            header += "    Yellow/Black"
        if show_solar_terms:
            header += "  Time "
        
        print(header)
        print("-" * len(header))
        
        # Data rows
        for day_info in month_data:
            day = day_info['date'].day
            date_str = day_info['date'].strftime("%Y-%m-%d")
            
            row = f"{day:2d}   {date_str} "
            
            if show_lunar:
                # lunar_date is now a string in positional format
                if isinstance(day_info['lunar_date'], str):
                    lunar_str = day_info['lunar_date']
                else:
                    lunar_str = day_info['lunar_date'].strftime("%Y-%m-%d")
                    
                if day_info['leap_flag']:
                    lunar_str += "*"
                row += f"{lunar_str:10s} "
            
            solar_term = reader.get_solar_term_name(day_info['jq_index'])
            jc_name = reader.get_jian_chu_name(day_info['jc_index'])
            
            row += f"{solar_term:12s}        {jc_name}"
            
            if show_ybp and 'ybp_index' in day_info:
                ybp_name = bz.getYellowBlackPathGod(day_info['ybp_index'])
                ybp_classification = bz.isYellowBlackPathGood(ybp_name)
                ybp_symbol = '○' if ybp_classification == 'good' else '●'
                row += f"    {ybp_symbol}{ybp_name}"
            
            if show_solar_terms and day_info['is_solar_term']:
                row += f"  {day_info['jq_time']}"
            
            print(row)
        
        # Summary
        print()
        solar_term_days = sum(1 for d in month_data if d['is_solar_term'])
        leap_days = sum(1 for d in month_data if d['leap_flag'])
        
        print(f"Summary: {len(month_data)} days")
        if solar_term_days > 0:
            print(f"  Solar terms: {solar_term_days} days")
        if leap_days > 0:
            print(f"  Lunar leap month: {leap_days} days")
    
    def view_year(self, year: int, summary_only: bool = False):
        """View calendar information for a specific year."""
        print(f"Calendar for {year}:")
        print("=" * 50)
        
        # Get appropriate reader
        reader, format_type, version = self.get_reader_for_year(year)
        
        if not reader:
            print(f"No data found for year {year}")
            return
        
        file_info = reader.get_file_info(year)
        if not file_info or 'error' in file_info:
            print(f"No data found for year {year}")
            return
        
        print(f"File: {file_info['filename']}")
        print(f"Version: {version} ({format_type} format)")
        print(f"Size: {file_info['file_size']:,} bytes")
        print(f"Days: {file_info['days_count']}")
        print(f"Date range: {file_info['date_range']}")
        print()
        
        if summary_only:
            return
        
        # Get solar terms for the year
        if format_type == 'positional':
            solar_terms = reader.get_solar_terms_in_year_positional(year)
        else:
            solar_terms = reader.get_solar_terms_in_year(year)
        
        print(f"Solar Terms ({len(solar_terms)}):")
        print("-" * 40)
        
        for i, term in enumerate(solar_terms):
            season = ""
            if 0 <= term['jq_index'] <= 5:
                season = "Spring"
            elif 6 <= term['jq_index'] <= 11:
                season = "Summer"
            elif 12 <= term['jq_index'] <= 17:
                season = "Autumn"
            elif 18 <= term['jq_index'] <= 23:
                season = "Winter"
            
            print(f"{term['jq_index']:2d}. {term['name']} - {term['date']} at {term['jq_time']} ({season})")
        
        # Additional statistics
        try:
            if format_type == 'positional':
                version, solar_terms_data, year_data = reader.get_positional_year_data(year)
            else:
                version, year_data = reader.get_year_data(year)
            
            leap_days = sum(1 for d in year_data if d.get('leap_flag', False))
            unique_jc = len(set(d.get('jc_index', 0) for d in year_data if 'jc_index' in d))
            unique_jq = len(set(term['jq_index'] for term in solar_terms))
            
            print()
            print("Statistics:")
            print(f"  Lunar leap month days: {leap_days}")
            print(f"  Unique Jian Chu indices: {unique_jc}/12")
            print(f"  Unique Solar Term indices: {unique_jq}/24")
            
        except Exception as e:
            print(f"Error calculating statistics: {e}")
    
    def view_solar_terms(self, year: int, season: Optional[str] = None):
        """View solar terms for a specific year, optionally filtered by season."""
        print(f"Solar Terms for {year}:")
        print("=" * 60)
        
        # Get appropriate reader
        reader, format_type, version = self.get_reader_for_year(year)
        
        if not reader:
            print(f"No solar terms data found for {year}")
            return
        
        # Get solar terms based on format
        if format_type == 'positional':
            solar_terms = reader.get_solar_terms_in_year_positional(year)
        else:
            solar_terms = reader.get_solar_terms_in_year(year)
        
        if not solar_terms:
            print(f"No solar terms data found for {year}")
            return
        
        # Filter by season if specified
        if season:
            season_lower = season.lower()
            season_ranges = {
                'spring': (0, 5),
                'summer': (6, 11),
                'autumn': (12, 17),
                'fall': (12, 17),
                'winter': (18, 23)
            }
            
            if season_lower in season_ranges:
                start_idx, end_idx = season_ranges[season_lower]
                solar_terms = [t for t in solar_terms if start_idx <= t['jq_index'] <= end_idx]
                print(f"Season: {season.title()}")
                print()
        
        # Display terms
        for term in solar_terms:
            # Calculate days until next term
            term_date = term['date']
            next_term_date = None
            
            for next_term in solar_terms:
                if next_term['jq_index'] == (term['jq_index'] + 1) % 24:
                    next_term_date = next_term['date']
                    break
            
            days_duration = ""
            if next_term_date:
                duration = (next_term_date - term_date).days
                days_duration = f" ({duration} days)"
            
            weekday = term_date.strftime("%A")
            print(f"{term['jq_index']:2d}. {term['name']:6s} - {term['date']} ({weekday}) at {term['jq_time']}{days_duration}")


def main():
    parser = argparse.ArgumentParser(
        description="View binary calendar data with various filtering options",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python calendar_viewer.py --list-years
  python calendar_viewer.py --year 2025
  python calendar_viewer.py --year 2025 --month 3
  python calendar_viewer.py --date 2025-03-05
  python calendar_viewer.py --year 2025 --solar-terms
  python calendar_viewer.py --year 2025 --solar-terms --season spring
        """
    )
    
    parser.add_argument('--data-dir', default='calendar10k/data',
                       help='Directory containing binary calendar files')
    
    parser.add_argument('--list-years', action='store_true',
                       help='List all available years')
    
    parser.add_argument('--year', type=int,
                       help='Year to display (1550-2648)')
    
    parser.add_argument('--month', type=int,
                       help='Month to display (1-12, requires --year)')
    
    parser.add_argument('--date',
                       help='Specific date to display (YYYY-MM-DD)')
    
    parser.add_argument('--solar-terms', action='store_true',
                       help='Show solar terms for the year')
    
    parser.add_argument('--season', choices=['spring', 'summer', 'autumn', 'fall', 'winter'],
                       help='Filter solar terms by season')
    
    parser.add_argument('--summary', action='store_true',
                       help='Show only summary information')
    
    parser.add_argument('--show-lunar', action='store_true',
                       help='Show lunar calendar dates in month view')
    
    parser.add_argument('--show-solar-term-times', action='store_true',
                       help='Show solar term times in month view')
    
    parser.add_argument('--show-ybp', action='store_true',
                       help='Force show Yellow/Black Path (黄黑道) in month view (shown by default if available in v3.0.0+)')
    
    parser.add_argument('--no-ybp', action='store_true',
                       help='Disable Yellow/Black Path (黄黑道) display in month view')
    
    parser.add_argument('--verbose', action='store_true',
                       help='Show additional technical details')
    
    args = parser.parse_args()
    
    # Initialize viewer
    viewer = CalendarViewer(args.data_dir)
    
    # Handle different view modes
    if args.list_years:
        viewer.list_available_years()
    
    elif args.date:
        try:
            target_date = datetime.strptime(args.date, '%Y-%m-%d').date()
            viewer.view_date(target_date, args.verbose)
        except ValueError:
            print(f"Invalid date format: {args.date}. Use YYYY-MM-DD")
            sys.exit(1)
    
    elif args.year:
        if args.solar_terms:
            viewer.view_solar_terms(args.year, args.season)
        elif args.month:
            if not (1 <= args.month <= 12):
                print(f"Invalid month: {args.month}. Use 1-12")
                sys.exit(1)
            # Always show lunar dates in month view, YBP shown by default if available
            # Determine YBP display preference
            show_ybp = None  # Default: auto-detect based on availability
            if args.show_ybp:
                show_ybp = True  # Force show
            elif args.no_ybp:
                show_ybp = False  # Force hide
            
            viewer.view_month(args.year, args.month, True, args.show_solar_term_times, show_ybp)
        else:
            viewer.view_year(args.year, args.summary)
    
    else:
        print("Please specify what to view. Use --help for options.")
        parser.print_help()


if __name__ == "__main__":
    main() 