#!/usr/bin/env python3
"""
End-to-end example: assemble `solver_results` from the flop decision tree, then call
/v1/gto/range to update ranges along a flop action line (range conversion).

The /v1/gto/range endpoint is a proxy to the solver's /api/range — YOU assemble all
inputs, including the decision tree (`solver_results`). If you already have a flop solve
tree, pass it directly. This script shows how to build it from the platform's flop API:

  1) POST /v1/gto/flop/tree   -> starting oop_range/ip_range + the list of decision nodes
  2) POST /v1/gto/flop {node} -> each node's whole-range strategy (omit hole_cards)
  3) nest into {node_type, player, strategy:{actions,strategy}, childrens}
  4) POST /v1/gto/range       -> the solver narrows the ranges along your node_id

Verified: the assembled tree's /api/range output matches a hand-computed narrowing exactly.

Usage:  POKERAI_API_KEY=gto_xxx python3 assemble_solver_results.py
"""
import json, os, urllib.request

KEY  = os.environ["POKERAI_API_KEY"]
BASE = "https://pokerai.bet"
POS  = ["SB", "BB", "UTG", "MP", "CO", "BTN"]   # postflop action order

def post(path, body):
    req = urllib.request.Request(BASE + path, data=json.dumps(body).encode(),
        headers={"Content-Type": "application/json", "Authorization": "Bearer " + KEY})
    return json.load(urllib.request.urlopen(req, timeout=30))

# --- the spot: SRP, flop 2c2h2s, hero UTG (raiser) vs BTN (caller) ---
spot = {"board": "2c2h2s", "pot_type": "SRP",
        "positions": {"hero": "UTG", "raiser": "UTG", "caller": "BTN"}}
active = ["UTG", "BTN"]

# 1) tree: starting ranges + decision nodes (charges 1 lookup quota)
tree = post("/v1/gto/flop/tree", spot)
oop_range, ip_range = tree["oop_range"], tree["ip_range"]

# OOP = the active player earliest in postflop order; map hero side -> solver player (0=IP, 1=OOP)
oop_pos     = min(active, key=POS.index)
hero_player = 1 if spot["positions"]["hero"] == oop_pos else 0
vill_player = 1 - hero_player

# 2) every node's whole-range strategy (no hole_cards) — keyed by the node path
node = {}
for n in tree["nodes"]:
    nd = post("/v1/gto/flop", {"node": n["token"]})   # no hole_cards -> range_strategy
    node[n["node"]] = {"is_hero": nd["is_hero"], "actions": nd["actions"],
                       "range_strategy": nd["range_strategy"]}

# action naming: api path token (CHECK / BET_8) vs solver name (CHECK / "BET 8.000000")
def token(a):
    act = a["action"].upper()
    return act if act in ("CHECK", "CALL", "FOLD") else f"{act}_{a['amount_bb']}"
def sname(a):
    act = a["action"].upper()
    return act if act in ("CHECK", "CALL", "FOLD") else f"{act} {float(a['amount_bb']):.6f}"

# 3) nest into the solver_results shape
def build(path):
    nd = node[path]
    childrens = {}
    for a in nd["actions"]:
        child = path + "/" + token(a)
        if child in node:                      # decision node -> recurse (off-path/terminal omitted)
            childrens[sname(a)] = build(child)
    return {"node_type": "action_node",
            "player": hero_player if nd["is_hero"] else vill_player,
            "strategy": {"actions": [sname(a) for a in nd["actions"]],
                         "strategy": nd["range_strategy"]},
            "childrens": childrens}

solver_results = build("root")

# 4) range conversion along a flop line (OOP checks). node_id uses solver names; board is comma-separated.
out = post("/v1/gto/range", {
    "range_oop": oop_range, "range_ip": ip_range,
    "solver_results": solver_results,
    "node_id": "root/CHECK",        # e.g. "root/CHECK/BET 8.000000" for OOP check -> IP bet 8
    "board": "2c,2h,2s",
})
print("range_oop_new:", out["range_oop_new"][:120], "...")
print("range_ip_new :", out["range_ip_new"][:120], "...")
print("quota:", out["quota"])
# out["range_oop_new"] / out["range_ip_new"] -> feed as oop_range/ip_range into /v1/gto/solver (turn)
