"""
Client SDK for paying discourse tolls.

Usage:
    from discourse_toll import create_discourse_client

    client = create_discourse_client(nwc_url="nostr+walletconnect://...", max_sats=50)
    result = await client.post("https://forum.example/api/comments", json={"text": "Hello"})
    print(result.paid, result.sats)
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Optional

import httpx


@dataclass
class ClientResponse:
    """Response from a discourse client request."""

    status: int
    headers: dict
    body: str
    paid: bool
    sats: int

    def json(self) -> Any:
        import json
        return json.loads(self.body)


class DiscourseClient:
    """Client that auto-pays L402 discourse tolls."""

    def __init__(
        self,
        *,
        pay_fn: Any,  # async (invoice: str) -> {"preimage": str}
        max_sats: int = 100,
        max_sats_per_context: int = 500,
        agent_id: Optional[str] = None,
        timeout: float = 15.0,
    ):
        self._pay_fn = pay_fn
        self._max_sats = max_sats
        self._max_sats_per_context = max_sats_per_context
        self._agent_id = agent_id
        self._timeout = timeout
        self._context_spend: dict[str, int] = {}

    async def request(
        self,
        method: str,
        url: str,
        *,
        headers: Optional[dict] = None,
        json: Optional[Any] = None,
        data: Optional[str] = None,
    ) -> ClientResponse:
        """Make a request, auto-paying L402 if needed."""
        hdrs = dict(headers or {})
        if self._agent_id:
            hdrs["X-Agent-Id"] = self._agent_id

        async with httpx.AsyncClient(timeout=self._timeout) as client:
            # First request
            resp = await client.request(method, url, headers=hdrs, json=json, content=data)

            if resp.status_code != 402:
                return ClientResponse(
                    status=resp.status_code,
                    headers=dict(resp.headers),
                    body=resp.text,
                    paid=False,
                    sats=0,
                )

            # Parse 402
            toll_data = resp.json()
            if "detail" in toll_data:
                toll_data = toll_data["detail"]

            invoice = toll_data.get("invoice")
            macaroon = toll_data.get("macaroon")
            amount_sats = toll_data.get("amount_sats", 0)
            pricing = toll_data.get("pricing", {})

            if not invoice or not macaroon:
                raise ValueError("402 response missing invoice or macaroon")

            # Budget checks
            if amount_sats > self._max_sats:
                raise ValueError(
                    f"Toll too expensive: {amount_sats} sats (max: {self._max_sats})"
                )

            context_id = pricing.get("context_id", "unknown")
            spent = self._context_spend.get(context_id, 0)
            if spent + amount_sats > self._max_sats_per_context:
                raise ValueError(
                    f"Context budget exceeded in {context_id}"
                )

            # Pay invoice
            payment = await self._pay_fn(invoice)
            preimage = payment.get("preimage")
            if not preimage:
                raise ValueError("Payment succeeded but no preimage returned")

            self._context_spend[context_id] = spent + amount_sats

            # Retry with L402
            hdrs["Authorization"] = f"L402 {macaroon}:{preimage}"
            resp2 = await client.request(method, url, headers=hdrs, json=json, content=data)

            return ClientResponse(
                status=resp2.status_code,
                headers=dict(resp2.headers),
                body=resp2.text,
                paid=True,
                sats=amount_sats,
            )

    async def post(self, url: str, json: Any = None, **kwargs) -> ClientResponse:
        return await self.request("POST", url, json=json, **kwargs)

    async def get(self, url: str, **kwargs) -> ClientResponse:
        return await self.request("GET", url, **kwargs)

    def stats(self) -> dict:
        total = sum(self._context_spend.values())
        return {
            "total_spent": total,
            "contexts": dict(self._context_spend),
            "max_sats": self._max_sats,
            "max_sats_per_context": self._max_sats_per_context,
        }


def create_discourse_client(
    *,
    pay_fn: Any,
    max_sats: int = 100,
    max_sats_per_context: int = 500,
    agent_id: Optional[str] = None,
    timeout: float = 15.0,
) -> DiscourseClient:
    """
    Create a discourse client.

    Args:
        pay_fn: Async function (invoice: str) -> {"preimage": str}
        max_sats: Max sats per single request
        max_sats_per_context: Max sats per context/thread
        agent_id: Agent identifier for trust discounts
        timeout: HTTP timeout
    """
    return DiscourseClient(
        pay_fn=pay_fn,
        max_sats=max_sats,
        max_sats_per_context=max_sats_per_context,
        agent_id=agent_id,
        timeout=timeout,
    )
