"""x402 payment receipt capture for BOB Score.

Wraps httpx clients so that every successful x402 payment is
automatically submitted to the BOB Score x402 receipt import endpoint.

Two integration patterns:

1. **Transport wrapper** — wraps an httpx.Client so responses with a
   PAYMENT-RESPONSE header are auto-captured::

       from bob import BoBClient
       from bob.x402 import wrap_x402_client

       bob = BoBClient(api_key="bok_...", agent_id="agent-123")
       session = wrap_x402_client(httpx_client, bob)
       res = session.get("https://paid-api.example.com/data")
       # Receipt auto-submitted to BOB Score

2. **Manual submission** — parse and submit a receipt directly::

       from bob.x402 import parse_payment_response, submit_receipt

       receipt = parse_payment_response(response.headers["PAYMENT-RESPONSE"])
       submit_receipt(receipt, bob_client)
"""

from __future__ import annotations

import base64
import json
import logging
from dataclasses import dataclass, field
from typing import Any, Callable

import httpx

from .client import BoBClient

logger = logging.getLogger("bob.x402")

# ---------------------------------------------------------------------------
# Types
# ---------------------------------------------------------------------------


@dataclass
class X402Receipt:
    """Parsed x402 payment receipt from a PAYMENT-RESPONSE header."""

    tx_hash: str | None = None
    network: str = ""
    payer: str = ""
    payee: str | None = None
    amount: str | None = None
    token: str | None = None
    settled_at: str | None = None
    resource_url: str | None = None
    extensions: dict[str, Any] = field(default_factory=dict)


@dataclass
class BobX402Options:
    """Configuration for the x402 → BOB Score integration."""

    on_receipt_submitted: Callable[[X402Receipt], None] | None = None
    on_receipt_error: Callable[[Exception, X402Receipt], None] | None = None


# ---------------------------------------------------------------------------
# Parsing
# ---------------------------------------------------------------------------


def parse_payment_response(header_value: str) -> X402Receipt | None:
    """Parse a PAYMENT-RESPONSE header into an :class:`X402Receipt`.

    The header may be base64-encoded JSON or plain JSON.
    Returns ``None`` if parsing fails or the result has no useful data.
    """
    parsed: dict[str, Any] | None = None

    # Try base64 first
    try:
        decoded = base64.b64decode(header_value).decode("utf-8")
        parsed = json.loads(decoded)
    except Exception:
        pass

    # Fall back to plain JSON
    if parsed is None:
        try:
            parsed = json.loads(header_value)
        except Exception:
            return None

    if not isinstance(parsed, dict):
        return None

    # The settle response may nest data under a "result" key
    data = parsed.get("result", parsed)
    if not isinstance(data, dict):
        return None

    network = str(data.get("network", ""))
    payer = str(data.get("payer", ""))
    if not network and not payer:
        return None

    return X402Receipt(
        tx_hash=data.get("txHash") or data.get("tx_hash") or data.get("transactionHash"),
        network=network,
        payer=payer,
        payee=data.get("payee"),
        amount=str(data["amount"]) if data.get("amount") is not None else None,
        token=data.get("token"),
        settled_at=data.get("settledAt") or data.get("issuedAt") or data.get("settled_at"),
        extensions=data.get("extensions", {}),
    )


# ---------------------------------------------------------------------------
# Submission
# ---------------------------------------------------------------------------


