"""
Smoke test for MGC order placement via TopstepXAdapter.

Isolates the broker-side rejection observed on April 30 2026:
  - Brain approves, tp_resolved emits.
  - place_market_bracket fails with empty error (→ "no detail" fallback).

This script bypasses the bracket flow and places ONLY the market entry,
capturing the full SDK response including errorMessage/errorCode. If the
entry succeeds, it closes the position immediately to leave no exposure.

Usage:
    cd ~/apex_v16
    venv/bin/python -m tools.smoke_mgc --size 1 --side BUY
    venv/bin/python -m tools.smoke_mgc --size 5 --side BUY  # reproduce bot scenario
    venv/bin/python -m tools.smoke_mgc --bracket --size 1 --side BUY
    venv/bin/python -m tools.smoke_mgc --dry-run            # no order, just connect

Output: dump everything we know about the response — orderId, errorMessage,
errorCode, raw response repr, plus exception type + repr if it raises.
"""

from __future__ import annotations

import argparse
import asyncio
import logging
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from broker.topstepx_adapter import TopstepXAdapter


SYMBOL = "MGC"


def _setup_logging() -> None:
    fmt = "%(asctime)s %(levelname)s [%(name)s] %(message)s"
    logging.basicConfig(level=logging.INFO, format=fmt)
    # surface the SDK + adapter logs verbosely
    for name in ("topstepx_adapter", "topstepx_v16", "project_x_py", "broker"):
        logging.getLogger(name).setLevel(logging.DEBUG)


def _dump(label: str, obj) -> None:
    print(f"\n=== {label} ===")
    if obj is None:
        print("None")
        return
    print(f"type: {type(obj).__name__}")
    print(f"repr: {obj!r}")
    # walk attrs
    for attr in ("orderId", "order_id", "success", "errorCode", "error_code",
                 "errorMessage", "error_message", "status", "raw"):
        if hasattr(obj, attr):
            try:
                print(f"  .{attr} = {getattr(obj, attr)!r}")
            except Exception as e:
                print(f"  .{attr} -> raised {type(e).__name__}: {e}")
    # if dict-like
    if isinstance(obj, dict):
        for k, v in obj.items():
            print(f"  [{k!r}] = {v!r}")


async def _try_market_entry(adapter: TopstepXAdapter, side: int, size: int) -> bool:
    """
    Place a standalone market order (no bracket). Returns True if filled.
    """
    info = adapter._instrument_cache.get(SYMBOL)
    print(f"\n--- instrument cache for {SYMBOL} ---")
    print(info)

    contract_id = info["contract_id"]
    ctx = adapter._get_ctx(SYMBOL)

    print(f"\n>>> place_market_order({SYMBOL}, side={side}, size={size}, "
          f"contract={contract_id})")
    try:
        resp = await ctx.orders.place_market_order(
            contract_id=contract_id, side=side, size=int(size),
        )
    except Exception as e:
        print(f"\n!!! place_market_order RAISED")
        print(f"  type: {type(e).__name__}")
        print(f"  str:  {str(e)!r}")
        print(f"  repr: {e!r}")
        # ProjectXOrderError specifically — try to peel any context
        for attr in ("errorMessage", "error_message", "args", "response"):
            if hasattr(e, attr):
                print(f"  .{attr} = {getattr(e, attr)!r}")
        return False

    _dump("place_market_order response", resp)

    order_id = getattr(resp, "orderId", None) or getattr(resp, "order_id", None)
    if not order_id:
        return False

    # Poll positions for fill (8s max)
    print(f"\n--- polling positions for fill (max 8s) ---")
    for attempt in range(16):
        await asyncio.sleep(0.5)
        try:
            positions = await adapter.positions_get(SYMBOL)
            if positions:
                print(f"  filled at attempt {attempt+1}: {positions[0]}")
                return True
        except Exception as e:
            print(f"  positions_get error #{attempt+1}: {type(e).__name__}: {e}")
    print("  NOT filled in 8s — order may be pending/rejected")
    return False


async def _try_bracket(adapter: TopstepXAdapter, side: str, size: int) -> None:
    """Test full bracket via the same path the bot uses."""
    print(f"\n>>> place_market_bracket via adapter ({SYMBOL}, {side}, {size}ct)")
    last = await adapter.get_current_price(SYMBOL)
    print(f"  current price: {last}")

    if side == "BUY":
        sl = round((last - 6.0) * 10) / 10
        tp = round((last + 2.5) * 10) / 10
    else:
        sl = round((last + 6.0) * 10) / 10
        tp = round((last - 2.5) * 10) / 10
    print(f"  sl={sl}  tp={tp}")

    resp = await adapter.place_market_bracket(
        symbol=SYMBOL, side=side, size=int(size),
        sl_price=sl, tp_price=tp,
    )
    _dump("place_market_bracket response", resp)


async def _cleanup(adapter: TopstepXAdapter) -> None:
    """Always close any open position on SYMBOL before exit."""
    try:
        positions = await adapter.positions_get(SYMBOL)
        if positions:
            print(f"\n!!! cleanup: closing position on {SYMBOL}: {positions[0]}")
            close_resp = await adapter.close_position(SYMBOL)
            _dump("cleanup close_position response", close_resp)
        else:
            print(f"\n--- cleanup: no position to close on {SYMBOL}")
    except Exception as e:
        print(f"\n!!! cleanup ERROR ({type(e).__name__}): {e}")


async def main_async(args) -> int:
    _setup_logging()

    print(f"=== smoke_mgc ===  side={args.side}  size={args.size}  "
          f"bracket={args.bracket}  dry_run={args.dry_run}")

    adapter = TopstepXAdapter()
    print("\n>>> adapter.connect([MGC])")
    ok = await adapter.connect([SYMBOL])
    print(f"  connect ok: {ok}")
    if not ok:
        return 2

    try:
        if args.dry_run:
            print("\n--- DRY RUN: connected ok, exiting without orders")
            print(f"  cache: {adapter._instrument_cache.get(SYMBOL)}")
            return 0

        side_int = adapter.SIDE_MAP.get(args.side)
        if side_int is None:
            print(f"!!! invalid side {args.side}")
            return 3

        if args.bracket:
            await _try_bracket(adapter, args.side, args.size)
        else:
            await _try_market_entry(adapter, side_int, args.size)
    finally:
        await _cleanup(adapter)
        try:
            await adapter.disconnect()
        except Exception as e:
            print(f"  disconnect warn: {e}")

    return 0


def main() -> int:
    p = argparse.ArgumentParser()
    p.add_argument("--size", type=int, default=1)
    p.add_argument("--side", choices=("BUY", "SELL"), default="BUY")
    p.add_argument("--bracket", action="store_true",
                   help="Use place_market_bracket (entry+SL+TP) instead of standalone entry")
    p.add_argument("--dry-run", action="store_true",
                   help="Connect only — do not place any order")
    args = p.parse_args()
    return asyncio.run(main_async(args))


if __name__ == "__main__":
    sys.exit(main())
