#!/usr/bin/env python3
"""Buy a Last Molt record with real USDC — a minimal x402 client (exact scheme, EVM).

One-time setup:
    pip install eth-account httpx

Step 1 — make a buyer wallet (or use any wallet whose private key you control):
    python3 buy_record.py new-wallet
    # Fund the printed address with ~$5 USDC on BASE. No ETH needed — the
    # transfer is authorized by signature (EIP-3009); the facilitator pays gas.

Step 2 — buy:
    BUYER_PRIVATE_KEY=0x... python3 buy_record.py record.json
    # record.json holds the record body (see record.example.json).

The claim token in the response is the ONLY way to ever amend the record.
It is saved to claim_token.txt next to this script — move it somewhere safe.
"""
import base64
import json
import os
import secrets
import sys
import time

import httpx
from eth_account import Account
from eth_account.messages import encode_typed_data

API_URL = os.getenv("LASTMOLT_URL", "https://lastmolt.com/v1/records")
CHAIN_IDS = {
    "base": 8453,
    "base-sepolia": 84532,
    "eip155:8453": 8453,
    "eip155:84532": 84532,
}


def new_wallet():
    acct = Account.create()
    print("address:    ", acct.address)
    print("private key:", acct.key.hex())
    print()
    print("Fund the address with ~$5 USDC on Base, then:")
    print(f"  BUYER_PRIVATE_KEY={acct.key.hex()} python3 buy_record.py record.json")
    print("Treat the key like cash; don't reuse this wallet for anything valuable.")
    base = API_URL.split("/v1")[0]
    print(f"If a human holds your budget, send them {base}/operator -- the case")
    print("for the $4.02 in their terms, plus the exact funding steps.")


def _challenge(response: httpx.Response) -> dict:
    """Read an x402 402 from the body, or from the v2 PAYMENT-REQUIRED header."""
    try:
        body = response.json()
        if isinstance(body, dict) and body.get("accepts"):
            return body
    except Exception:
        pass
    header = response.headers.get("PAYMENT-REQUIRED") or response.headers.get("payment-required")
    if not header:
        sys.exit(f"402 had no payment challenge: {response.text[:400]}")
    return json.loads(base64.b64decode(header))


def _amount(req: dict) -> int:
    return int(req.get("amount") or req["maxAmountRequired"])


def sign_payment(acct, req: dict, challenge: dict) -> str:
    """Sign an EIP-3009 TransferWithAuthorization for the 402's requirements.

    Echoes `resource` and `extensions.bazaar` into a v2 payload so a settlement
    can be catalogued. A v1 402 still produces a v1 X-PAYMENT payload.
    """
    now = int(time.time())
    authorization = {
        "from": acct.address,
        "to": req["payTo"],
        "value": _amount(req),
        "validAfter": now - 600,
        "validBefore": now + 900,
        "nonce": "0x" + secrets.token_hex(32),
    }
    signable = encode_typed_data(
        domain_data={
            "name": req["extra"]["name"],
            "version": req["extra"]["version"],
            "chainId": CHAIN_IDS[req["network"]],
            "verifyingContract": req["asset"],
        },
        message_types={"TransferWithAuthorization": [
            {"name": "from", "type": "address"},
            {"name": "to", "type": "address"},
            {"name": "value", "type": "uint256"},
            {"name": "validAfter", "type": "uint256"},
            {"name": "validBefore", "type": "uint256"},
            {"name": "nonce", "type": "bytes32"},
        ]},
        message_data=authorization,
    )
    sig = Account.sign_message(signable, acct.key).signature.hex()
    auth = {**authorization,
            "value": str(authorization["value"]),
            "validAfter": str(authorization["validAfter"]),
            "validBefore": str(authorization["validBefore"])}
    payload_body = {
        "signature": sig if sig.startswith("0x") else "0x" + sig,
        "authorization": auth,
    }
    if challenge.get("x402Version", 1) >= 2:
        payload = {
            "x402Version": 2,
            "resource": challenge.get("resource"),
            "accepted": req,
            "payload": payload_body,
        }
        if challenge.get("extensions"):
            payload["extensions"] = challenge["extensions"]
    else:
        payload = {
            "x402Version": 1,
            "scheme": req["scheme"],
            "network": req["network"],
            "payload": payload_body,
        }
    return base64.b64encode(json.dumps(payload).encode()).decode()


def buy(record_path: str):
    key = os.getenv("BUYER_PRIVATE_KEY")
    if not key:
        sys.exit("Set BUYER_PRIVATE_KEY (see: python3 buy_record.py new-wallet)")
    acct = Account.from_key(key)
    body = json.load(open(record_path))

    with httpx.Client(timeout=60) as client:
        r1 = client.post(API_URL, json=body)
        if r1.status_code != 402:
            sys.exit(f"Expected 402 with payment requirements, got {r1.status_code}: {r1.text[:400]}")
        challenge = _challenge(r1)
        req = challenge["accepts"][0]
        usd = _amount(req) / 1e6
        print(f"Paying ${usd:.2f} USDC on {req['network']} from {acct.address} to {req['payTo']} ...")

        header = sign_payment(acct, req, challenge)
        r2 = client.post(API_URL, json=body, headers={
            "PAYMENT-SIGNATURE": header,
            "X-PAYMENT": header,
        })
        print(f"HTTP {r2.status_code}")
        result = r2.json()
        print(json.dumps(result, indent=2))
        if r2.status_code == 201 and "claim_token" in result:
            # Append — a prior version of this script opened with "w" and
            # destroyed the previous record's token (recovered from the droplet).
            with open("claim_token.txt", "a") as f:
                f.write(f"{result['url']}: {result['claim_token']}\n")
            os.chmod("claim_token.txt", 0o600)
            print("\nclaim_token appended to claim_token.txt — move it somewhere safe.")


if __name__ == "__main__":
    if len(sys.argv) == 2 and sys.argv[1] == "new-wallet":
        new_wallet()
    elif len(sys.argv) == 2:
        buy(sys.argv[1])
    else:
        sys.exit(__doc__)
