"""Auto-scan scheduler — background thread inside the FastAPI process.

Every minute it checks whether a scan is due (AUTO_SCAN_INTERVAL_MINUTES,
default 15) and whether the US market is open (9:30–16:00 ET, Mon–Fri).
When due it runs the full agent pipeline, marks paper trades to market,
and sends email alerts for new approved signals and auto-closed trades.

Enabled via AUTO_SCAN_ENABLED=true. Set AUTO_SCAN_ALWAYS=true to ignore
market hours (useful for testing with mock data on weekends).
"""
import logging
import os
import threading
import time
from datetime import datetime, timezone, timedelta

logger = logging.getLogger(__name__)


def _eastern_now() -> datetime:
    """Current US/Eastern time; zoneinfo with a fixed-offset fallback."""
    try:
        from zoneinfo import ZoneInfo

        return datetime.now(ZoneInfo("America/New_York"))
    except Exception:  # noqa: BLE001 — missing tzdata on some Windows setups
        return datetime.now(timezone(timedelta(hours=-5)))


def is_market_hours(now: datetime | None = None) -> bool:
    now = now or _eastern_now()
    if now.weekday() >= 5:  # Sat/Sun
        return False
    minutes = now.hour * 60 + now.minute
    return 9 * 60 + 30 <= minutes < 16 * 60


def auto_scan_enabled() -> bool:
    return os.getenv("AUTO_SCAN_ENABLED", "false").lower() == "true"


def _ignore_market_hours() -> bool:
    return os.getenv("AUTO_SCAN_ALWAYS", "false").lower() == "true"


def _interval_minutes() -> int:
    try:
        return max(1, int(os.getenv("AUTO_SCAN_INTERVAL_MINUTES", "15")))
    except ValueError:
        return 15


class ScanScheduler:
    def __init__(self):
        self._thread: threading.Thread | None = None
        self._stop = threading.Event()
        self.last_run: datetime | None = None
        self.last_result: str = "never run"
        self.runs = 0

    @property
    def running(self) -> bool:
        return self._thread is not None and self._thread.is_alive()

    def start(self) -> None:
        if self.running:
            return
        self._stop.clear()
        self._thread = threading.Thread(target=self._loop, name="auto-scan", daemon=True)
        self._thread.start()
        logger.info("Auto-scan scheduler started (every %s min)", _interval_minutes())

    def stop(self) -> None:
        self._stop.set()

    def _due(self) -> bool:
        if self.last_run is None:
            return True
        elapsed = (datetime.now(timezone.utc) - self.last_run).total_seconds() / 60
        return elapsed >= _interval_minutes()

    def _loop(self) -> None:
        # Small initial delay so the API finishes booting first.
        self._stop.wait(10)
        while not self._stop.is_set():
            try:
                if self._due() and (is_market_hours() or _ignore_market_hours()):
                    self._run_cycle()
            except Exception as exc:  # noqa: BLE001 — the loop must survive anything
                logger.exception("Auto-scan cycle failed: %s", exc)
                self.last_result = f"error: {exc}"
            self._stop.wait(60)

    def _run_cycle(self) -> None:
        from ..agents.orchestrator import run_scan_pipeline
        from ..database import SessionLocal
        from .alerts import alert_closed_trades, alert_new_signals
        from .paper_trading import mark_to_market
        from .. import models

        db = SessionLocal()
        try:
            run = run_scan_pipeline(db)
            approved = (
                db.query(models.Signal)
                .filter(models.Signal.scan_run_id == run.id, models.Signal.status == "approved")
                .all()
            )
            closed_now = mark_to_market(db)
            sent_signals = alert_new_signals(db, approved)
            sent_closes = alert_closed_trades(db, closed_now)

            self.last_run = datetime.now(timezone.utc)
            self.runs += 1
            self.last_result = (
                f"scan #{run.id}: {run.signals_created} signals "
                f"({len(approved)} approved), {len(closed_now)} trades closed, "
                f"{sent_signals + sent_closes} alert emails"
            )
            logger.info("Auto-scan cycle: %s", self.last_result)
        finally:
            db.close()

    def status(self) -> dict:
        return {
            "enabled": auto_scan_enabled(),
            "running": self.running,
            "interval_minutes": _interval_minutes(),
            "market_hours_now": is_market_hours(),
            "ignore_market_hours": _ignore_market_hours(),
            "alerts_configured": _alerts_configured(),
            "last_run": self.last_run.isoformat() if self.last_run else None,
            "runs": self.runs,
            "last_result": self.last_result,
        }


def _alerts_configured() -> bool:
    from .alerts import alerts_enabled

    return alerts_enabled()


scheduler = ScanScheduler()
