"""Prove Poly-US supports the two primitives the live maker depends on.

DO NOT SKIP THIS AND BUILD ON THE DOCS. poly_us_trader.cancel_order carries the
reason in its own docstring: the previous cancel called an endpoint that did not
exist, returned 404 every time, and nothing noticed for the entire period the
account was auto-trading -- because nothing had ever needed to cancel. Docs said
one thing, the venue did another. Prefer evidence.

Two things get proven here:

  POST-ONLY (`participateDontInitiate`) -- the maker's whole economic case is
      receiving the 0.0125*p(1-p) rebate rather than paying the 0.06*p(1-p)
      taker fee. If a crossing order is silently ACCEPTED instead of rejected,
      that sign flip happens without warning.

  SELF-EXPIRY (GTD + `goodTillTime`) -- Kalshi orders die in 90s so a crash
      cannot leave anything resting. This box was off for 2.5 days unnoticed on
      2026-08-10 and had a 15-hour silent outage on 2026-08-18, so DAY orders
      are not an acceptable substitute.

SAFETY OF THE PROBE ITSELF
  * Prices are placed FAR from the market (deep, unfillable) except for the one
    deliberate crossing test, which is EXPECTED to be rejected.
  * Size is the smallest the venue allows and is capped by
    poly_us_trader.MAX_DOLLARS_PER_ORDER ($1.00).
  * Everything placed is cancelled at the end, and the script re-reads open
    orders to confirm the account is flat before it exits.
  * --dry prints the plan and places nothing.

Usage:  python research\\polyus_capability_probe.py --dry
        python research\\polyus_capability_probe.py --live
"""
import sys
import time

sys.path.insert(0, r'E:\soccer\main')
import poly_us_auth as pa
import poly_us_prices as prices
import poly_us_trader as put

DRY = '--dry' in sys.argv or '--live' not in sys.argv


def show_orders(tag):
    try:
        o = put.open_orders()
    except Exception as e:
        print(f'   [{tag}] open_orders FAILED: {type(e).__name__}: {e}')
        return []
    print(f'   [{tag}] resting: {len(o)}')
    for x in o:
        print(f'        {x.get("orderId") or x.get("id")}  {x.get("marketSlug")}  '
              f'{x.get("price")}  qty={x.get("quantity")}  {x.get("status")}')
    return o


def pick_market():
    """A real soccer market with a two-sided book.

    get_bbo returns bestBid/bestAsk/askDepth/bidDepth -- NOT bid/ask. Reading
    the wrong keys made the first version of this silently find nothing and
    report "no suitable market", which looks identical to the venue being
    closed. Same shape of bug as the fee-field trap in kalshi_maker.
    """
    import live_monitor as LM
    _, matches = LM.load_watchlist_file()
    for m in matches:
        div = (m.get('lc') or '').upper()
        for side in ('home', 'draw', 'away'):
            try:
                mk = prices.find_market(div, m.get('home'), m.get('away'), side)
            except Exception:
                mk = None
            if not mk:
                continue
            slug = mk.get('slug') or mk.get('marketSlug')
            if not slug:
                continue
            try:
                bbo = prices.get_bbo(slug)
            except Exception:
                continue
            if not bbo:
                continue
            bid, ask = bbo.get('bestBid'), bbo.get('bestAsk')
            if bid is None or ask is None:
                continue
            bid, ask = float(bid), float(ask)
            # need room to place a DEEP unfillable order below the bid
            if 0.15 < bid < ask < 0.95:
                return (slug, bid, ask,
                        f"{m.get('home')} v {m.get('away')} [{side}] "
                        f"depth bid={bbo.get('bidDepth')} ask={bbo.get('askDepth')}")
    return None, None, None, None


