"""Polling scheduler — 시간대별 분기 + 휴장일 처리."""
from __future__ import annotations
import json
import logging
from datetime import datetime, timedelta, time
from pathlib import Path
from zoneinfo import ZoneInfo

logger = logging.getLogger(__name__)

KST = ZoneInfo("Asia/Seoul")
_HOLIDAYS_PATH = Path(__file__).parent / "holidays.json"
_HOLIDAYS: set[str] = set(json.loads(_HOLIDAYS_PATH.read_text(encoding="utf-8")))

# Market windows (정규장)
_PRE_OPEN = time(7, 0)
_OPEN = time(9, 0)
_CLOSE = time(15, 30)
_POST_END = time(20, 0)

# NXT windows (시간외)
_NXT_PRE_END = time(23, 30)
_NXT_POST_OPEN = time(4, 30)
# 23:30 - 04:30 (dead zone) skip


def _is_market_day(now: datetime) -> bool:
    """평일 + 휴장일 아닌 날."""
    if now.weekday() >= 5:  # Sat/Sun
        return False
    return now.strftime("%Y-%m-%d") not in _HOLIDAYS


def _is_polling_window(now: datetime) -> bool:
    """폴링 윈도우: 07:00-23:30 + 04:30-07:00."""
    t = now.time()
    return (
        (_PRE_OPEN <= t < _NXT_PRE_END)
        or (_NXT_POST_OPEN <= t < _PRE_OPEN)
    )


def _next_interval(now: datetime) -> float:
    """다음 폴링까지 sleep 초수."""
    if not _is_market_day(now):
        return _seconds_until_next_market_open(now)

    t = now.time()
    if _PRE_OPEN <= t < _OPEN:
        return 300.0  # 장전 5분
    elif _OPEN <= t < _CLOSE:
        return 60.0   # 장중 1분
    elif _CLOSE <= t < _POST_END:
        return 300.0  # 장후 5분
    elif _POST_END <= t < _NXT_PRE_END:
        return 300.0  # NXT 야간 5분
    elif _NXT_POST_OPEN <= t < _PRE_OPEN:
        return 300.0  # NXT 새벽 5분
    else:
        # Dead zone (23:30-04:30) — wait until next 04:30
        return _seconds_until_nxt_or_market_open(now)


def _seconds_until_nxt_or_market_open(now: datetime) -> float:
    """다음 04:30 (NXT 새벽 start) 까지 초수. 휴장일은 다음 영업일 07:00."""
    candidate = now.replace(hour=4, minute=30, second=0, microsecond=0)
    if candidate <= now:
        candidate += timedelta(days=1)

    for _ in range(14):
        if _is_market_day(candidate):
            return (candidate - now).total_seconds()
        candidate += timedelta(days=1)

    logger.warning("could not find next market day within 14 days")
    return 86400.0


def _is_post_close_trigger(now: datetime, last_post_close_date) -> bool:
    """F3 — 16:00 KST 이후 오늘 아직 post-close cycle 안 돌렸으면 True (상태기반).

    이전엔 16:00:00-16:00:59 1분 윈도우라 5분 sleep + 비결정적 cycle 시작시각
    조합으로 영영 못 잡는 경우 발생 (예: cycle이 15:31에 시작되면 16:01에 깸).

    Args:
        now: 현재 KST datetime.
        last_post_close_date: 마지막 post-close 실행 영업일 date (None=미실행).
    """
    if not _is_market_day(now):
        return False
    if now.time() < time(16, 0):
        return False
    return last_post_close_date != now.date()


def _seconds_until_next_market_open(now: datetime) -> float:
    """다음 영업일의 07:00 KST 까지 초수 (휴장일/주말용)."""
    candidate = now.replace(hour=7, minute=0, second=0, microsecond=0)
    if candidate <= now:
        candidate += timedelta(days=1)

    for _ in range(14):  # safety bound (max 2 weeks of holidays)
        if _is_market_day(candidate):
            return (candidate - now).total_seconds()
        candidate += timedelta(days=1)

    logger.warning("could not find next market day within 14 days")
    return 86400.0
