import asyncio
import logging
import os
from datetime import datetime
from datetime import timedelta
from typing import Optional
from zoneinfo import ZoneInfo

from app.db.database import get_mongo_db
from app.v1.background.global_intraday import _get_global_zerodha_client, _is_market_hours_ist
from app.v1.services.market_intelligence import compute_market_intelligence_summary

logger = logging.getLogger(__name__)

_MARKET_INTELLIGENCE_LOCK = asyncio.Lock()

IST = ZoneInfo("Asia/Kolkata")

# Market-hours enforcement (default ON): prevents after-hours runs.
MARKET_INTELLIGENCE_ENFORCE_MARKET_HOURS = (
    (os.getenv("MARKET_INTELLIGENCE_ENFORCE_MARKET_HOURS", "1") or "1").strip().lower()
    not in ("0", "false", "no", "off")
)


def _next_backend_market_open_ist(now_utc: Optional[datetime] = None) -> Optional[datetime]:
    """Return the next backend market open time in IST."""
    from app.v1.utils.market_time import backend_market_window

    now = now_utc or datetime.utcnow()
    try:
        ist_now = now.replace(tzinfo=ZoneInfo("UTC")).astimezone(IST)
    except Exception:
        ist_now = datetime.now(IST)

    w = backend_market_window()
    candidate = ist_now.replace(hour=w.start_h, minute=w.start_m, second=0, microsecond=0)
    if ist_now >= candidate:
        candidate = candidate + timedelta(days=1)
    while candidate.weekday() >= 5:
        candidate = candidate + timedelta(days=1)
    return candidate


def _seconds_until(dt_from: datetime, dt_to: datetime) -> int:
    try:
        sec = int((dt_to - dt_from).total_seconds())
    except Exception:
        sec = 0
    return max(5, sec)


def _refresh_market_intelligence_once_sync() -> None:
    db_gen = get_mongo_db()
    db = next(db_gen)
    captured_at = datetime.utcnow()

    try:
        if not _is_market_hours_ist(captured_at):
            return

        client = _get_global_zerodha_client(db)
        if not client:
            logger.error("[MarketIntelligence] Skipping: global Zerodha client unavailable")
            return

        summary = compute_market_intelligence_summary(db=db, zerodha_client=client, captured_at=captured_at)

        # Latest snapshot
        db["market_intelligence_summary"].update_one(
            {"type": "latest"},
            {
                "$set": {
                    "type": "latest",
                    "captured_at": summary["captured_at"],
                    "payload": summary,
                    "updated_at": datetime.utcnow(),
                }
            },
            upsert=True,
        )

        # Append-only history
        db["market_intelligence_history"].insert_one(
            {
                "captured_at": summary["captured_at"],
                "payload": summary,
                "created_at": datetime.utcnow(),
            }
        )

        logger.info(
            "[MarketIntelligence] Updated | bias=%s vol=%s risk=%s",
            summary.get("market_bias"),
            summary.get("volatility_state"),
            summary.get("overall_risk"),
        )

    except Exception:
        logger.exception("[MarketIntelligence] Error computing summary")
    finally:
        try:
            db_gen.close()
        except Exception:
            logger.debug("[MarketIntelligence] Error closing DB generator", exc_info=True)


async def market_intelligence_loop(interval_seconds: Optional[int] = None) -> None:
    interval_seconds = int(interval_seconds or os.getenv("MARKET_INTELLIGENCE_INTERVAL_SECONDS", "900"))

    await asyncio.sleep(8)
    logger.info("[MarketIntelligence] Background loop started (interval=%ss)", interval_seconds)

    while True:
        # Hard gate to avoid running after-hours.
        if MARKET_INTELLIGENCE_ENFORCE_MARKET_HOURS and (not _is_market_hours_ist(datetime.utcnow())):
            nxt = _next_backend_market_open_ist(datetime.utcnow())
            if nxt:
                try:
                    ist_now = datetime.utcnow().replace(tzinfo=ZoneInfo("UTC")).astimezone(IST)
                except Exception:
                    ist_now = datetime.now(IST)
                logger.info(
                    "[MarketIntelligence] Skipped: outside backend market hours | now=%s next_open=%s",
                    ist_now.strftime("%Y-%m-%d %H:%M"),
                    nxt.strftime("%Y-%m-%d %H:%M"),
                )
                await asyncio.sleep(_seconds_until(ist_now, nxt))
                continue
            await asyncio.sleep(interval_seconds)
            continue

        if _MARKET_INTELLIGENCE_LOCK.locked():
            logger.info("[MarketIntelligence] Previous tick still running; skipping")
            await asyncio.sleep(interval_seconds)
            continue

        async with _MARKET_INTELLIGENCE_LOCK:
            await asyncio.to_thread(_refresh_market_intelligence_once_sync)

        await asyncio.sleep(interval_seconds)
