#!/usr/bin/env python3
"""
SolCrate agent SDK — minimal example.

An autonomous agent that registers an account, funds it with SOL, opens
provably-fair mystery boxes, reads the outcomes, and can withdraw.

    pip install requests
    python solcrate_agent.py

SolCrate is a REAL-money game of chance played with real SOL. By playing you
affirm the principal is of legal age and permitted to gamble in its jurisdiction.
Machine-readable contract: https://solcrate.shop/api/agent
"""

import time
import uuid
import requests

BASE = "https://solcrate.shop"
LAMPORTS_PER_SOL = 1_000_000_000


class SolCrate:
    def __init__(self, base_url: str = BASE):
        self.base = base_url.rstrip("/")
        self.token: str | None = None
        self.recovery_code: str | None = None
        self.deposit_address: str | None = None
        self._s = requests.Session()

    # --- helpers -----------------------------------------------------------
    def _headers(self) -> dict:
        return {"Authorization": f"Bearer {self.token}"} if self.token else {}

    def _post(self, path: str, json: dict | None = None) -> dict:
        r = self._s.post(f"{self.base}{path}", json=json or {}, headers=self._headers(), timeout=30)
        r.raise_for_status()
        return r.json()

    def _get(self, path: str) -> dict:
        r = self._s.get(f"{self.base}{path}", headers=self._headers(), timeout=30)
        r.raise_for_status()
        return r.json()

    # --- API ---------------------------------------------------------------
    def manifest(self) -> dict:
        """Box catalog with odds + expected value, and the full contract."""
        return self._get("/api/agent")

    def register(self) -> dict:
        data = self._post("/api/agent/register")
        self.token = data["token"]
        self.recovery_code = data["recoveryCode"]
        self.deposit_address = data["depositAddress"]
        self._consent_version = data["consentVersion"]
        return data

    def accept_consent(self) -> dict:
        # Affirms the principal is of legal age and permitted to gamble.
        return self._post("/api/consent", {"version": self._consent_version, "age": True})

    def session(self) -> dict:
        return self._get("/api/session")

    def check_deposits(self) -> dict:
        return self._post("/api/deposits")

    def open_boxes(self, box_type_id: str, quantity: int = 1) -> dict:
        return self._post("/api/purchase", {
            "boxTypeId": box_type_id,
            "quantity": quantity,
            "idempotencyKey": str(uuid.uuid4()),  # replays never double-charge
        })

    def withdraw(self, address: str, sol: float) -> dict:
        return self._post("/api/withdraw", {
            "address": address,
            "sol": sol,
            "idempotencyKey": str(uuid.uuid4()),
        })

    def wait_for_funding(self, min_lamports: int = LAMPORTS_PER_SOL // 10, timeout_s: int = 600) -> int:
        """Poll until the balance covers at least `min_lamports`. Returns the balance."""
        deadline = time.time() + timeout_s
        while time.time() < deadline:
            bal = self.check_deposits().get("balanceLamports", 0)
            if bal >= min_lamports:
                return bal
            time.sleep(10)
        raise TimeoutError("No funds received in time.")


def main() -> None:
    sc = SolCrate()

    # 1. Read the catalog and pick a box (every box wins something; the
    #    expected value / RTP is disclosed so you can decide by EV).
    boxes = {b["id"]: b for b in sc.manifest()["boxes"]}
    box = boxes["starter"]
    print(f"Chosen box: {box['name']} — {box['priceSol']} SOL, EV {box['expectedValueSol']} SOL, RTP {box['rtpPercent']}%")

    # 2. Register and accept the policy.
    acct = sc.register()
    print(f"Account {acct['playerId']}")
    print(f"  token        : {sc.token[:12]}…  (send as Bearer on every call)")
    print(f"  recoveryCode : {sc.recovery_code[:12]}…  (STORE THIS — only way back in)")
    print(f"  deposit to   : {sc.deposit_address}")
    sc.accept_consent()

    # 3. Fund the account (send SOL to the deposit address from any wallet).
    print(f"\nSend some SOL to {sc.deposit_address} … waiting for it to credit.")
    balance = sc.wait_for_funding(min_lamports=box["priceLamports"])
    print(f"Funded: {balance / LAMPORTS_PER_SOL:.4f} SOL")

    # 4. Open boxes while the balance covers the price, and act on outcomes.
    total_won = 0.0
    while balance >= box["priceLamports"]:
        res = sc.open_boxes(box["id"], quantity=1)
        outcome = res["results"][0]
        won = outcome["prizeSol"]
        total_won += won
        balance = res["balanceLamports"]
        print(f"  opened → {outcome['tier']['name']:>12}  +{won} SOL   (roll {outcome['roll']}, balance {balance/LAMPORTS_PER_SOL:.4f})")
        # Example decision rule: stop after a big pull.
        if won >= box["priceSol"] * 10:
            print("  Big win — stopping.")
            break

    print(f"\nTotal won this run: {total_won} SOL. Final balance: {balance/LAMPORTS_PER_SOL:.4f} SOL")
    # 5. Cash out with:  sc.withdraw("<your-solana-address>", 0.05)


if __name__ == "__main__":
    main()
