import time
from datetime import datetime, timezone

from indian_paper_trading_strategy.engine.db import (
    run_with_retry,
    insert_engine_event,
    get_default_user_id,
    get_active_run_id,
    get_running_runs,
    engine_context,
)

def log_event(event: str, data: dict | None = None):
    now = datetime.utcnow().replace(tzinfo=timezone.utc)
    payload = data or {}

    def _op(cur, _conn):
        insert_engine_event(cur, event, data=payload, ts=now)

    run_with_retry(_op)

def _update_engine_status(user_id: str, run_id: str, status: str):
    now = datetime.utcnow().replace(tzinfo=timezone.utc)

    def _op(cur, _conn):
        cur.execute(
            """
            INSERT INTO engine_status (user_id, run_id, status, last_updated)
            VALUES (%s, %s, %s, %s)
            ON CONFLICT (user_id, run_id) DO UPDATE
            SET status = EXCLUDED.status,
                last_updated = EXCLUDED.last_updated
            """,
            (user_id, run_id, status, now),
        )

    run_with_retry(_op)

from indian_paper_trading_strategy.engine.config import load_strategy_config, save_strategy_config
from indian_paper_trading_strategy.engine.market import india_market_status
from indian_paper_trading_strategy.engine.execution import try_execute_sip
from indian_paper_trading_strategy.engine.state import load_state
from indian_paper_trading_strategy.engine.broker import PaperBroker
from indian_paper_trading_strategy.engine.data import fetch_live_price
from indian_paper_trading_strategy.engine.mtm import log_mtm, should_log_mtm
from indian_paper_trading_strategy.engine.history import load_monthly_close
from indian_paper_trading_strategy.engine.strategy import allocation
from indian_paper_trading_strategy.engine.time_utils import frequency_to_timedelta, normalize_logical_time

NIFTY = "NIFTYBEES.NS"
GOLD = "GOLDBEES.NS"
SMA_MONTHS = 36

def run_engine(user_id: str | None = None, run_id: str | None = None):
    print("Strategy engine started")
    active_runs: dict[tuple[str, str], bool] = {}

    if run_id and not user_id:
        raise ValueError("user_id is required when run_id is provided")

    while True:
        try:
            if user_id and run_id:
                runs = [(user_id, run_id)]
            elif user_id:
                runs = get_running_runs(user_id)
            else:
                runs = get_running_runs()
                if not runs:
                    default_user = get_default_user_id()
                    if default_user:
                        runs = get_running_runs(default_user)

            seen = set()
            for scope_user, scope_run in runs:
                if not scope_user or not scope_run:
                    continue
                seen.add((scope_user, scope_run))
                with engine_context(scope_user, scope_run):
                    cfg = load_strategy_config(user_id=scope_user, run_id=scope_run)
                    if not cfg.get("active"):
                        continue

                    strategy_name = cfg.get("strategy_name", "golden_nifty")
                    sip_amount = cfg.get("sip_amount", 0)
                    configured_frequency = cfg.get("sip_frequency") or {}
                    if not isinstance(configured_frequency, dict):
                        configured_frequency = {}
                    frequency_value = int(configured_frequency.get("value", cfg.get("frequency", 0)))
                    frequency_unit = configured_frequency.get("unit", cfg.get("unit", "days"))
                    frequency_info = {"value": frequency_value, "unit": frequency_unit}
                    frequency_label = f"{frequency_value} {frequency_unit}"

                    if not active_runs.get((scope_user, scope_run)):
                        log_event(
                            "ENGINE_START",
                            {
                                "strategy": strategy_name,
                                "sip_amount": sip_amount,
                                "frequency": frequency_label,
                            },
                        )
                        active_runs[(scope_user, scope_run)] = True

                    _update_engine_status(scope_user, scope_run, "RUNNING")

                    market_open, _ = india_market_status()
                    if not market_open:
                        log_event("MARKET_CLOSED", {"reason": "Outside market hours"})
                        continue

                    now = datetime.now()
                    mode = (cfg.get("mode") or "PAPER").strip().upper()
                    if mode not in {"PAPER", "LIVE"}:
                        mode = "PAPER"
                    state = load_state(mode=mode)
                    initial_cash = float(state.get("initial_cash") or 0.0)
                    broker = PaperBroker(initial_cash=initial_cash) if mode == "PAPER" else None

                    nifty_price = fetch_live_price(NIFTY)
                    gold_price = fetch_live_price(GOLD)

                    next_run = cfg.get("next_run")
                    if next_run is None or now >= datetime.fromisoformat(next_run):
                        nifty_hist = load_monthly_close(NIFTY)
                        gold_hist = load_monthly_close(GOLD)

                        nifty_sma = nifty_hist.rolling(SMA_MONTHS).mean().iloc[-1]
                        gold_sma = gold_hist.rolling(SMA_MONTHS).mean().iloc[-1]

                        eq_w, gd_w = allocation(
                            sp_price=nifty_price,
                            gd_price=gold_price,
                            sp_sma=nifty_sma,
                            gd_sma=gold_sma,
                        )

                        weights = {"equity": eq_w, "gold": gd_w}
                        state, executed = try_execute_sip(
                            now=now,
                            market_open=True,
                            sip_interval=frequency_to_timedelta(frequency_info).total_seconds(),
                            sip_amount=sip_amount,
                            sp_price=nifty_price,
                            gd_price=gold_price,
                            eq_w=eq_w,
                            gd_w=gd_w,
                            broker=broker,
                            mode=mode,
                        )

                        if executed:
                            log_event(
                                "SIP_TRIGGERED",
                                {
                                    "date": now.date().isoformat(),
                                    "allocation": weights,
                                    "cash_used": sip_amount,
                                },
                            )
                            portfolio_value = (
                                state["nifty_units"] * nifty_price
                                + state["gold_units"] * gold_price
                            )
                            log_event(
                                "PORTFOLIO_UPDATED",
                                {
                                    "nifty_units": state["nifty_units"],
                                    "gold_units": state["gold_units"],
                                    "portfolio_value": portfolio_value,
                                },
                            )
                            cfg["next_run"] = (now + frequency_to_timedelta(frequency_info)).isoformat()
                            save_strategy_config(cfg, user_id=scope_user, run_id=scope_run)

                    if should_log_mtm(None, now):
                        state = load_state(mode=mode)
                        log_mtm(
                            nifty_units=state["nifty_units"],
                            gold_units=state["gold_units"],
                            nifty_price=nifty_price,
                            gold_price=gold_price,
                            total_invested=state["total_invested"],
                            logical_time=normalize_logical_time(now),
                        )

            for key in list(active_runs.keys()):
                if key not in seen:
                    active_runs.pop(key, None)

            time.sleep(30)
        except Exception as e:
            log_event("ENGINE_ERROR", {"error": str(e)})
            raise

if __name__ == "__main__":
    run_engine()

