"""TGTC intraday scheduler daemon.

Invoked as: python -m apps.tgtc_trader.daemon

Reads active sessions from the TGTC DB and runs each session through the
daily phase sequence (pre_screen → collect_snapshot → finalize_candidates →
entry_check → stop_check → eod_exit → post_close).

The daemon is started by tgtc_service.ORBAutoScheduler-style controller
via subprocess.Popen, and communicates back via .tgtc_trigger_*.json files.
"""
from __future__ import annotations

import asyncio
import datetime as dt
import logging
import os
import signal
import sys
import time
from pathlib import Path
from zoneinfo import ZoneInfo

log = logging.getLogger(__name__)
_TZ_ET = ZoneInfo("America/New_York")

_DB_ENV = os.environ.get("TGTC_TRADER_DB", "data/paper/tgtc.db")
_DRY_RUN = os.environ.get("TGTC_DRY_RUN", "1") == "1"
_LOG_FILE = os.environ.get("TGTC_LOG_FILE", "data/paper/tgtc_scheduler.log")

# Start-now mode env vars
_START_NOW = os.environ.get("TGTC_START_NOW", "0") == "1"
_COLLECT_DURATION_SECS = int(os.environ.get("TGTC_COLLECT_DURATION_SECS", "300"))
_QUICK_END_AFTER_MINS = int(os.environ.get("TGTC_QUICK_END_AFTER_MINS", "60"))

# ── Schedule definition ───────────────────────────────────────────────────────

_PRE_SCREEN_OFFSET_MINS = -10   # 09:20 ET (10 min before market open)
_COLLECTION_START = dt.time(9, 29, 30)
_COLLECTION_END   = dt.time(10, 0, 0)
_EOD_EXIT         = dt.time(15, 55)
_POST_CLOSE       = dt.time(16, 0)
_ENTRY_STOP_START = dt.time(10, 5)   # first entry/stop check
_ENTRY_STOP_END   = dt.time(15, 50)
_ENTRY_STOP_INTERVAL_MINS = 5


def _is_trading_day(date: dt.date) -> bool:
    """Weekdays only (exchange holiday check omitted for simplicity)."""
    return date.weekday() < 5


def _now_et() -> dt.datetime:
    return dt.datetime.now(tz=_TZ_ET)


def _sleep_until(target_et: dt.datetime) -> None:
    now = _now_et()
    delta = (target_et - now).total_seconds()
    if delta > 0:
        log.info("TGTC daemon: sleeping %.0f s until %s ET", delta, target_et.strftime("%H:%M:%S"))
        time.sleep(delta)


def _today_et(time_of_day: dt.time) -> dt.datetime:
    today = _now_et().date()
    return dt.datetime(today.year, today.month, today.day,
                       time_of_day.hour, time_of_day.minute, time_of_day.second,
                       tzinfo=_TZ_ET)


# ── Main loop ─────────────────────────────────────────────────────────────────

async def run_daily(sessions: list, db_path: str) -> None:
    """Run one trading day for all sessions."""
    from apps.tgtc_trader.engine import make_tgtc_engine

    engines = {}
    for sess in sessions:
        try:
            eng = make_tgtc_engine(
                session_id=sess.session_id,
                config_path=sess.config_path,
                db_path=db_path,
            )
            engines[sess.session_id] = eng
        except Exception as exc:
            log.error("TGTC daemon: failed to init engine for %s: %s", sess.session_name, exc)

    if not engines:
        log.warning("TGTC daemon: no engines initialized, exiting")
        return

    today = _now_et().date()
    date_str = today.isoformat()

    # 09:20 ET — pre-screen
    pre_screen_time = _today_et(dt.time(9, 20))
    _sleep_until(pre_screen_time)
    for sid, eng in engines.items():
        log.info("TGTC daemon: pre_screen session %s", sid)
        eng.run_pre_screen(date_str)

    # 09:29:30 ET — start snapshot collection
    col_start = _today_et(_COLLECTION_START)
    _sleep_until(col_start)
    log.info("TGTC daemon: starting snapshot collection")
    # Collection is async; run all sessions concurrently
    await asyncio.gather(*[eng.run_collect_snapshot(date_str) for eng in engines.values()])

    # 10:00 ET — finalize candidates
    for sid, eng in engines.items():
        log.info("TGTC daemon: finalize_candidates session %s", sid)
        eng.run_finalize_candidates(date_str)

    # 10:05–15:50 ET — entry + stop checks every 5 minutes
    cursor = _today_et(_ENTRY_STOP_START)
    eod_dt = _today_et(_EOD_EXIT)
    while cursor < eod_dt:
        _sleep_until(cursor)
        now_et = _now_et()
        if now_et >= eod_dt:
            break
        for sid, eng in engines.items():
            eng.run_entry_check(date_str)
            eng.run_stop_check(date_str)
        cursor += dt.timedelta(minutes=_ENTRY_STOP_INTERVAL_MINS)

    # 15:55 ET — EOD exit
    _sleep_until(eod_dt)
    for sid, eng in engines.items():
        log.info("TGTC daemon: eod_exit session %s", sid)
        eng.run_eod_exit(date_str)

    # 16:00 ET — post close
    post_close_dt = _today_et(_POST_CLOSE)
    _sleep_until(post_close_dt)
    for sid, eng in engines.items():
        log.info("TGTC daemon: post_close session %s", sid)
        eng.run_post_close(date_str)

    log.info("TGTC daemon: daily cycle complete for %s", date_str)


