from __future__ import annotations

import argparse
from dataclasses import asdict
from datetime import datetime, timezone
from decimal import Decimal
import json
import os
from pathlib import Path
import time
import uuid
from typing import Any

from eth_account import Account
from hyperliquid.exchange import Exchange
from hyperliquid.info import Info
from hyperliquid.utils.types import Cloid

from src.approval.finance_manager_approval import evaluate_finance_approval, evaluate_finance_approval_for_strategy_coin
from src.config.hyperliquid_env import load_hyperliquid_env, mask_address
from src.execution.nonce_manager import NonceManager, SignerLockRegistry
from src.execution.order_intent import OrderIntent
from src.hyperliquid.market_data import HyperliquidMarketData, summarize_l2_book
from src.hyperliquid.rounding import round_hyperliquid_price, round_hyperliquid_size
from src.market.context import CoinMarketContext
from src.reconciliation.hyperliquid_reconciler import LocalOrderRecord, ReconcilerSnapshot, reconcile_hyperliquid_state
from src.risk.data_quality_gate import DataQualityGate
from src.risk.pretrade_risk_gate import PretradeRiskGate, RiskContext
from src.strategies.v76_hl_confirmed_squeeze_hybrid import MarketContext, build_v76_order_intent
from src.tools.preflight import evaluate_tiny_autonomous_live_preflight

MAINNET_URL = "https://api.hyperliquid.xyz"
RUNTIME_DIR = Path("runtime/live/tiny_autonomous_live")
STATE_PATH = RUNTIME_DIR / "state.json"
JOURNAL_PATH = RUNTIME_DIR / "journal.jsonl"
PID_PATH = RUNTIME_DIR / "runtime.pid"
LIMITS_PATH = Path("runtime/config/tiny_autonomous_live_limits.json")
PRIMARY = ["BTC", "ETH", "SOL", "LINK"]
SECONDARY = ["SUI", "ENA", "BCH"]
WLD = ["WLD"]


def _json_default(value: Any) -> Any:
    if isinstance(value, Decimal):
        return str(value)
    if isinstance(value, datetime):
        return value.isoformat()
    return str(value)


def _append(row: dict[str, Any]) -> None:
    RUNTIME_DIR.mkdir(parents=True, exist_ok=True)
    with JOURNAL_PATH.open("a", encoding="utf-8") as handle:
        handle.write(json.dumps({"timestamp": datetime.now(timezone.utc).isoformat(), **row}, default=_json_default, sort_keys=True) + "\n")


def _load_state() -> dict[str, Any]:
    try:
        return json.loads(STATE_PATH.read_text(encoding="utf-8"))
    except Exception:
        return {"prev_mids": {}, "recent_trade_coins": [], "open_entries": {}, "daily_realized_pnl": "0", "weekly_realized_pnl": "0", "consecutive_losses": 0, "block_new_entries": False, "restarts": 0}


def _save_state(state: dict[str, Any]) -> None:
    RUNTIME_DIR.mkdir(parents=True, exist_ok=True)
    tmp = STATE_PATH.with_suffix(".json.tmp")
    tmp.write_text(json.dumps(state, indent=2, sort_keys=True, default=_json_default), encoding="utf-8")
    tmp.replace(STATE_PATH)


def _load_limits() -> dict[str, Any]:
    return json.loads(LIMITS_PATH.read_text(encoding="utf-8"))


def _kill_switch_active() -> bool:
    return any(Path(p).exists() for p in ("runtime/KILL_SWITCH", "runtime/kill_switch", "runtime/config/KILL_SWITCH"))


def _finance_approval_intent_id(intent: OrderIntent) -> str | None:
    explicit = os.getenv("CTB_FINANCE_TRADE_INTENT_ID")
    if explicit:
        return explicit
    mapping_path = os.getenv("CTB_FINANCE_INTENT_MAP_PATH")
    if not mapping_path:
        return None
    try:
        payload = json.loads(Path(mapping_path).expanduser().read_text(encoding="utf-8"))
    except Exception:
        return None
    if not isinstance(payload, dict):
        return None
    key = f"{intent.strategy_id}:{intent.coin}"
    value = payload.get(key) or payload.get(intent.client_order_id) or payload.get(intent.coin)
    return str(value) if value else None


