"""
Smart Timetable System with Automatic Day Order Calculation

This module handles:
1. Day order calculation based on a reference date and working day rotation
2. Timetable data storage and retrieval
3. Working day computation (excludes Sundays)
"""

from datetime import datetime, timedelta
from typing import Tuple, Dict, List


# Reference date: Sunday, 2026-02-15 is the start (Day 0)
# Monday, 2026-02-16 is Day 1 of the rotation... but wait, user says 2026-02-16 is Day 3
# Let me recalculate: if 2026-02-16 is Day 3, then we need to find what day of week it is
# and work backwards to find the reference point

# 2026-02-16 is a Monday. If it's Day 3, then:
# Day 1 would be 2 working days before, which is Friday 2026-02-13 (counting backwards)
# But we need to skip Sundays when counting back

# Let's verify the logic:
# Reference: 2026-02-16 (Monday) = Day 3
# Working backwards (excluding Sundays):
# 2026-02-16 (Monday) = Day 3
# 2026-02-15 (Sunday) = skip
# 2026-02-14 (Saturday) = Day 2
# 2026-02-13 (Friday) = Day 1
# 2026-02-12 (Thursday) = Day 6 (previous cycle)
# 2026-02-11 (Wednesday) = Day 5
# 2026-02-10 (Tuesday) = Day 4

REFERENCE_DATE = datetime(2026, 2, 16)  # This is Day 3
REFERENCE_DAY_ORDER = 3


# Timetable data: maps day order (1-6) to list of periods
# Each period includes (time_slot, subject, faculty)
# Time slots: 1. 10-11 AM, 2. 11-12 Noon, 3. 12-12:15 (BREAK), 4. 12:15-1 PM, 
#             5. 1-1:15 (LUNCH), 6. 1:15-2 PM, 7. 2-3 PM, 8. 3-4 PM

