import asyncio
import logging
import os
from datetime import datetime
from zoneinfo import ZoneInfo

from app.db.database import get_mongo_db
from app.v1.services.movers import refresh_movers_service, refresh_live_movers_from_latest_movers
from app.v1.utils.market_time import backend_market_window, format_window, is_backend_market_hours, local_time_str, local_now, next_backend_market_open_local, seconds_until

logger = logging.getLogger(__name__)

IST = ZoneInfo("Asia/Kolkata")

_MOVERS_REFRESH_LOCK = asyncio.Lock()

MOVERS_REFRESH_ENFORCE_MARKET_HOURS = (
    (os.getenv("MOVERS_REFRESH_ENFORCE_MARKET_HOURS", "1") or "1").strip().lower()
    not in ("0", "false", "no", "off")
)


def _refresh_movers_once_sync() -> None:
    """Run a single ET movers refresh cycle (synchronous).

    This is run in a worker thread so the FastAPI event loop stays responsive.
    """

    db_gen = get_mongo_db()
    db = next(db_gen)
    try:
        record = refresh_movers_service(db)
        gainers = len(record.get("gainers", []))
        losers = len(record.get("losers", []))

        lm = refresh_live_movers_from_latest_movers(db)

        logger.info(
            "[MoversRefresh] Refreshed movers: %s | gainers=%d losers=%d",
            record.get("fetched_at"),
            gainers,
            losers,
        )
        logger.info(
            "[MoversRefresh] Rebuilt live_movers from movers | built=%s skipped=%s",
            lm.get("built"),
            lm.get("skipped"),
        )
    except Exception:  # pragma: no cover - defensive logging
        logger.exception("[MoversRefresh] Error refreshing ET movers")
    finally:
        try:
            db_gen.close()
        except Exception:
            logger.debug("[MoversRefresh] Error closing DB generator", exc_info=True)


async def movers_refresh_loop(interval_seconds: int = 900) -> None:
    """Background loop to refresh ET movers periodically.

    Intended to be started from FastAPI's startup event via
    `asyncio.create_task(movers_refresh_loop(...))`.

    Parameters
    ----------
    interval_seconds: int
        Sleep interval between refresh cycles. Default: 900s (15 minutes).
    """
    # Small initial delay so the app can finish startup cleanly.
    await asyncio.sleep(5)
    logger.info(
        "[MoversRefresh] Background ET movers loop started (interval=%ss)",
        interval_seconds,
    )
    while True:
        if MOVERS_REFRESH_ENFORCE_MARKET_HOURS and (not is_backend_market_hours()):
            nxt = next_backend_market_open_local()
            logger.info(
                "[MoversRefresh] Skipped: outside backend market hours | now=%s | window=%s",
                local_time_str(),
                format_window(backend_market_window()),
            )
            if nxt is not None:
                await asyncio.sleep(seconds_until(local_now(), nxt))
                continue
            await asyncio.sleep(interval_seconds)
            continue

        if _MOVERS_REFRESH_LOCK.locked():
            logger.info("[MoversRefresh] Previous refresh still running; skipping this tick")
            await asyncio.sleep(interval_seconds)
            continue

        async with _MOVERS_REFRESH_LOCK:
            # Offload blocking ET scrape + DB writes to a worker thread.
            await asyncio.to_thread(_refresh_movers_once_sync)

        await asyncio.sleep(interval_seconds)