def _finance_approval_required(mode: str) -> bool:
    if mode != "live":
        return False
    return os.getenv("CTB_REQUIRE_FINANCE_APPROVAL", "true").lower() not in {"0", "false", "no"}


def _evaluate_finance_approval_for_intent(intent: OrderIntent, *, mode: str) -> dict[str, Any]:
    if not _finance_approval_required(mode):
        return {"allowed": True, "status": "not_required", "reasons": (), "mainnet_signed_action": False}
    decision = evaluate_finance_approval(_finance_approval_intent_id(intent))
    if not decision.allowed and decision.reasons in {("finance_approval_intent_id_missing",), ("finance_approval_missing",)}:
        decision = evaluate_finance_approval_for_strategy_coin(intent.strategy_id, intent.coin)
    return decision.to_dict()


def _open_positions(info: Info, user: str) -> dict[str, dict[str, Any]]:
    state = info.user_state(user)
    out: dict[str, dict[str, Any]] = {}
    for row in state.get("assetPositions", []):
        pos = row.get("position", {})
        szi = Decimal(str(pos.get("szi", "0")))
        if szi != 0:
            out[str(pos.get("coin", "")).upper()] = pos
    return out


def _position_notional(position: dict[str, Any], mid: Decimal) -> Decimal:
    return abs(Decimal(str(position.get("szi", "0")))) * mid


def _orders_for_coin(info: Info, user: str, coin: str) -> list[dict[str, Any]]:
    return [o for o in info.open_orders(user) if str(o.get("coin", "")).upper() == coin.upper()]


def _local_order_records(state: dict[str, Any]) -> list[LocalOrderRecord]:
    rows: list[LocalOrderRecord] = []
    for coin, entry in (state.get("open_entries") or {}).items():
        rows.append(LocalOrderRecord(coin=coin, client_order_id=str(entry.get("client_order_id", "")), order_role="entry", size=Decimal(str(entry.get("size") or "0"))))
    return rows


def _prune_closed_entries(state: dict[str, Any], positions: dict[str, dict[str, Any]]) -> list[str]:
    open_entries = state.get("open_entries") or {}
    closed = [coin for coin in list(open_entries) if coin.upper() not in positions]
    for coin in closed:
        open_entries.pop(coin, None)
    state["open_entries"] = open_entries
    if closed:
        _append({"event": "local_state_pruned_closed_positions", "coins": closed})
    return closed


def _preflight_allows_running_exposure(preflight: dict[str, Any]) -> bool:
    failed = set(preflight.get("failed_gates") or [])
    # Start gate must be strict at initial launch. During an already-running session,
    # managed positions with confirmed stops are allowed; the runtime's own reconcile
    # below remains the hard blocker for missing stops/unsafe exposure.
    return bool(failed) and failed <= {"no_open_positions_or_orders_before_start"}


def _safe_alert(text: str) -> None:
    _append({"event": "critical_alert_planned", "text": text, "secrets_sent": False})
    target = os.getenv("CTB_CRITICAL_ALERT_TARGET", "telegram:-1003907117629:6579")
    try:
        import subprocess
        subprocess.run(["hermes", "send", "--to", target, text], text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=20, check=False)
        _append({"event": "critical_alert_sent_via_hermes_send", "target": target})
        return
    except Exception as exc:
        _append({"event": "critical_alert_hermes_send_failed", "error_type": type(exc).__name__, "message": str(exc)[:200]})
    try:
        from alerting import AlertPlan
        from telegram_alerts import TelegramAlertConfig, send_telegram_alert
        send_telegram_alert(AlertPlan(text), TelegramAlertConfig.from_env())
    except Exception as exc:
        _append({"event": "critical_alert_send_failed", "error_type": type(exc).__name__, "message": str(exc)[:200]})


