"""Python 3.10+ Spendkit MCP example using only the standard library.

Set SPENDKIT_CLIENT_ID, SPENDKIT_CLIENT_SECRET, and SPENDKIT_RECIPIENT.
The default run is read-only. Payment also requires your wallet adapter below.
The base URL defaults below. Dashboard > AI Agents > your Agent > Connect Agent
provides Client ID/Secret. Your own wallet signer supplies the payment key;
never enter a private key in the dashboard or expose it to the AI model.
"""
import json
import os
import urllib.error
import urllib.parse
import urllib.request

BASE = os.getenv("SPENDKIT_BASE_URL", "https://spendkit-alpha.vercel.app").rstrip("/")
PROTOCOL = "2026-07-28"
CLIENT_INFO = {"name": "spendkit-python-example", "version": "1.0.0"}


def post_json(url, body, headers):
    request = urllib.request.Request(
        url, data=json.dumps(body).encode(), headers=headers, method="POST"
    )
    try:
        with urllib.request.urlopen(request, timeout=15) as response:
            return json.load(response)
    except urllib.error.HTTPError as error:
        raise RuntimeError(f"HTTP {error.code}: {error.read().decode()}") from error


def get_access_token():
    client_id = os.getenv("SPENDKIT_CLIENT_ID")
    client_secret = os.getenv("SPENDKIT_CLIENT_SECRET")
    if not client_id or not client_secret:
        raise RuntimeError("Set SPENDKIT_CLIENT_ID and SPENDKIT_CLIENT_SECRET")
    form = urllib.parse.urlencode({
        "grant_type": "client_credentials",
        "client_id": client_id,
        "client_secret": client_secret,
    }).encode()
    request = urllib.request.Request(
        f"{BASE}/oauth/token", data=form,
        headers={"Content-Type": "application/x-www-form-urlencoded"}, method="POST"
    )
    try:
        with urllib.request.urlopen(request, timeout=15) as response:
            return json.load(response)["access_token"]
    except urllib.error.HTTPError as error:
        raise RuntimeError(f"OAuth HTTP {error.code}: {error.read().decode()}") from error


class SpendkitMcp:
    def __init__(self, token):
        self.token = token
        self.next_id = 0

    def rpc(self, method, params=None):
        self.next_id += 1
        params = dict(params or {})
        params["_meta"] = {
            "io.modelcontextprotocol/protocolVersion": PROTOCOL,
            "io.modelcontextprotocol/clientInfo": CLIENT_INFO,
            "io.modelcontextprotocol/clientCapabilities": {},
        }
        headers = {
            "Authorization": f"Bearer {self.token}",
            "Content-Type": "application/json",
            "Accept": "application/json",
            "MCP-Protocol-Version": PROTOCOL,
            "Mcp-Method": method,
        }
        if method == "tools/call":
            headers["Mcp-Name"] = params["name"]
        message = post_json(f"{BASE}/mcp", {
            "jsonrpc": "2.0", "id": self.next_id,
            "method": method, "params": params,
        }, headers)
        if "error" in message:
            raise RuntimeError(f"MCP {method}: {message['error']}")
        return message["result"]

    def tool(self, name, arguments=None):
        result = self.rpc("tools/call", {"name": name, "arguments": arguments or {}})
        data = result.get("structuredContent")
        if data is None:
            data = json.loads(next(item["text"] for item in result["content"] if item["type"] == "text"))
        if result.get("isError") and data.get("allowed") is not False and data.get("accepted") is not False:
            raise RuntimeError(f"{name} failed: {data}")
        return data


def create_payment_wallet_sender():
    # YOUR WALLET CODE: return a function that validates and submits the exact
    # Router transaction from the wallet bound to this Agent, then returns its
    # real tx hash. Do not give the wallet key to the model or Spendkit Server.
    raise NotImplementedError("Implement the wallet sender before enabling payment")


def main():
    mcp = SpendkitMcp(get_access_token())
    mcp.rpc("server/discover")
    mcp.rpc("tools/list")
    connection = mcp.tool("spendkit_get_connection")
    policy = mcp.tool("spendkit_get_policy")
    print("Agent:", connection["agent"]["name"], "Policy ready:", policy["ready"])

    # YOUR BUSINESS CODE: replace these environment values with the payment
    # amount and recipient selected by your application or AI Agent.
    payment = {
        "amount": os.getenv("SPENDKIT_AMOUNT", "0.01"),
        "recipient": os.getenv("SPENDKIT_RECIPIENT", ""),
        "token": "USDG",
    }
    if not payment["recipient"]:
        print("Set SPENDKIT_RECIPIENT to preview a payment")
        return
    preview = mcp.tool("spendkit_preview_payment", payment)
    print("Preview:", preview["allowed"], preview.get("reason"))
    if not preview["allowed"] or os.getenv("SPENDKIT_ENABLE_PAYMENT") != "1":
        return

    # This fails before an authorization is created until your wallet code exists.
    send_from_bound_wallet = create_payment_wallet_sender()
    key = os.getenv("SPENDKIT_IDEMPOTENCY_KEY")
    if not key:
        raise RuntimeError("Set one stable SPENDKIT_IDEMPOTENCY_KEY per logical payment")
    authorized = mcp.tool("spendkit_authorize_payment", {**payment, "idempotencyKey": key})
    if not authorized.get("allowed") or not authorized.get("transaction"):
        raise RuntimeError("No new transaction was authorized; inspect intent status")
    wallet = connection["agent"]["paymentWallet"]["address"].lower()
    transaction = authorized["transaction"]
    if (transaction["from"].lower() != wallet
            or authorized["authorization"]["paymentWallet"].lower() != wallet
            or authorized["authorization"]["recipient"].lower() != payment["recipient"].lower()
            or authorized["authorization"]["amount"] != preview["amountAtomic"]
            or authorized["authorization"]["token"].lower() != preview["policy"]["tokenAddress"].lower()
            or transaction["to"].lower() != connection["network"]["routerAddress"].lower()
            or transaction["chainId"] != connection["network"]["chainId"]):
        raise RuntimeError("Authorization does not match intended payment or bound wallet")
    tx_hash = send_from_bound_wallet(transaction)
    # KEEP THIS CALL after wallet submission: intentId + txHash lets Spendkit
    # track the payment promptly. Without it, Activity/status can lag until
    # onchain reconciliation; the Router still enforces spending limits.
    recorded = mcp.tool("spendkit_record_payment", {
        "intentId": authorized["intentId"], "txHash": tx_hash,
    })
    # If recording fails, retry with this intentId/txHash; never resend payment.
    if not recorded["accepted"]:
        raise RuntimeError(f"Record failed: {recorded.get('error')}")
    # KEEP THIS CALL to reconcile the receipt; submitted is not final success.
    status = mcp.tool("spendkit_get_payment_status", {"intentId": authorized["intentId"]})
    print("Payment status:", status["status"], "Transaction:", status.get("txHash"))


if __name__ == "__main__":
    main()
