"""Trading window utilities for the autonomous trading engine.

Pure computation module that determines whether a given timestamp falls
within the allowed trading window (9:45 AM – 3:45 PM ET on weekdays),
whether the US market is open, and when the next trading window opens.

Uses ``zoneinfo.ZoneInfo("America/New_York")`` for Eastern Time handling.
Checks major US market holidays for 2026.
"""

from __future__ import annotations

from datetime import date, datetime, time, timedelta
from zoneinfo import ZoneInfo

# US Eastern timezone
ET = ZoneInfo("America/New_York")

# Trading window boundaries (excludes first/last 15 min of market hours)
WINDOW_OPEN = time(9, 45)
WINDOW_CLOSE = time(15, 45)

# Full US market hours
MARKET_OPEN = time(9, 30)
MARKET_CLOSE = time(16, 0)

# Weekday range: Monday=0 … Friday=4
_WEEKDAYS = range(0, 5)


def _us_market_holidays_2026() -> set[date]:
    """Return a set of US market holiday dates for 2026.

    Major holidays observed by NYSE/NASDAQ:
    - New Year's Day (Jan 1)
    - MLK Day (3rd Monday of January)
    - Presidents' Day (3rd Monday of February)
    - Good Friday (April 3)
    - Memorial Day (last Monday of May)
    - Juneteenth (June 19)
    - Independence Day (July 3 observed — July 4 is Saturday)
    - Labor Day (1st Monday of September)
    - Thanksgiving (4th Thursday of November)
    - Christmas (Dec 25)
    """
    return {
        date(2026, 1, 1),    # New Year's Day
        date(2026, 1, 19),   # MLK Day (3rd Monday)
        date(2026, 2, 16),   # Presidents' Day (3rd Monday)
        date(2026, 4, 3),    # Good Friday
        date(2026, 5, 25),   # Memorial Day (last Monday)
        date(2026, 6, 19),   # Juneteenth
        date(2026, 7, 3),    # Independence Day (observed)
        date(2026, 9, 7),    # Labor Day (1st Monday)
        date(2026, 11, 26),  # Thanksgiving (4th Thursday)
        date(2026, 12, 25),  # Christmas
    }


_HOLIDAYS_2026 = _us_market_holidays_2026()


def is_within_trading_window(dt: datetime) -> bool:
    """Return True if *dt* is between 9:45 AM ET and 3:45 PM ET on a weekday.

    The timestamp is first converted to US/Eastern time.  Weekends and
    US market holidays (2026) are always outside the window.
    """
    et_dt = dt.astimezone(ET)
    if et_dt.weekday() not in _WEEKDAYS:
        return False
    if et_dt.date() in _HOLIDAYS_2026:
        return False
    t = et_dt.time()
    return WINDOW_OPEN <= t < WINDOW_CLOSE


def next_window_open(dt: datetime) -> datetime:
    """Return the next datetime when the trading window opens (9:45 AM ET).

    If *dt* is before 9:45 AM ET on a weekday the same day's open is
    returned.  Otherwise the next weekday's 9:45 AM ET is returned.
    """
    et_dt = dt.astimezone(ET)
    today_open = et_dt.replace(
        hour=WINDOW_OPEN.hour,
        minute=WINDOW_OPEN.minute,
        second=0,
        microsecond=0,
    )

    # If we haven't reached today's open yet and it's a weekday, return today
    if et_dt < today_open and et_dt.weekday() in _WEEKDAYS:
        return today_open

    # Otherwise advance to the next weekday
    candidate = et_dt + timedelta(days=1)
    candidate = candidate.replace(
        hour=WINDOW_OPEN.hour,
        minute=WINDOW_OPEN.minute,
        second=0,
        microsecond=0,
    )
    while candidate.weekday() not in _WEEKDAYS:
        candidate += timedelta(days=1)
    return candidate


def is_market_open(dt: datetime) -> bool:
    """Return True if *dt* is during US market hours (9:30 AM – 4:00 PM ET) on a weekday.

    Returns False on weekends and US market holidays (2026).
    """
    et_dt = dt.astimezone(ET)
    if et_dt.weekday() not in _WEEKDAYS:
        return False
    if et_dt.date() in _HOLIDAYS_2026:
        return False
    t = et_dt.time()
    return MARKET_OPEN <= t < MARKET_CLOSE
