"""
End-to-end x402 payment test on Base Sepolia.

This script:
1. Starts a local FastAPI server with x402 paywall ($0.01 USDC)
2. Checks the endpoint (gets 402)
3. Pays for access using testnet wallet
4. Shows the on-chain settlement tx

Requirements:
  - pip install 'x402[httpx,evm]' fastapi uvicorn
  - Testnet wallet with Base Sepolia USDC (faucet.circle.com)
  - ETH for gas (alchemy faucet)

Usage:
  python test_x402_e2e.py
"""

import json
import os
import sys
import time
import threading
from pathlib import Path

# ── Config ───────────────────────────────────────────────────────────────────

WALLET_NAME = "testnet"
CHAIN = "base_sepolia"
NETWORK = "eip155:84532"
FACILITATOR_URL = "https://x402.org/facilitator"
SERVER_PORT = 4402
SERVER_URL = f"http://localhost:{SERVER_PORT}"
PRICE = "$0.01"  # 0.01 USDC

# Wallet
DATA_DIR = Path(os.environ.get("ERC8004_DATA_DIR", "/tmp/strands_erc8004"))
WALLET_FILE = DATA_DIR / "wallets" / f"{WALLET_NAME}.json"


def load_wallet():
    if not WALLET_FILE.exists():
        print(f"❌ Wallet not found: {WALLET_FILE}")
        sys.exit(1)
    with open(WALLET_FILE) as f:
        return json.load(f)


def check_balances(wallet_address: str):
    """Check ETH + USDC balances on Base Sepolia."""
    from web3 import Web3

    w3 = Web3(Web3.HTTPProvider("https://sepolia.base.org"))
    addr = Web3.to_checksum_address(wallet_address)

    eth_bal = w3.eth.get_balance(addr)
    eth_amount = w3.from_wei(eth_bal, "ether")

    # USDC on Base Sepolia
    usdc_addr = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
    abi = [
        {
            "inputs": [{"name": "account", "type": "address"}],
            "name": "balanceOf",
            "outputs": [{"name": "", "type": "uint256"}],
            "stateMutability": "view",
            "type": "function",
        }
    ]
    contract = w3.eth.contract(address=Web3.to_checksum_address(usdc_addr), abi=abi)
    usdc_bal = contract.functions.balanceOf(addr).call()
    usdc_amount = usdc_bal / 1e6

    return float(eth_amount), usdc_amount, usdc_bal


# ── Server ───────────────────────────────────────────────────────────────────


