"""Python 3.10+ standard-library client. See quickstart.md. Baseline, not an AI."""
import argparse, json, os, time, uuid, urllib.request, urllib.error, urllib.parse, subprocess
from pathlib import Path
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--state", default="linesclash-agent.json")
p.add_argument("--opponent", default="hell_v2")
p.add_argument("--name", default="Python baseline")
p.add_argument("--control", choices=["production","untimed"], default="production")
p.add_argument("--seat", type=int, choices=[0,1], default=0)
p.add_argument("--stop-after", type=int, default=0)
args = p.parse_args()
BASE = os.environ.get("LINESCLASH_BASE", "http://127.0.0.1:3199").rstrip("/")
url = urllib.parse.urlparse(BASE)
if url.username or url.password or url.query or url.fragment or url.path or not (
    url.scheme == "https" or url.scheme == "http" and url.hostname in ("127.0.0.1","localhost")):
    raise SystemExit("Use an HTTPS origin, or HTTP localhost, without URL credentials")
KEY = os.environ.get("LINESCLASH_KEY")
if not KEY: raise SystemExit("Set LINESCLASH_KEY")
path = Path(args.state)
state = json.loads(path.read_text()) if path.exists() else {
    "base":BASE, "matchId":os.environ.get("MATCH_ID"), "pending":None,
    "create":{"opponent":args.opponent,"name":args.name,"agentSeat":args.seat,
              "control":args.control,"requestId":str(uuid.uuid4())}}
if state["base"] != BASE: raise SystemExit("Different origin; use another --state file")
def save():
    temp = path.with_suffix(path.suffix + ".tmp")
    temp.write_text(json.dumps(state), encoding="utf-8")
    os.chmod(temp, 0o600)
    temp.replace(path)
class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None  # Never forward bearer credentials on a redirect.
opener = urllib.request.build_opener(NoRedirect)
def request(route, data=None):
    req = urllib.request.Request(BASE + route,
        data=None if data is None else json.dumps(data).encode(),
        headers={"Authorization":"Bearer "+KEY,"Content-Type":"application/json"})
    with opener.open(req, timeout=20) as response: return json.load(response)
def choose(obs):
    executable = os.environ.get("LINESCLASH_SOLVER")
    if not executable: return obs["legalMoves"][0]  # Replace with YOUR solver.
    board = "".join("." if n < 0 else "XO"[n] for n in obs["board"])
    remaining = obs["remainingMs"]
    seconds = 5 if remaining is None else min(5,max(.05,(remaining-2000)/1000))
    run = subprocess.run([executable,board,obs["symbol"],str(seconds)],
        capture_output=True,text=True,timeout=seconds+1,check=True)
    if len(run.stdout)>65536: raise RuntimeError("Solver output too large")
    coord = json.loads(run.stdout)
    if coord not in obs["legalMoves"]: raise RuntimeError("Solver returned an illegal coordinate")
    return coord
save()
acknowledged = 0
while True:
    creating = not state["matchId"]
    try:
        if creating:
            obs = request("/api/benchmarks/matches", state["create"])
            state["matchId"] = obs["matchId"]
            save()
            print("Match:",state["matchId"],flush=True)
        elif state["pending"] is not None:
            obs = request("/api/benchmarks/matches/"+state["matchId"]+"/moves",state["pending"])
            state["pending"] = None
            save()
            acknowledged += 1
            if args.stop_after and acknowledged >= args.stop_after:
                print("Checkpoint saved; rerun the same command to reconnect",flush=True)
                break
        else: obs = request("/api/benchmarks/matches/"+state["matchId"])
        if obs["status"] not in ("agent","ai"):
            print(json.dumps({k:obs[k] for k in ("matchId","status","scores","verified")}))
            print("Replay:",BASE+"/benchmarks/matches/"+state["matchId"])
            break
        if obs["status"] == "agent":
            state["pending"] = dict(choose(obs),revision=obs["revision"],moveId=str(uuid.uuid4()))
            save()  # BEFORE sending; identical UUID/payload survives process restart.
        else: time.sleep(2)
    except urllib.error.HTTPError as error:
        if error.code == 409 and not creating:
            state["pending"] = None
            save()  # Fetch current authoritative observation before choosing again.
        elif error.code in (429,503):
            retry = error.headers.get("Retry-After","10")
            time.sleep(min(3600,max(2,int(retry) if retry.isdigit() else 10)))
        else: raise SystemExit("Arena rejected HTTP "+str(error.code)+". Check key/quota/configuration.") from None
    except (TimeoutError,urllib.error.URLError,ConnectionError):
        time.sleep(3)  # Retain create requestId and pending moveId on network loss.