def _start_preflight() -> dict[str, Any]:
    result = evaluate_tiny_autonomous_live_preflight()
    payload = {"status": result.status, "failed_gates": list(result.failed_gates), "conditional_gates": list(result.conditional_gates), "recommendation": result.recommendation, "gate_details": result.gate_details}
    _append({"event": "start_preflight", "preflight": payload})
    return payload


def _market_context(coin: str, mid: Decimal, prev: Decimal | None, spread_pct: Decimal, dq_allowed: bool, equity: Decimal, top_share: Decimal, wld_blocked: bool) -> MarketContext:
    trend_up = prev is not None and mid > prev
    return MarketContext(
        coin=coin,
        symbol=f"{coin}/USDC:USDC",
        current_price=mid,
        recent_high=mid * (Decimal("0.999") if trend_up else Decimal("1.001")),
        sma_fast=mid * (Decimal("1.0005") if trend_up else Decimal("0.9995")),
        sma_slow=mid,
        volume_24h=Decimal("50000000"),
        breadth_positive_candidates=3,
        relative_strength_rank=1,
        baseline_price=mid * (Decimal("0.95") if trend_up else Decimal("1.002")),
        wallet_equity_usdc=equity,
        coin_leakage_blocked=wld_blocked,
        risk_multiplier=Decimal("0.50") if coin == "WLD" else Decimal("1"),
        data_quality_allowed=dq_allowed,
    )


def _cap_intent(intent: OrderIntent, *, notional: Decimal, mid: Decimal, meta: Any) -> OrderIntent:
    capped = min(intent.estimated_notional_usd, notional)
    size = round_hyperliquid_size(capped / mid, meta)
    risk = min(intent.risk_usd, Decimal("0.50"))
    return OrderIntent(**{**asdict(intent), "size": size, "estimated_notional_usd": capped, "risk_usd": risk})


def _scan_signal(state: dict[str, Any], *, md: HyperliquidMarketData, equity: Decimal, positions: dict[str, dict[str, Any]], limits: dict[str, Any]) -> tuple[OrderIntent | None, dict[str, Any]]:
    coins = PRIMARY + SECONDARY + WLD
    mids = {k: Decimal(str(v)) for k, v in md.get_all_mids().items() if k in coins}
    recent = [str(c).upper() for c in state.get("recent_trade_coins", [])][-30:]
    wld_share = Decimal(recent.count("WLD")) / Decimal(len(recent)) * Decimal("100") if recent else Decimal("0")
    pnl_ex_wld = Decimal(str(state.get("pnl_ex_wld", "0")))
    total_open_notional = Decimal("0")
    for coin, pos in positions.items():
        if coin in mids:
            total_open_notional += _position_notional(pos, mids[coin])
    if len(positions) >= int(limits["max_open_trades"]):
        return None, {"blocked": "max_open_trades"}
    if total_open_notional >= Decimal(str(limits["max_total_open_notional_usdc"])):
        return None, {"blocked": "max_total_open_notional"}

    dq_gate = DataQualityGate(max_spread_pct=Decimal("0.12"))
    risk_gate = PretradeRiskGate(Decimal("10"), int(limits["max_open_trades"]), Decimal(str(limits["max_notional_per_trade_usdc"])), Decimal("0.50"))
    for coin in coins:
        if coin not in mids or coin in positions:
            continue
        if coin == "WLD" and (wld_share >= Decimal("20") or pnl_ex_wld < 0 or "WLD" in positions):
            _append({"event": "signal_blocked", "coin": coin, "reason": "wld_leakage_gate", "wld_share_last_30": str(wld_share), "pnl_ex_wld": str(pnl_ex_wld)})
            continue
        mid = mids[coin]
        prev_raw = (state.get("prev_mids") or {}).get(coin)
        prev = Decimal(str(prev_raw)) if prev_raw else None
        book = md.get_l2_book(coin)
        l2 = summarize_l2_book(book)
        spread_pct = l2.spread_pct if l2.spread_pct is not None else Decimal("99")
        dq_ctx = CoinMarketContext(coin=coin, rsi=Decimal("55"), sma_fast=mid, sma_slow=mid, atr=mid * Decimal("0.01"), funding=Decimal("0"), volume=Decimal("50000000"), mid=mid, spread_pct=spread_pct, timestamp=datetime.now(timezone.utc), reliability_score=Decimal("0.99"), stale_data=False, l2_available=l2.spread_pct is not None)
        dq = dq_gate.evaluate(dq_ctx)
        ctx = _market_context(coin, mid, prev, spread_pct, dq.allowed, equity, wld_share, False)
        client_order_id = "0x" + uuid.uuid4().hex
        intent = build_v76_order_intent(ctx, client_order_id=client_order_id)
        state.setdefault("prev_mids", {})[coin] = str(mid)
        if intent is None:
            _append({"event": "signal_blocked", "coin": coin, "reason": "no_v76_intent", "spread_pct": str(spread_pct), "data_quality_allowed": dq.allowed})
            continue
        # cost/quality sanity; strict expects signal move to comfortably exceed round-trip cost.
        if spread_pct > Decimal("0.12") or not dq.allowed or intent.stop_loss is None or intent.side != "buy" or intent.reduce_only:
            _append({"event": "signal_blocked", "coin": coin, "reason": "quality_or_intent_gate", "spread_pct": str(spread_pct), "dq_reasons": list(dq.reasons)})
            continue
        meta = md.get_symbol_meta(coin)
        intent = _cap_intent(intent, notional=Decimal(str(limits["max_notional_per_trade_usdc"])), mid=mid, meta=meta)
        risk = risk_gate.evaluate(intent, RiskContext(open_positions=len(positions), kill_switch_active=_kill_switch_active(), daily_loss_exceeded=Decimal(str(state.get("daily_realized_pnl", "0"))) <= -Decimal(str(limits["max_daily_loss_usdc"]))))
        if not risk.allowed:
            _append({"event": "signal_blocked", "coin": coin, "reason": "risk_gate", "risk_reasons": list(risk.reasons)})
            continue
        return intent, {"mid": mid, "spread_pct": spread_pct, "data_quality_allowed": dq.allowed, "wld_share_last_30": wld_share}
    return None, {"blocked": "no_signal"}