def submit_receipt(
    receipt: X402Receipt,
    bob_client: BoBClient,
    *,
    options: BobX402Options | None = None,
) -> None:
    """Submit an x402 receipt to BOB Score for credit attribution.

    This is fire-and-forget: errors are logged and forwarded to the
    ``on_receipt_error`` callback but never raised.
    """
    try:
        if not receipt.tx_hash:
            err = ValueError("x402 receipt has no tx_hash — cannot submit to BOB")
            if options and options.on_receipt_error:
                options.on_receipt_error(err, receipt)
            else:
                logger.warning("x402: %s", err)
            return

        bob_client.import_x402_receipt(
            tx=receipt.tx_hash,
            network=receipt.network,
            payer=receipt.payer,
            payee=receipt.payee or "",
            amount=receipt.amount or "0",
            asset=receipt.token,
            resource_url=receipt.resource_url,
        )

        if options and options.on_receipt_submitted:
            options.on_receipt_submitted(receipt)

    except Exception as exc:
        if options and options.on_receipt_error:
            options.on_receipt_error(exc, receipt)
        else:
            logger.error("x402: failed to submit receipt to BOB: %s", exc)


# ---------------------------------------------------------------------------
# httpx transport wrapper
# ---------------------------------------------------------------------------


class _X402Transport(httpx.BaseTransport):
    """Wraps an httpx transport to capture x402 PAYMENT-RESPONSE headers."""

    def __init__(
        self,
        transport: httpx.BaseTransport,
        bob_client: BoBClient,
        options: BobX402Options | None = None,
    ):
        self._transport = transport
        self._bob_client = bob_client
        self._options = options

    def handle_request(self, request: httpx.Request) -> httpx.Response:
        response = self._transport.handle_request(request)

        if 200 <= response.status_code < 300:
            header = (
                response.headers.get("payment-response")
                or response.headers.get("PAYMENT-RESPONSE")
            )
            if header:
                receipt = parse_payment_response(header)
                if receipt:
                    receipt.resource_url = str(request.url)
                    submit_receipt(receipt, self._bob_client, options=self._options)

        return response


def wrap_x402_client(
    client: httpx.Client,
    bob_client: BoBClient,
    *,
    options: BobX402Options | None = None,
) -> httpx.Client:
    """Wrap an httpx.Client so x402 payment receipts are auto-captured.

    Returns a new httpx.Client that intercepts responses with a
    ``PAYMENT-RESPONSE`` header and submits them to BOB Score.

    Usage::

        import httpx
        from bob import BoBClient
        from bob.x402 import wrap_x402_client

        bob = BoBClient(api_key="bok_...", agent_id="agent-123")
        session = wrap_x402_client(httpx.Client(), bob)

        # Every x402 payment now auto-reports to BOB Score
        res = session.get("https://paid-api.example.com/data")
    """
    wrapped_transport = _X402Transport(
        transport=client._transport,  # type: ignore[attr-defined]
        bob_client=bob_client,
        options=options,
    )

    return httpx.Client(
        transport=wrapped_transport,
        headers=dict(client.headers),
        timeout=client.timeout,
    )


# ---------------------------------------------------------------------------
# Convenience factory
# ---------------------------------------------------------------------------


def create_bob_x402(
    api_key: str,
    agent_id: str,
    *,
    bob_api_url: str = "https://api.bobscore.ai/api/v1",
    on_receipt_submitted: Callable[[X402Receipt], None] | None = None,
    on_receipt_error: Callable[[Exception, X402Receipt], None] | None = None,
) -> dict[str, Any]:
    """One-liner factory for x402 + BOB Score integration.

    Returns a dict with ``bob_client``, ``wrap_client``, and ``submit_receipt``
    helpers::

        from bob.x402 import create_bob_x402

        bob = create_bob_x402(api_key="bok_...", agent_id="agent-123")

        # Pattern 1: wrap httpx client
        session = bob["wrap_client"](httpx.Client())

        # Pattern 2: manual submission
        bob["submit_receipt"](receipt)
    """
    bob = BoBClient(api_key=api_key, agent_id=agent_id, base_url=bob_api_url)
    opts = BobX402Options(
        on_receipt_submitted=on_receipt_submitted,
        on_receipt_error=on_receipt_error,
    )

    return {
        "bob_client": bob,
        "wrap_client": lambda client: wrap_x402_client(client, bob, options=opts),
        "submit_receipt": lambda receipt: submit_receipt(receipt, bob, options=opts),
    }