TIMETABLE: Dict[int, List[Tuple[str, str, str]]] = {
    1: [  # Day I
        ("10:00-11:00 AM", "Programming in Python (23SS5P636)", "Dr. R. Umanandhi (RU)"),
        ("11:00-12:00 Noon", "Big Data Analytics (23SS5P38B)", "Dr. N. Sathya (NS)"),
        ("12:00-12:15 PM", "BREAK", ""),
        ("12:15-01:00 PM", "Machine Learning (23SS5P634)", "Dr. A. Subhashini (AS)"),
        ("01:00-01:15 PM", "LUNCH", ""),
        ("01:15-02:00 PM", "Open-Source Technologies (23SS5P635)", "Lt. Dr. D. Antony Arul Raj (DA)"),
        ("02:00-03:00 PM", "Agile Software Development (23SS5P637)", "Dr. V. Usharani (VU)"),
    ],
    2: [  # Day II
        ("10:00-11:00 AM", "Practical XIV - ML Lab (23SS5P639)", "Dr. A. Subhashini (AS), Dr. G. S. Karthick (GSK), Dr. G. Karthikeyan (GK) [E110]"),
        ("11:00-12:00 Noon", "Practical XIV - ML Lab (23SS5P639)", "Dr. A. Subhashini (AS), Dr. G. S. Karthick (GSK), Dr. G. Karthikeyan (GK) [E110]"),
        ("12:00-12:15 PM", "BREAK", ""),
        ("12:15-01:00 PM", "Practical XIV - ML Lab (23SS5P639)", "Dr. A. Subhashini (AS), Dr. G. S. Karthick (GSK), Dr. G. Karthikeyan (GK) [E110]"),
        ("01:00-01:15 PM", "LUNCH", ""),
        ("01:15-02:00 PM", "Agile Software Development (23SS5P637)", "Dr. V. Usharani (VU)"),
        ("02:00-03:00 PM", "Programming in Python (23SS5P636)", "Dr. R. Umanandhi (RU)"),
    ],
    3: [  # Day III
        ("10:00-11:00 AM", "Big Data Analytics (23SS5P38B)", "Dr. N. Sathya (NS)"),
        ("11:00-12:00 Noon", "Open-Source Technologies (23SS5P635)", "Lt. Dr. D. Antony Arul Raj (DA)"),
        ("12:00-12:15 PM", "BREAK", ""),
        ("12:15-01:00 PM", "Programming in Python (23SS5P636)", "Dr. R. Umanandhi (RU)"),
        ("01:00-01:15 PM", "LUNCH", ""),
        ("01:15-02:00 PM", "Agile Software Development (23SS5P637)", "Dr. V. Usharani (VU)"),
        ("02:00-03:00 PM", "Machine Learning (23SS5P634)", "Dr. A. Subhashini (AS)"),
    ],
    4: [  # Day IV
        ("10:00-11:00 AM", "Machine Learning (23SS5P634)", "Dr. A. Subhashini (AS)"),
        ("11:00-12:00 Noon", "Professional Competency Skill (23PCS1J603)", "Dr. K. V. Rukmani (KVR)"),
        ("12:00-12:15 PM", "BREAK", ""),
        ("12:15-01:00 PM", "Big Data Analytics (23SS5P38B)", "Dr. N. Sathya (NS)"),
        ("01:00-01:15 PM", "LUNCH", ""),
        ("01:15-02:00 PM", "Practical XV - OST Lab (23SS5P640)", "Lt. Dr. D. Antony Arul Raj (DA), Dr. G. Karthikeyan (GK) [E110]"),
        ("02:00-03:00 PM", "Practical XV - OST Lab (23SS5P640)", "Lt. Dr. D. Antony Arul Raj (DA), Dr. G. Karthikeyan (GK) [E110]"),
    ],
    5: [  # Day V
        ("10:00-11:00 AM", "Agile Software Development (23SS5P637)", "Dr. V. Usharani (VU)"),
        ("11:00-12:00 Noon", "Open-Source Technologies (23SS5P635)", "Lt. Dr. D. Antony Arul Raj (DA)"),
        ("12:00-12:15 PM", "BREAK", ""),
        ("12:15-01:00 PM", "Machine Learning (23SS5P634)", "Dr. A. Subhashini (AS)"),
        ("01:00-01:15 PM", "LUNCH", ""),
        ("01:15-02:00 PM", "Practical XVI - Python Lab (23SS5P641)", "Dr. R. Umanandhi (RU), Dr. G. Karthikeyan (GK) [E311]"),
        ("02:00-03:00 PM", "Practical XVI - Python Lab (23SS5P641)", "Dr. R. Umanandhi (RU), Dr. G. Karthikeyan (GK) [E311]"),
    ],
    6: [  # Day VI
        ("10:00-11:00 AM", "Programming in Python (23SS5P636)", "Dr. R. Umanandhi (RU)"),
        ("11:00-12:00 Noon", "Big Data Analytics (23SS5P38B)", "Dr. N. Sathya (NS)"),
        ("12:00-12:15 PM", "BREAK", ""),
        ("12:15-01:00 PM", "Machine Learning (23SS5P634)", "Dr. A. Subhashini (AS)"),
        ("01:00-01:15 PM", "LUNCH", ""),
        ("01:15-02:00 PM", "Professional Competency Skill (23PCS1J603)", "Dr. K. V. Rukmani (KVR)"),
        ("02:00-03:00 PM", "Open-Source Technologies (23SS5P635)", "Lt. Dr. D. Antony Arul Raj (DA)"),
    ],
}


def is_working_day(date: datetime) -> bool:
    """
    Check if a date is a working day (Monday-Saturday).
    
    Args:
        date: datetime object to check
        
    Returns:
        True if Monday-Saturday, False if Sunday
    """
    # weekday(): Monday=0, Sunday=6
    return date.weekday() != 6


def count_working_days_between(start_date: datetime, end_date: datetime) -> int:
    """
    Count the number of working days (Mon-Sat) between two dates.
    
    Args:
        start_date: Starting date (inclusive)
        end_date: Ending date (inclusive)
        
    Returns:
        Number of working days between the dates
    """
    count = 0
    current = start_date
    
    while current <= end_date:
        if is_working_day(current):
            count += 1
        current += timedelta(days=1)
    
    return count