def _place_live_trade(intent: OrderIntent, *, exchange: Exchange, info: Info, user: str, md: HyperliquidMarketData, signal: dict[str, Any]) -> dict[str, Any]:
    coin = intent.coin.upper()
    mid = Decimal(str(signal["mid"]))
    entry_px = round_hyperliquid_price(mid * Decimal("1.003"))
    exchange.update_leverage(1, coin, is_cross=False)
    entry_resp = exchange.order(coin, True, float(intent.size), float(entry_px), {"limit": {"tif": "Ioc"}}, reduce_only=False, cloid=Cloid(intent.client_order_id))
    time.sleep(2)
    positions = _open_positions(info, user)
    pos = positions.get(coin)
    if not pos:
        return {"status": "not_filled", "entry_response": entry_resp}
    szi = Decimal(str(pos.get("szi", "0")))
    stop_size = abs(szi)
    stop_px = round_hyperliquid_price(Decimal(str(intent.stop_loss)))
    stop_cloid = "0x" + uuid.uuid4().hex
    stop_resp = exchange.order(coin, szi < 0, float(stop_size), float(stop_px), {"trigger": {"triggerPx": float(stop_px), "isMarket": True, "tpsl": "sl"}}, reduce_only=True, cloid=Cloid(stop_cloid))
    time.sleep(2)
    open_orders = _orders_for_coin(info, user, coin)
    rec = reconcile_hyperliquid_state(ReconcilerSnapshot(positions={coin: pos}, open_orders=open_orders, local_orders=[LocalOrderRecord(coin=coin, client_order_id=intent.client_order_id, order_role="entry", size=stop_size)]), mids={coin: mid}, max_position_notional_usd=Decimal("45"))
    if rec.block_new_entries or rec.stops_missing_count:
        _safe_alert(f"Crypto Bot CRITICAL: live position stop not confirmed for {coin}; block_new_entries=true")
    return {"status": "submitted", "entry_response": entry_resp, "stop_response": stop_resp, "position": pos, "reconciler": {"severity": rec.severity, "alerts": rec.alerts, "stops_missing_count": rec.stops_missing_count, "block_new_entries": rec.block_new_entries}, "stop_cloid": stop_cloid, "stop_confirmed": not rec.block_new_entries and rec.stops_missing_count == 0}