def main():
    print('=== Poly-US capability probe ===')
    print(f'mode: {"DRY (nothing placed)" if DRY else "LIVE (real orders)"}')
    b = pa.get_account_balances()['balances'][0]
    print(f'balance ${float(b["currentBalance"]):.2f}  '
          f'buyingPower ${float(b.get("buyingPower") or 0):.2f}  '
          f'reserved ${float(b.get("balanceReservation") or 0):.2f}')
    print()

    slug, bid, ask, label = pick_market()
    if not slug:
        print('no suitable two-sided market found right now -- try again nearer kickoffs')
        return 1
    print(f'market: {slug}')
    print(f'   {label}   bid={bid:.2f}  ask={ask:.2f}')
    print()

    # Deep price: well below the bid, so it cannot fill. This is the RESTING test.
    deep = max(0.01, round(bid - 0.10, 2))
    qty = put.size_for_dollars(0.50, deep)

    print('PLAN')
    print(f'  1. post-only + GTD 90s BUY at {deep:.2f} (deep, unfillable) qty={qty}')
    print(f'     -> expect: accepted, rests, disappears within ~90s')
    print(f'  2. post-only BUY at {ask:.2f} (crosses the ask)')
    print(f'     -> expect: REJECTED. If accepted, post-only does NOT work.')
    print(f'  3. cancel anything left, verify flat')
    print()

    if DRY:
        print('DRY RUN -- nothing placed.')
        return 0

    show_orders('before')

    print()
    print('--- 1. post-only + GTD resting order ---')
    try:
        r = put.place_limit_order(slug, deep, qty, post_only=True, ttl_secs=90)
        oid = r.get('orderId') or (r.get('order') or {}).get('orderId')
        print(f'   accepted, orderId={oid}')
    except Exception as e:
        print(f'   FAILED: {type(e).__name__}: {e}')
        oid = None
    time.sleep(3)
    show_orders('after place')

    print()
    print('--- 2. post-only crossing order ---')
    # HTTP 200 IS NOT ACCEPTANCE. Verified 2026-08-22: a post-only order that
    # would cross comes back {"id": "...", "executions": []} -- a success-shaped
    # 200 -- and is then silently killed: it never executes and never rests.
    # An earlier version of this probe read the 200 alone and wrongly reported
    # "post-only does NOT work". The real test is executions + did it rest +
    # did the balance move. The maker must verify the same way rather than
    # trusting the response, since "received" and "resting" look identical here.
    bal0 = float(pa.get_account_balances()['balances'][0]['currentBalance'])
    try:
        r2 = put.place_limit_order(slug, ask, qty, post_only=True)
        execs = (r2 or {}).get('executions') or []
        time.sleep(6)
        rested = [x for x in put.open_orders() if x.get('marketSlug') == slug
                  and str(x.get('price', {}).get('value', '')) == f'{ask:.2f}']
        bal1 = float(pa.get_account_balances()['balances'][0]['currentBalance'])
        if execs or rested or abs(bal1 - bal0) > 1e-6:
            print(f'   *** POST-ONLY FAILED: executions={len(execs)} '
                  f'resting={len(rested)} balance delta {bal1-bal0:+.4f}')
            print('   *** DO NOT run the maker here until this is understood.')
            for x in rested:
                put.cancel_order(x.get('id') or x.get('orderId'), slug)
        else:
            print('   post-only HELD: 200 returned, but no execution, nothing '
                  'rested, balance unchanged -- the cross was refused.')
    except Exception as e:
        print(f'   rejected at HTTP level: {type(e).__name__}: {str(e)[:200]}')

    print()
    print('--- 3. wait for GTD expiry (~90s) ---')
    for i in range(7):
        time.sleep(20)
        try:
            n = len(put.open_orders())
        except Exception:
            n = -1
        print(f'   t+{20*(i+1):3}s  resting={n}')
        if n == 0:
            print('   -> GTD expiry WORKS')
            break
    else:
        print('   -> still resting after ~140s: GTD expiry did NOT work')

    print()
    print('--- cleanup ---')
    left = show_orders('final')
    for x in left:
        i = x.get('orderId') or x.get('id')
        s = x.get('marketSlug') or slug
        try:
            put.cancel_order(i, s)
            print(f'   cancelled {i}')
        except Exception as e:
            print(f'   CANCEL FAILED {i}: {type(e).__name__}: {e}')
    show_orders('post-cleanup')
    return 0


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