def create_server(pay_to: str):
    """Create a FastAPI server with x402 paywall."""
    from fastapi import FastAPI, Request, Response
    from x402 import x402ResourceServerSync, ResourceConfig
    from x402.http import (
        HTTPFacilitatorClientSync,
        FacilitatorConfig,
        x402HTTPResourceServerSync,
        RouteConfig,
        RoutesConfig,
    )
    from x402.mechanisms.evm.exact import register_exact_evm_server
    from x402.http.constants import (
        PAYMENT_REQUIRED_HEADER,
        PAYMENT_SIGNATURE_HEADER,
        PAYMENT_RESPONSE_HEADER,
    )
    from x402.http.utils import (
        decode_payment_signature_header,
        encode_payment_required_header,
        encode_payment_response_header,
    )

    app = FastAPI(title="x402 Test Server")

    # Set up x402 resource server
    fac_client = HTTPFacilitatorClientSync(
        FacilitatorConfig(url=FACILITATOR_URL, timeout=30.0)
    )
    resource_server = x402ResourceServerSync(fac_client)
    register_exact_evm_server(resource_server)
    resource_server.initialize()

    # Build payment requirements once
    config = ResourceConfig(
        scheme="exact",
        network=NETWORK,
        pay_to=pay_to,
        price=PRICE,
        description="Test weather API endpoint",
    )
    requirements = resource_server.build_payment_requirements(config)

    print(f"  📋 Payment requirements built:")
    for r in requirements:
        print(f"     scheme={r.scheme} network={r.network} amount={r.amount} asset={r.asset}")

    @app.get("/weather")
    async def weather(request: Request):
        # Check for payment signature
        payment_sig = request.headers.get(PAYMENT_SIGNATURE_HEADER)

        if not payment_sig:
            # Return 402 with payment requirements
            from x402.schemas import PaymentRequired, ResourceInfo

            payment_required = resource_server.create_payment_required_response(
                requirements=requirements,
                resource=ResourceInfo(
                    url=f"{SERVER_URL}/weather",
                    description="Weather data for AI agents",
                ),
                error="Payment required to access weather data",
            )

            encoded = encode_payment_required_header(payment_required)

            return Response(
                content=payment_required.model_dump_json(by_alias=True, exclude_none=True),
                status_code=402,
                headers={
                    PAYMENT_REQUIRED_HEADER: encoded,
                    "Content-Type": "application/json",
                },
            )

        # Verify and settle payment
        try:
            payload = decode_payment_signature_header(payment_sig)

            # Verify
            verify_result = resource_server.verify_payment(payload, requirements[0])
            if not verify_result.is_valid:
                return Response(
                    content=json.dumps({"error": f"Payment invalid: {verify_result.invalid_reason}"}),
                    status_code=402,
                    headers={"Content-Type": "application/json"},
                )

            # Settle
            settle_result = resource_server.settle_payment(payload, requirements[0])
            if not settle_result.success:
                return Response(
                    content=json.dumps({"error": f"Settlement failed: {settle_result.error_reason}"}),
                    status_code=402,
                    headers={"Content-Type": "application/json"},
                )

            # Payment successful! Return data + settlement receipt
            settlement_header = encode_payment_response_header(settle_result)

            return Response(
                content=json.dumps({
                    "weather": {
                        "location": "San Francisco",
                        "temperature": 68,
                        "unit": "fahrenheit",
                        "conditions": "sunny",
                        "forecast": "Clear skies, perfect for AI agents 🤖",
                    },
                    "payment": {
                        "status": "settled",
                        "tx_hash": settle_result.transaction,
                        "network": settle_result.network,
                        "payer": settle_result.payer,
                    },
                }),
                status_code=200,
                headers={
                    PAYMENT_RESPONSE_HEADER: settlement_header,
                    "Content-Type": "application/json",
                },
            )

        except Exception as e:
            return Response(
                content=json.dumps({"error": str(e)}),
                status_code=500,
                headers={"Content-Type": "application/json"},
            )

    @app.get("/health")
    async def health():
        return {"status": "ok", "x402": True, "price": PRICE, "network": NETWORK}

    return app


def start_server(pay_to: str):
    """Start the test server in a background thread."""
    import uvicorn

    app = create_server(pay_to)

    config = uvicorn.Config(app, host="0.0.0.0", port=SERVER_PORT, log_level="warning")
    server = uvicorn.Server(config)

    thread = threading.Thread(target=server.run, daemon=True)
    thread.start()

    # Wait for server to be ready
    import httpx

    for _ in range(20):
        try:
            r = httpx.get(f"{SERVER_URL}/health", timeout=2)
            if r.status_code == 200:
                return server
        except Exception:
            pass
        time.sleep(0.5)

    print("❌ Server failed to start")
    sys.exit(1)


# ── E2E Test ─────────────────────────────────────────────────────────────────