def get_day_order(date: datetime) -> int:
    """
    Calculate the day order (1-6) for a given date.
    
    The algorithm:
    1. Count working days between reference date and target date
    2. For dates after reference: offset = working_days - 1
    3. For dates before reference: offset = -(working_days - 1)
    4. Apply modulo 6 and add 1 to get day order (1-6)
    
    Sundays are excluded from the rotation.
    
    Args:
        date: datetime object for which to calculate day order
        
    Returns:
        Day order (1-6)
        
    Example:
        2026-02-16 (Monday, reference) -> Day 3
        2026-02-17 (Tuesday) -> Day 4
        2026-02-15 (Sunday) -> Day 3 (same day as 2026-02-16, since Sunday is skipped)
    """
    # Ensure we're comparing dates without time
    date = date.replace(hour=0, minute=0, second=0, microsecond=0)
    ref_date = REFERENCE_DATE.replace(hour=0, minute=0, second=0, microsecond=0)
    
    if date == ref_date:
        return REFERENCE_DAY_ORDER
    
    # Count working days between dates (inclusive on both ends)
    working_days = count_working_days_between(
        min(date, ref_date), max(date, ref_date)
    )
    
    # Adjust for inclusive counting (subtract 1 to get actual days between)
    days_between = working_days - 1
    
    if date < ref_date:
        # For past dates: subtract from reference day order
        day_order = REFERENCE_DAY_ORDER - days_between
    else:
        # For future dates: add to reference day order
        day_order = REFERENCE_DAY_ORDER + days_between
    
    # Normalize to 1-6 range using modulo arithmetic
    day_order = ((day_order - 1) % 6) + 1
    
    return day_order


def get_next_working_day(date: datetime) -> datetime:
    """
    Get the next working day (Mon-Sat) after the given date.
    
    Args:
        date: Starting date
        
    Returns:
        Next working day after the given date
    """
    next_day = date + timedelta(days=1)
    while not is_working_day(next_day):
        next_day += timedelta(days=1)
    return next_day


def get_timetable_for_day(day_order: int) -> List[Tuple[str, str, str]]:
    """
    Get the timetable for a specific day order.
    
    Args:
        day_order: Day order (1-6)
        
    Returns:
        List of (time_slot, subject, faculty) tuples for that day
        
    Raises:
        ValueError: If day_order is not between 1 and 6
    """
    if not 1 <= day_order <= 6:
        raise ValueError(f"Day order must be between 1 and 6, got {day_order}")
    
    return TIMETABLE.get(day_order, [])


def format_timetable_output(date: datetime, day_order: int) -> str:
    """
    Format the timetable output as a readable string.
    
    Args:
        date: Date to display
        day_order: Day order number
        
    Returns:
        Formatted timetable string
    """
    timetable = get_timetable_for_day(day_order)
    
    # Format date as YYYY-MM-DD
    date_str = date.strftime("%Y-%m-%d")
    day_name = date.strftime("%A")
    
    # Build output
    lines = [
        f"Date: {date_str} ({day_name})",
        f"Day Order: {day_order}",
        "",
        "=" * 80,
    ]
    
    for period_num, (time_slot, subject, faculty) in enumerate(timetable, 1):
        lines.append(f"\nPeriod {period_num}: {time_slot}")
        if subject.upper() not in ("BREAK", "LUNCH"):
            lines.append(f"  Subject: {subject}")
            if faculty:
                lines.append(f"  Faculty: {faculty}")
        else:
            lines.append(f"  ({subject.upper()})")
    
    lines.append("\n" + "=" * 80)
    return "\n".join(lines)


def get_today_timetable() -> str:
    """Get timetable for today."""
    today = datetime.now()
    # If today is Sunday, get the day order for the previous working day
    if not is_working_day(today):
        # Get last working day
        prev_day = today - timedelta(days=1)
        while not is_working_day(prev_day):
            prev_day -= timedelta(days=1)
        today = prev_day
    
    day_order = get_day_order(today)
    return format_timetable_output(today, day_order)


def get_tomorrow_timetable() -> str:
    """Get timetable for tomorrow (next working day)."""
    today = datetime.now()
    tomorrow = get_next_working_day(today)
    day_order = get_day_order(tomorrow)
    return format_timetable_output(tomorrow, day_order)


def get_day_timetable(day_order: int) -> str:
    """
    Get timetable for a specific day order.
    
    Args:
        day_order: Day order (1-6)
        
    Returns:
        Formatted timetable string
    """
    if not 1 <= day_order <= 6:
        raise ValueError(f"Day order must be between 1 and 6, got {day_order}")
    
    # Find the next occurrence of this day order
    today = datetime.now()
    current = today
    
    while True:
        if is_working_day(current):
            if get_day_order(current) == day_order:
                return format_timetable_output(current, day_order)
        current += timedelta(days=1)
