"""Market-open/close status for the dashboard header. Pure computation —
no API needed; the schedules are known constants. Holidays are NOT modelled
(would require a region-specific calendar); a closed Monday will still show
"open" if the time-of-day fits. Good enough for the strategic dashboard.
"""
from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, time, timedelta, timezone
from zoneinfo import ZoneInfo


@dataclass(frozen=True)
class Market:
    code: str
    name: str
    tz: str          # IANA zone (handles DST automatically)
    open: time       # local time
    close: time      # local time


# Mon=0 .. Sun=6. Markets observe Mon-Fri unless overridden.
_WORKWEEK = {0, 1, 2, 3, 4}


MARKETS: list[Market] = [
    Market("NYSE",  "NYSE",     "America/New_York", time(9, 30),  time(16, 0)),
    Market("LSE",   "LSE",      "Europe/London",    time(8, 0),   time(16, 30)),
    Market("XETRA", "Frankfurt","Europe/Berlin",    time(9, 0),   time(17, 30)),
    Market("JPX",   "Tokyo",    "Asia/Tokyo",       time(9, 0),   time(15, 0)),
    Market("HKEX",  "Hong Kong","Asia/Hong_Kong",   time(9, 30),  time(16, 0)),
    Market("SSE",   "Shanghai", "Asia/Shanghai",    time(9, 30),  time(15, 0)),
]


def _next_open_at(m: Market, now_utc: datetime) -> datetime:
    """Earliest future open datetime (UTC) for this market, scanning ahead
    up to 7 days for the next weekday."""
    tz = ZoneInfo(m.tz)
    local = now_utc.astimezone(tz)
    candidate_date = local.date()
    for _ in range(8):  # today + 7 days
        weekday = candidate_date.weekday()
        if weekday in _WORKWEEK:
            local_open = datetime.combine(candidate_date, m.open, tzinfo=tz)
            if local_open > local:
                return local_open.astimezone(timezone.utc)
        candidate_date = candidate_date + timedelta(days=1)
    return now_utc + timedelta(days=7)  # fallback (shouldn't happen)


def _close_at(m: Market, now_utc: datetime) -> datetime:
    """Today's close in UTC (assumes we've already established it's open)."""
    tz = ZoneInfo(m.tz)
    local = now_utc.astimezone(tz)
    return datetime.combine(local.date(), m.close, tzinfo=tz).astimezone(timezone.utc)


def status_for(m: Market, now_utc: datetime) -> dict:
    tz = ZoneInfo(m.tz)
    local = now_utc.astimezone(tz)
    is_workday = local.weekday() in _WORKWEEK
    in_session = is_workday and m.open <= local.time() < m.close
    if in_session:
        return {
            "code": m.code,
            "name": m.name,
            "open": True,
            "until": _close_at(m, now_utc),
            "label": "open",
        }
    return {
        "code": m.code,
        "name": m.name,
        "open": False,
        "until": _next_open_at(m, now_utc),
        "label": "closed",
    }


def all_statuses(now_utc: datetime | None = None) -> list[dict]:
    if now_utc is None:
        now_utc = datetime.now(timezone.utc)
    return [status_for(m, now_utc) for m in MARKETS]
