"""Partner Webhook Example (Python)

Demonstrates how a payment partner (Assemble AI, NS3, or any EVM chain)
should structure their webhook payload when notifying Vaultfire of a payment.

The HMAC-SHA256 signature must be computed over the canonical JSON message
that matches x402_receipts.py's _canonical_receipt_message() function.

Webhook endpoint: POST /vaultfire/x402-webhook
(provided by x402_listener.py Flask blueprint)

Usage:
    PARTNER_SECRET=yoursecret python examples/partner-webhook.py

Environment variables:
    VAULTFIRE_WEBHOOK_URL   — Vaultfire webhook URL (default: https://theloopbreaker.com/...)
    PARTNER_SECRET          — Shared HMAC secret from Vaultfire
    PARTNER_ID              — Your partner identifier ('assemble', 'ns3', etc.)
    KEY_ID                  — Your key rotation ID (default: 'k1')

Facilitator: https://x402.org/facilitator
Hub:         https://theloopbreaker.com
"""

from __future__ import annotations

import hashlib
import hmac
import json
import os
import secrets
import time
import urllib.request
from typing import Any, Mapping

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------

VAULTFIRE_WEBHOOK_URL: str = os.getenv(
    "VAULTFIRE_WEBHOOK_URL",
    "https://theloopbreaker.com/vaultfire/x402-webhook",
)
PARTNER_SECRET: str = os.getenv("PARTNER_SECRET", "your-partner-secret-from-vaultfire")
PARTNER_ID: str = os.getenv("PARTNER_ID", "assemble")
KEY_ID: str = os.getenv("KEY_ID", "k1")


# ---------------------------------------------------------------------------
# HMAC signing helpers
# ---------------------------------------------------------------------------

def build_canonical_message(payload: Mapping[str, Any]) -> str:
    """Build the canonical JSON string for HMAC signing.

    Must match the canonicalization in x402_receipts._canonical_receipt_message().

    The canonical message includes only privacy-safe fields, sorted alphabetically,
    with partner_id + key_id to prevent cross-issuer replay attacks.
    """
    msg = {
        "partner_id": payload.get("partner_id") or payload.get("issuer"),
        "key_id": payload.get("key_id") or payload.get("kid") or "k1",
        "rail": payload.get("rail") or payload.get("provider"),
        "currency": (payload.get("currency") or "").upper(),
        "amount": payload.get("amount"),
        "wallet_address": payload.get("wallet_address") or payload.get("wallet"),
        "tx_ref": (
            payload.get("tx_ref")
            or payload.get("tx_hash")
            or payload.get("transaction_hash")
        ),
        "nonce": payload.get("nonce"),
        "timestamp": payload.get("timestamp"),
    }
    return json.dumps(msg, separators=(",", ":"), sort_keys=True)