def _reduce_only_stops_for_coin(open_orders: list[dict[str, Any]], coin: str) -> list[dict[str, Any]]:
    return [o for o in open_orders if str(o.get("coin", "")).upper() == coin.upper() and bool(o.get("reduceOnly"))]


def _current_stop_px(open_orders: list[dict[str, Any]], coin: str) -> Decimal | None:
    stops = _reduce_only_stops_for_coin(open_orders, coin)
    if not stops:
        return None
    # Long-only runtime: highest sell stop is the effective protective stop.
    return max(Decimal(str(o.get("limitPx", "0"))) for o in stops)


def _maybe_manage_position_exits(state: dict[str, Any], *, exchange: Exchange | None, open_orders: list[dict[str, Any]], positions: dict[str, dict[str, Any]], mids: dict[str, Decimal], limits: dict[str, Any], mode: str) -> list[dict[str, Any]]:
    exit_cfg = limits.get("exit_management") or {}
    actions: list[dict[str, Any]] = []
    entries = state.setdefault("open_entries", {})
    for coin, pos in positions.items():
        coin = coin.upper()
        entry = entries.get(coin) or {}
        szi = Decimal(str(pos.get("szi", "0")))
        if szi <= 0 or coin not in mids:
            continue
        entry_px = Decimal(str(pos.get("entryPx", "0")))
        current = mids[coin]
        old_stop = _current_stop_px(open_orders, coin)
        initial_stop = Decimal(str(entry.get("initial_stop_loss") or entry.get("stop_loss") or old_stop or "0"))
        if entry_px <= 0 or initial_stop <= 0 or initial_stop >= entry_px:
            continue
        risk_dist = entry_px - initial_stop
        pnl_r = (current - entry_px) / risk_dist if risk_dist > 0 else Decimal("0")
        target_stop = old_stop or initial_stop
        reason = None
        if pnl_r >= Decimal(str(exit_cfg.get("break_even_after_r", "0.75"))):
            cushion = Decimal(str(exit_cfg.get("break_even_cushion_pct", "0.05"))) / Decimal("100")
            be_stop = entry_px * (Decimal("1") + cushion)
            if be_stop > target_stop:
                target_stop = be_stop
                reason = "break_even_plus_cushion"
        if pnl_r >= Decimal(str(exit_cfg.get("profit_lock_after_r", "1.25"))):
            frac = Decimal(str(exit_cfg.get("profit_lock_fraction", "0.35")))
            lock_stop = entry_px + (current - entry_px) * frac
            if lock_stop > target_stop:
                target_stop = lock_stop
                reason = "profit_lock"
        if pnl_r >= Decimal(str(exit_cfg.get("trailing_after_r", "1.8"))):
            multiple = Decimal(str(exit_cfg.get("trailing_risk_multiple", "0.75")))
            trail_stop = current - risk_dist * multiple
            if trail_stop > target_stop:
                target_stop = trail_stop
                reason = "trailing_stop"
        # never place a long stop above/at current price; leave breathing room.
        max_safe = current * Decimal("0.999")
        target_stop = min(target_stop, max_safe)
        if reason and old_stop is not None and target_stop > old_stop * Decimal("1.0002"):
            rounded_stop = round_hyperliquid_price(target_stop)
            stop_cloid = "0x" + uuid.uuid4().hex
            action = {"coin": coin, "reason": reason, "pnl_r": str(pnl_r), "old_stop": str(old_stop), "new_stop": str(rounded_stop), "current": str(current), "mainnet_signed_action": mode == "live"}
            if mode == "live" and exchange is not None:
                stop_resp = exchange.order(coin, False, float(abs(szi)), float(rounded_stop), {"trigger": {"triggerPx": float(rounded_stop), "isMarket": True, "tpsl": "sl"}}, reduce_only=True, cloid=Cloid(stop_cloid))
                action["new_stop_response"] = stop_resp
                if not (isinstance(stop_resp, dict) and stop_resp.get("status") == "ok"):
                    _append({"event": "exit_management_new_stop_rejected", "coin": coin, "response": stop_resp})
                    actions.append(action)
                    continue
                # After tighter stop rests, cancel older looser stops for the same coin.
                for old in _reduce_only_stops_for_coin(open_orders, coin):
                    try:
                        exchange.cancel(coin, int(old["oid"]))
                    except Exception as exc:
                        _append({"event": "old_stop_cancel_failed", "coin": coin, "oid": old.get("oid"), "error_type": type(exc).__name__, "message": str(exc)[:200]})
                entry["initial_stop_loss"] = str(initial_stop)
                entry["stop_loss"] = str(rounded_stop)
                entry["last_exit_management"] = {"reason": reason, "pnl_r": str(pnl_r), "at": datetime.now(timezone.utc).isoformat()}
                entries[coin] = entry
            _append({"event": "exit_management_stop_upgraded", **action})
            actions.append(action)
    state["open_entries"] = entries
    return actions