async def run_daily_start_now(
    sessions: list,
    db_path: str,
    collect_duration_secs: int = 300,
    quick_end_after_mins: int = 60,  # kept for backward compat — ignored
) -> None:
    """Run one trading day immediately (START_NOW mode).

    Difference from normal schedule: collection starts immediately for
    collect_duration_secs, then finalize runs. After that, entry/stop loop
    runs on the same 5-min cadence until 15:55 ET (EOD), just like run_daily.

    Phases:
      1. pre_screen    — immediately
      2. collect       — immediately for collect_duration_secs seconds
      3. finalize      — immediately after collection ends
      4. entry/stop    — every 5 min until 15:55 ET (same as normal schedule)
      5. eod_exit      — 15:55 ET
      6. post_close    — 16:00 ET
    """
    from apps.tgtc_trader.engine import make_tgtc_engine
    import datetime as _dt

    log.info(
        "TGTC daemon: starting in START_NOW mode (collect=%ds, loop until %s ET)",
        collect_duration_secs, _EOD_EXIT.strftime("%H:%M"),
    )

    engines = {}
    for sess in sessions:
        try:
            eng = make_tgtc_engine(
                session_id=sess.session_id,
                config_path=sess.config_path,
                db_path=db_path,
            )
            engines[sess.session_id] = eng
        except Exception as exc:
            log.error("TGTC daemon: failed to init engine for %s: %s", sess.session_name, exc)

    if not engines:
        log.warning("TGTC daemon: no engines initialized, exiting")
        return

    today = _now_et().date()
    date_str = today.isoformat()

    # 1. pre_screen — immediately
    for sid, eng in engines.items():
        log.info("TGTC daemon: pre_screen session %s", sid)
        eng.run_pre_screen(date_str)

    # 2. Override collection end time to now + collect_duration_secs, then collect
    collect_end_et = _now_et() + _dt.timedelta(seconds=collect_duration_secs)
    collect_end_str = collect_end_et.strftime("%H:%M")
    log.info("TGTC daemon: collect_snapshot starting (end=%s ET)", collect_end_str)
    for eng in engines.values():
        eng._params.collection.end_et = collect_end_str
    await asyncio.gather(*[eng.run_collect_snapshot(date_str) for eng in engines.values()])

    # 3. finalize_candidates — immediately after collection
    for sid, eng in engines.items():
        log.info("TGTC daemon: finalize_candidates session %s", sid)
        eng.run_finalize_candidates(date_str)

    # 4. entry + stop checks every 5 min until 15:55 ET (same as normal schedule)
    eod_dt = _today_et(_EOD_EXIT)
    cursor = _now_et() + _dt.timedelta(minutes=_ENTRY_STOP_INTERVAL_MINS)

    log.info(
        "TGTC daemon: entry/stop loop until %s ET (EOD)",
        eod_dt.strftime("%H:%M:%S"),
    )
    while cursor < eod_dt:
        _sleep_until(cursor)
        now_et = _now_et()
        if now_et >= eod_dt:
            break
        for sid, eng in engines.items():
            eng.run_entry_check(date_str)
            eng.run_stop_check(date_str)
        cursor += _dt.timedelta(minutes=_ENTRY_STOP_INTERVAL_MINS)

    # 5. eod_exit at 15:55 ET
    _sleep_until(eod_dt)
    for sid, eng in engines.items():
        log.info("TGTC daemon: eod_exit session %s", sid)
        eng.run_eod_exit(date_str)

    # 6. post_close at 16:00 ET
    post_close_dt = _today_et(_POST_CLOSE)
    _sleep_until(post_close_dt)
    for sid, eng in engines.items():
        log.info("TGTC daemon: post_close session %s", sid)
        eng.run_post_close(date_str)

    log.info("TGTC daemon: START_NOW daily cycle complete for %s", date_str)


def main() -> None:
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s %(levelname)s %(name)s %(message)s",
        handlers=[
            logging.StreamHandler(sys.stdout),
            logging.FileHandler(_LOG_FILE, mode="a"),
        ],
    )

    # Resolve DB path relative to project root
    project_root = Path(__file__).parent.parent.parent
    db_path = _DB_ENV if Path(_DB_ENV).is_absolute() else str(project_root / _DB_ENV)
    Path(db_path).parent.mkdir(parents=True, exist_ok=True)
    os.chdir(project_root)

    # ── PID file: write own PID so TGTCDaemonController.running can detect us ──
    pid_file = Path(db_path).parent / ".tgtc_scheduler.pid"
    try:
        pid_file.write_text(str(os.getpid()))
    except Exception as exc:
        log.warning("TGTC daemon: could not write PID file: %s", exc)

    def _cleanup_pid(*_: object) -> None:
        try:
            if pid_file.exists():
                pid_file.unlink()
        except Exception:
            pass

    # Clean up PID file on SIGTERM
    signal.signal(signal.SIGTERM, lambda s, f: (_cleanup_pid(), sys.exit(0)))

    try:
        from apps.tgtc_trader.state import TGTCStateManager
        state = TGTCStateManager(db_path)
        sessions = [s for s in state.list_sessions() if s.status == "active"]

        if not sessions:
            log.warning("TGTC daemon: no active sessions, exiting")
            return

        log.info(
            "TGTC daemon: starting with %d sessions, dry_run=%s, start_now=%s",
            len(sessions), _DRY_RUN, _START_NOW,
        )

        today = _now_et().date()
        if not _is_trading_day(today) and not _START_NOW:
            log.info("TGTC daemon: %s is not a trading day, exiting", today)
            return

        if _START_NOW:
            asyncio.run(run_daily_start_now(
                sessions, db_path,
                collect_duration_secs=_COLLECT_DURATION_SECS,
                quick_end_after_mins=_QUICK_END_AFTER_MINS,
            ))
        else:
            asyncio.run(run_daily(sessions, db_path))
    finally:
        _cleanup_pid()


if __name__ == "__main__":
    main()