def sign_payload(payload: Mapping[str, Any], secret: str) -> str:
    """Return hex(HMAC-SHA256(secret, canonical_message(payload)))."""
    message = build_canonical_message(payload)
    return hmac.new(
        secret.encode("utf-8"),
        message.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()


def generate_nonce() -> str:
    """Generate a cryptographically secure random nonce."""
    return secrets.token_hex(16)


# ---------------------------------------------------------------------------
# Build a signed webhook payload
# ---------------------------------------------------------------------------

def create_signed_payload(
    *,
    rail: str,
    currency: str,
    amount: float,
    wallet_address: str,
    tx_ref: str | None = None,
    loyalty_score: float = 0.0,
    event_type: str = "payment",
    status: str = "completed",
) -> dict[str, Any]:
    """Create a signed payment webhook payload.

    Parameters
    ----------
    rail:           Payment rail identifier ('assemble', 'ns3', 'evm').
    currency:       Currency symbol ('ASM', 'ETH', 'USDC').
    amount:         Payment amount (as a float).
    wallet_address: Payer wallet address or handle.
    tx_ref:         Transaction reference / receipt ID.
    loyalty_score:  Partner loyalty score 0.0–1.0 (high scores trigger airdrops).
    event_type:     Event type ('payment', 'refund', 'airdrop', etc.).
    status:         Payment status ('completed', 'pending', etc.).

    Returns the signed payload dict ready to POST to /vaultfire/x402-webhook.
    """
    timestamp = time.time()
    nonce = generate_nonce()

    base_payload: dict[str, Any] = {
        "type": event_type,
        "status": status,
        "rail": rail,
        "currency": currency,
        "amount": amount,
        "wallet_address": wallet_address,
        "tx_ref": tx_ref or f"{rail}_{int(timestamp)}_{nonce[:8]}",
        "loyalty_score": loyalty_score,
        "partner_id": PARTNER_ID,
        "key_id": KEY_ID,
        "nonce": nonce,
        "timestamp": timestamp,
        # Ghostkey mode: codex signature for wallet verification
        "codex_signature": "codex::partner-webhook",
        # Optional: belief signal for additional wallet verification
        "belief_signal": {
            "confidence": 0.95,
            "source": "belief-engine",
            "verified": True,
        },
    }

    # Compute and attach the HMAC signature
    hmac_sig = sign_payload(base_payload, PARTNER_SECRET)
    base_payload["signature"] = hmac_sig
    return base_payload


# ---------------------------------------------------------------------------
# Send webhook
# ---------------------------------------------------------------------------

def send_webhook(payload: dict[str, Any], url: str = VAULTFIRE_WEBHOOK_URL) -> dict[str, Any]:
    """POST a signed webhook payload to the Vaultfire x402 listener.

    Parameters
    ----------
    payload: Signed payload dict.
    url:     Webhook endpoint URL.

    Returns the parsed JSON response from the server.
    """
    body = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        url,
        data=body,
        headers={
            "Content-Type": "application/json",
            "Content-Length": str(len(body)),
            "X-Partner-ID": PARTNER_ID,
            "X-Key-ID": KEY_ID,
        },
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            return {
                "status": resp.status,
                "body": json.loads(resp.read().decode("utf-8")),
            }
    except urllib.error.HTTPError as exc:
        return {
            "status": exc.code,
            "body": json.loads(exc.read().decode("utf-8")),
        }


# ---------------------------------------------------------------------------
# Server-side: verify an incoming webhook signature
# ---------------------------------------------------------------------------

def verify_incoming_webhook(payload: Mapping[str, Any], secret: str) -> bool:
    """Verify an incoming webhook's HMAC signature.

    Use this on your server to validate that a payload was signed with your
    shared secret. Uses constant-time comparison to prevent timing attacks.

    Parameters
    ----------
    payload: The incoming webhook payload.
    secret:  The shared HMAC secret.

    Returns True if the signature is valid.
    """
    received = str(payload.get("signature", "")).lower()
    expected = sign_payload(payload, secret)
    return hmac.compare_digest(expected, received)


# ---------------------------------------------------------------------------
# Examples
# ---------------------------------------------------------------------------

def example_assemble_payment() -> None:
    """Send an ASM payment webhook from Assemble AI."""
    print("=== Vaultfire x402 Partner Webhook — Python Example ===\n")

    payload = create_signed_payload(
        rail="assemble",
        currency="ASM",
        amount=0.00021,
        wallet_address="bpow20.cb.id",  # ghostkey wallet
        tx_ref=f"asm_receipt_{int(time.time())}",
        loyalty_score=0.90,
    )

    print("[webhook] Sending payload:")
    print(json.dumps(payload, indent=2))

    response = send_webhook(payload)
    print(f"\n[webhook] Response status: {response['status']}")
    print(f"[webhook] Response body: {json.dumps(response['body'], indent=2)}")

    if response.get("body", {}).get("airdrop"):
        print("\n[webhook] AIRDROP TRIGGERED!")
        print(json.dumps(response["body"]["airdrop"], indent=2))


def example_ns3_payment() -> None:
    """Send an ETH/ASM payment webhook from an NS3 session."""
    payload = create_signed_payload(
        rail="ns3",
        currency="ETH",
        amount=0.00031,
        wallet_address="ephemeral:0xdeadbeef",
        wallet_type="ephemeral",
        tx_ref=f"ns3_sess_{secrets.token_hex(8)}",
        loyalty_score=0.70,
    )

    print("\n[webhook] NS3 session payment:")
    print(json.dumps(payload, indent=2))
    response = send_webhook(payload)
    print(f"[webhook] Response: {response['status']} — {response['body']}")


def example_signature_verification() -> None:
    """Demonstrate server-side HMAC verification."""
    payload = create_signed_payload(
        rail="assemble",
        currency="ASM",
        amount=0.0001,
        wallet_address="bpow20.cb.id",
    )
    valid = verify_incoming_webhook(payload, PARTNER_SECRET)
    print(f"\n[verify] Signature valid: {valid}")

    # Tamper with the amount and verify it fails
    tampered = dict(payload)
    tampered["amount"] = 9999.0
    valid_tampered = verify_incoming_webhook(tampered, PARTNER_SECRET)
    print(f"[verify] Tampered payload valid: {valid_tampered} (expected: False)")


# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    example_assemble_payment()
    example_signature_verification()
