import asyncio
import logging
from datetime import datetime
from typing import Optional

from app.db.database import get_mongo_db
from app.v1.services.paper_trading import close_paper_trades_for_eod, _paper_day_key
from app.v1.utils.market_time import (
    backend_market_window,
    format_window,
    is_backend_market_hours,
    local_now,
    local_time_str,
    next_backend_market_open_local,
    seconds_until,
)

logger = logging.getLogger(__name__)

_EOD_LOCK = asyncio.Lock()
_LAST_RUN_DAY: Optional[str] = None


async def paper_eod_loop(interval_seconds: int = 60) -> None:
    """Close all intraday paper trades at EOD (IST).

    Why this exists:
    - The per-symbol update loop closes trades only when a candle update arrives.
    - If candle updates stop before cutoff or symbols go quiet, trades can remain OPEN.

    This loop is DB-only. It runs once per day after the configured cutoff.
    """

    await asyncio.sleep(5)
    logger.info("[PaperEOD] Loop started (interval=%ss)", int(interval_seconds))

    global _LAST_RUN_DAY

    while True:
        try:
            if not is_backend_market_hours():
                nxt = next_backend_market_open_local()
                logger.info(
                    "[PaperEOD] Backend skipped due to time | 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))
                else:
                    await asyncio.sleep(interval_seconds)
                continue

            if _EOD_LOCK.locked():
                await asyncio.sleep(interval_seconds)
                continue

            async with _EOD_LOCK:
                now = datetime.utcnow()
                day_key = _paper_day_key(now)

                # Only run once per day.
                if _LAST_RUN_DAY == day_key:
                    await asyncio.sleep(interval_seconds)
                    continue

                # close_paper_trades_for_eod() is internally conservative and will
                # no-op before cutoff.
                db_gen = get_mongo_db()
                db = next(db_gen)
                try:
                    res = close_paper_trades_for_eod(db, now_utc=now)
                finally:
                    try:
                        db_gen.close()
                    except Exception:
                        pass

                if res.get("ran"):
                    _LAST_RUN_DAY = day_key
                    logger.info(
                        "[PaperEOD] Closed trades at EOD | day=%s closed=%s scanned=%s",
                        day_key,
                        res.get("closed"),
                        res.get("scanned"),
                    )
        except Exception:
            logger.exception("[PaperEOD] Unexpected error")

        await asyncio.sleep(interval_seconds)