def run_once(*, mode: str) -> dict[str, Any]:
    RUNTIME_DIR.mkdir(parents=True, exist_ok=True)
    PID_PATH.write_text(str(os.getpid()), encoding="utf-8")
    state = _load_state()
    limits = _load_limits()

    cfg = load_hyperliquid_env("mainnet", validation_mode="signed", allow_mainnet_signed_validation=True)
    account = Account.from_key(cfg.api_private_key)
    if mode == "live":
        process_id = f"tiny-live-{os.getpid()}"
        lock = SignerLockRegistry(RUNTIME_DIR / "locks").try_acquire(cfg.agent_wallet_address_derived or account.address, process_id=process_id)
        if not lock.acquired:
            return {"status": "blocked", "reason": lock.reason}
        NonceManager(RUNTIME_DIR / "nonce_state.json", signer=cfg.agent_wallet_address_derived or account.address).next_nonce()

    info = Info(MAINNET_URL, skip_ws=True)
    md = HyperliquidMarketData(env="mainnet")
    positions = _open_positions(info, cfg.account_address)
    _prune_closed_entries(state, positions)
    _save_state(state)

    preflight = _start_preflight()
    if preflight["status"] not in {"PASS", "PASS_WITH_CONDITIONAL_SCHEDULECANCEL"} and not _preflight_allows_running_exposure(preflight):
        return {"status": "blocked", "reason": "preflight", "preflight": preflight}
    if _kill_switch_active():
        return {"status": "blocked", "reason": "kill_switch"}
    if Decimal(str(state.get("daily_realized_pnl", "0"))) <= -Decimal(str(limits["max_daily_loss_usdc"])):
        return {"status": "blocked", "reason": "daily_loss_gate"}
    if Decimal(str(state.get("weekly_realized_pnl", "0"))) <= -Decimal(str(limits["max_weekly_loss_usdc"])):
        return {"status": "blocked", "reason": "weekly_loss_gate"}
    if int(state.get("consecutive_losses", 0)) >= int(limits["max_consecutive_losses"]):
        return {"status": "blocked", "reason": "loss_streak_gate"}

    open_orders = info.open_orders(cfg.account_address)
    rec = reconcile_hyperliquid_state(ReconcilerSnapshot(positions=positions, open_orders=open_orders, local_orders=_local_order_records(state)), max_position_notional_usd=Decimal(str(limits["max_total_open_notional_usdc"])), mids={k: Decimal(str(v)) for k, v in md.get_all_mids().items() if k in positions})
    if rec.block_new_entries:
        state["block_new_entries"] = True
        _save_state(state)
        _safe_alert("Crypto Bot CRITICAL: Mainnet reconcile unsafe; no new entries")
        return {"status": "blocked", "reason": "reconcile", "reconciler": {"alerts": rec.alerts, "stops_missing_count": rec.stops_missing_count}}
    state["block_new_entries"] = False
    _save_state(state)

    exchange = Exchange(account, MAINNET_URL, account_address=cfg.account_address) if mode == "live" else None
    mids_now = {k: Decimal(str(v)) for k, v in md.get_all_mids().items()}
    exit_actions = _maybe_manage_position_exits(state, exchange=exchange, open_orders=open_orders, positions=positions, mids=mids_now, limits=limits, mode=mode)
    if exit_actions:
        _save_state(state)
        # Refresh exchange view after stop upgrades before considering new entries.
        open_orders = info.open_orders(cfg.account_address)

    equity = Decimal(str(preflight.get("gate_details", {}).get("mainnet_reconcile", {}).get("free_usdc") or "77"))
    intent, signal = _scan_signal(state, md=md, equity=equity, positions=positions, limits=limits)
    _save_state(state)
    if intent is None:
        return {"status": "ok", "mode": mode, "action": "no_trade", "signal": signal, "positions": len(positions), "open_orders": len(open_orders)}
    _append({"event": "order_intent", "mode": mode, "intent": asdict(intent), "signal": signal, "mainnet_signed_action": False})
    approval_gate = _evaluate_finance_approval_for_intent(intent, mode=mode)
    _append({"event": "finance_approval_gate", "mode": mode, "coin": intent.coin, "strategy_id": intent.strategy_id, "gate": approval_gate, "mainnet_signed_action": False})
    if mode == "live" and not approval_gate.get("allowed"):
        return {"status": "blocked", "reason": "finance_approval_gate", "approval_gate": approval_gate, "mainnet_signed_action": False}
    if mode == "shadow":
        return {"status": "ok", "mode": mode, "action": "shadow_intent_only", "intent": asdict(intent), "mainnet_signed_action": False}
    result = _place_live_trade(intent, exchange=exchange or Exchange(account, MAINNET_URL, account_address=cfg.account_address), info=info, user=cfg.account_address, md=md, signal=signal)
    if result.get("status") == "submitted":
        state.setdefault("recent_trade_coins", []).append(intent.coin)
        state.setdefault("open_entries", {})[intent.coin] = {"client_order_id": intent.client_order_id, "size": str(intent.size), "stop_loss": str(intent.stop_loss), "initial_stop_loss": str(intent.stop_loss), "notional": str(intent.estimated_notional_usd), "opened_at": datetime.now(timezone.utc).isoformat()}
    _save_state(state)
    _append({"event": "live_trade_result", "result": result})
    return {"status": "ok", "mode": mode, "action": "live_trade_result", "result": result, "mainnet_signed_action": True}


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="Tiny Autonomous LiveRuntime for v76 strict. Shadow by default; live only with --mode live.")
    parser.add_argument("--mode", choices=["shadow", "live"], default="shadow")
    parser.add_argument("--iterations", type=int, default=1)
    parser.add_argument("--interval-seconds", type=int, default=60)
    parser.add_argument("--json", action="store_true")
    args = parser.parse_args(argv)
    if args.mode == "live" and os.getenv("CTB_TINY_AUTONOMOUS_LIVE_ALLOWED", "").lower() != "true":
        payload = {"status": "blocked", "reason": "CTB_TINY_AUTONOMOUS_LIVE_ALLOWED must be true", "mainnet_signed_action": False}
        print(json.dumps(payload, indent=2, default=_json_default, sort_keys=True) if args.json else payload)
        return 2
    results = []
    for idx in range(args.iterations):
        results.append(run_once(mode=args.mode))
        if idx < args.iterations - 1:
            time.sleep(args.interval_seconds)
    payload = {"status": "ok" if all(r.get("status") == "ok" for r in results) else "blocked", "mode": args.mode, "results": results, "runtime_dir": str(RUNTIME_DIR)}
    print(json.dumps(payload, indent=2, default=_json_default, sort_keys=True) if args.json else payload)
    return 0 if payload["status"] == "ok" else 2


if __name__ == "__main__":
    raise SystemExit(main())
