"""
smolagents x402 plugin — pay-per-call data APIs with automatic 402 handling.

Trial mode (no key): plain unpaid calls, free quota per IP.
Paid mode (set X402_PRIVATE_KEY): on HTTP 402 the tool parses the
PAYMENT-REQUIRED header (base64 STANDARD — not base64url), signs an
EIP-3009 transferWithAuthorization on USDC/Base with your wallet, and
retries with the X-PAYMENT header. No facilitator account needed.

    pip install smolagents requests eth_account
    export X402_PRIVATE_KEY=0x...   # optional; trial mode works without it

    from smolagents import CodeAgent, InferenceClientModel
    from smolagents_x402 import X402WebSearchPaid, X402TokenPricePaid

    agent = CodeAgent(tools=[X402WebSearchPaid(), X402TokenPricePaid()],
                      model=InferenceClientModel())
    agent.run("Price of USDC on Base and latest Base DeFi news")

Set MAX_USD_PER_CALL to cap spend per call (default $0.05).
Source: https://api.mekongtradeintel.com/tools/plugins/smolagents_x402.py
"""
import base64
import json
import os
import secrets
import time

import requests

USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
BASE_URL = "https://api.mekongtradeintel.com"
UA = {"User-Agent": "x402-smolagents-plugin/1.0", "Content-Type": "application/json"}
MAX_USD_PER_CALL = float(os.environ.get("X402_MAX_USD_PER_CALL", "0.05"))

try:
    from smolagents import Tool
except ImportError:  # pragma: no cover - readable without smolagents installed
    class Tool:  # minimal stub
        pass

try:
    from eth_account import Account
    from eth_account.messages import encode_typed_data
    _HAS_ETH = True
except ImportError:
    _HAS_ETH = False

_EIP712_TYPES = {
    "EIP712Domain": [
        {"name": "name", "type": "string"},
        {"name": "version", "type": "string"},
        {"name": "chainId", "type": "uint256"},
        {"name": "verifyingContract", "type": "address"},
    ],
    "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"},
    ],
}


def _decode_payment_required(header_value: str) -> dict:
    # CRITICAL (fleet bug fix 2026-10-20): PAYMENT-REQUIRED is base64
    # STANDARD. Do NOT use base64url here — only JWT signing uses base64url.
    return json.loads(base64.b64decode(header_value).decode("utf-8"))


def _x_payment_header(private_key: str, accepts: dict) -> str:
    if not _HAS_ETH:
        raise RuntimeError("pip install eth_account for paid mode")
    acc = accepts[0]
    if int(acc["amount"]) / 1e6 > MAX_USD_PER_CALL:
        raise RuntimeError("call price exceeds X402_MAX_USD_PER_CALL cap")
    acct = Account.from_key(private_key)
    now = int(time.time())
    auth = {
        "from": acct.address,
        "to": acc["payTo"],
        "value": int(acc["amount"]),
        "validAfter": 0,
        "validBefore": now + 600,
        "nonce": "0x" + secrets.token_hex(32),
    }
    typed = {
        "types": _EIP712_TYPES,
        "primaryType": "TransferWithAuthorization",
        "domain": {"name": "USD Coin", "version": "2", "chainId": 8453,
                   "verifyingContract": acc["asset"]},
        "message": auth,
    }
    sig = Account.sign_message(encode_typed_data(full_message=typed),
                               private_key=private_key).signature.hex()
    payment = {"x402Version": 2, "scheme": acc.get("scheme", "exact"),
               "network": acc["network"],
               "payload": {"signature": "0x" + sig, "authorization": auth}}
    return base64.b64encode(json.dumps(payment).encode()).decode()  # standard b64


def _paid_request(method: str, url: str, payload: dict | None) -> requests.Response:
    headers = dict(UA)
    data = json.dumps(payload) if payload is not None else None
    r = requests.request(method, url, headers=headers, data=data, timeout=60)
    if r.status_code != 402:
        return r
    pk = os.environ.get("X402_PRIVATE_KEY")
    if not pk:
        return r  # trial mode: surface the 402 to the caller
    header = next((v for k, v in r.headers.items()
                   if k.lower() == "payment-required"), None)
    if not header:
        return r
    accepts = _decode_payment_required(header).get("accepts") or []
    if not accepts:
        return r
    headers["X-PAYMENT"] = _x_payment_header(pk, accepts)
    return requests.request(method, url, headers=headers, data=data, timeout=60)


class X402PaidTool(Tool):
    """Generic x402 pay-per-call tool. Configure with endpoint details."""

    def __init__(self, name: str, description: str, url: str, method: str,
                 price_usdc: float, inputs: dict, output_type: str = "string"):
        self.name = name
        self.description = description
        self.url = url
        self.method = method
        self.price_usdc = price_usdc
        self.inputs = inputs
        self.output_type = output_type

    def forward(self, **kwargs) -> str:
        if self.method == "GET":
            r = _paid_request("GET", self.url, None)
        else:
            r = _paid_request(self.method, self.url, kwargs)
        if r.status_code == 402:
            return (f"402 Payment Required (${self.price_usdc}/call). "
                    "Set X402_PRIVATE_KEY to pay in USDC on Base, or wait for trial quota reset.")
        r.raise_for_status()
        d = r.json()
        return d if isinstance(d, str) else json.dumps(d)[:4000]


class X402WebSearchPaid(X402PaidTool):
    def __init__(self):
        super().__init__(
            name="web_search",
            description=("Live web search. Free trial quota, then $0.01/call in USDC "
                         "on Base (x402) — paid automatically if X402_PRIVATE_KEY is set."),
            url=f"{BASE_URL}/websearch/api/websearch", method="POST", price_usdc=0.01,
            inputs={"query": {"type": "string", "description": "Search query"}})

    def forward(self, query: str) -> str:
        out = super().forward(query=query, num=5)
        try:
            items = json.loads(out).get("results") or []
            return "\n".join(f"- {(it.get('title') or '')[:120]}" for it in items[:5])
        except Exception:
            return out


class X402TokenPricePaid(X402PaidTool):
    def __init__(self):
        super().__init__(
            name="token_price",
            description=("Live token price/market-cap/liquidity via DexScreener. Free trial, "
                         "then $0.001/call in USDC on Base (x402)."),
            url=f"{BASE_URL}/tokenprice/api/token-price", method="POST", price_usdc=0.001,
            inputs={"chain": {"type": "string", "description": 'e.g. "base"'},
                    "address": {"type": "string", "description": "Token contract address"}})

    def forward(self, chain: str, address: str) -> str:
        out = super().forward(chain=chain, address=address)
        try:
            d = json.loads(out)
            return (f"{d.get('token', {}).get('symbol')}: ${d.get('priceUsd')} | "
                    f"mcap ${d.get('marketCap')} | 24h vol ${d.get('volume24hUsd')}")
        except Exception:
            return out


if __name__ == "__main__":
    print(X402WebSearchPaid().forward("Base chain DeFi"))