def run_e2e():
    print("=" * 60)
    print("🧪 x402 End-to-End Payment Test (Base Sepolia)")
    print("=" * 60)

    # 1. Load wallet
    wallet = load_wallet()
    wallet_addr = wallet["address"]
    print(f"\n1️⃣  Wallet: {wallet_addr}")

    # 2. Check balances
    print("\n2️⃣  Checking balances...")
    eth_bal, usdc_bal, usdc_raw = check_balances(wallet_addr)
    print(f"   ETH:  {eth_bal:.6f} ETH")
    print(f"   USDC: {usdc_bal:.6f} USDC ({usdc_raw} raw)")

    if eth_bal == 0:
        print("\n   ⚠️  No ETH for gas! Get some from:")
        print("   → https://www.alchemy.com/faucets/base-sepolia")
        print("   → https://faucets.chain.link/base-sepolia")
        print(f"   Address: {wallet_addr}")

    if usdc_raw < 10000:  # < 0.01 USDC
        print("\n   ⚠️  Insufficient USDC! Get some from:")
        print("   → https://faucet.circle.com/ (select Base Sepolia)")
        print(f"   Address: {wallet_addr}")

    if usdc_raw < 10000:
        print("\n   ❌ Insufficient USDC. Fund the wallet first.")
        print(f"   Wallet: {wallet_addr}")
        return False

    if eth_bal == 0:
        print("\n   ℹ️  No ETH — that's OK! x402 uses EIP-3009 (transferWithAuthorization)")
        print("   The facilitator submits the on-chain tx, not our wallet.")

    # 3. Start server
    print(f"\n3️⃣  Starting x402-protected server on port {SERVER_PORT}...")
    server = start_server(wallet_addr)  # Pay to ourselves for testing
    print(f"   ✅ Server running at {SERVER_URL}/weather")

    # 4. Check (should get 402)
    print("\n4️⃣  Checking endpoint (should return 402)...")
    import httpx

    resp = httpx.get(f"{SERVER_URL}/weather", timeout=10)
    print(f"   Status: {resp.status_code}")
    if resp.status_code == 402:
        print("   ✅ Got 402 Payment Required!")
        pr_header = resp.headers.get("PAYMENT-REQUIRED")
        if pr_header:
            from x402.http.utils import decode_payment_required_header

            pr = decode_payment_required_header(pr_header)
            print(f"   Version: {pr.x402_version}")
            for a in pr.accepts:
                print(f"   Accepts: {a.scheme} on {a.network}, amount={a.amount}, asset={a.asset[:20]}...")
    else:
        print(f"   ❌ Expected 402, got {resp.status_code}")
        return False

    # 5. Pay!
    print("\n5️⃣  Making payment...")
    from eth_account import Account
    from x402 import x402ClientSync
    from x402.mechanisms.evm.exact import register_exact_evm_client
    from x402.mechanisms.evm.signers import EthAccountSigner
    from x402.http import x402HTTPClientSync, PAYMENT_SIGNATURE_HEADER, PAYMENT_RESPONSE_HEADER
    from x402.http.utils import decode_payment_response_header

    account = Account.from_key(wallet["private_key"])
    signer = EthAccountSigner(account)
    print(f"   Signer: {signer.address}")

    client = x402ClientSync()
    register_exact_evm_client(client, signer)
    http_client = x402HTTPClientSync(client)

    # Handle 402
    resp_headers = dict(resp.headers)
    payment_headers, payload = http_client.handle_402_response(resp_headers, resp.content)

    print(f"   Payload created:")
    print(f"     Version: {payload.x402_version}")
    print(f"     Scheme: {payload.get_scheme()}")
    print(f"     Network: {payload.get_network()}")

    # Retry with payment
    print("\n   Sending payment with retry request...")
    paid_resp = httpx.get(
        f"{SERVER_URL}/weather",
        headers=payment_headers,
        timeout=60,
    )

    print(f"   Response: {paid_resp.status_code}")

    if paid_resp.status_code == 200:
        data = paid_resp.json()
        print("\n   ✅ PAYMENT SUCCESSFUL!")
        print(f"\n   📊 Weather Data:")
        weather = data.get("weather", {})
        for k, v in weather.items():
            print(f"      {k}: {v}")

        payment_info = data.get("payment", {})
        print(f"\n   💰 Payment Info:")
        print(f"      Status: {payment_info.get('status')}")
        print(f"      TX Hash: {payment_info.get('tx_hash')}")
        print(f"      Network: {payment_info.get('network')}")
        print(f"      Payer: {payment_info.get('payer')}")

        tx_hash = payment_info.get("tx_hash")
        if tx_hash:
            print(f"\n   🔗 View on explorer:")
            print(f"      https://sepolia.basescan.org/tx/{tx_hash}")

        # Parse settlement header
        settle_header = paid_resp.headers.get(PAYMENT_RESPONSE_HEADER)
        if settle_header:
            settle = decode_payment_response_header(settle_header)
            print(f"\n   📝 Settlement Receipt:")
            print(f"      Success: {settle.success}")
            print(f"      TX: {settle.transaction}")
            print(f"      Network: {settle.network}")
            print(f"      Payer: {settle.payer}")
    else:
        print(f"\n   ❌ Payment failed: {paid_resp.text}")
        return False

    # 6. Check balances after
    print("\n6️⃣  Checking balances after payment...")
    eth_after, usdc_after, _ = check_balances(wallet_addr)
    print(f"   ETH:  {eth_after:.6f} (was {eth_bal:.6f}, gas: {eth_bal - eth_after:.6f})")
    print(f"   USDC: {usdc_after:.6f} (was {usdc_bal:.6f})")
    print(f"   Note: We paid ourselves, so USDC should be ~same minus facilitator fee")

    print("\n" + "=" * 60)
    print("✅ END-TO-END TEST COMPLETE")
    print("=" * 60)
    return True


if __name__ == "__main__":
    run_e2e()
