"""Live integration test against a real facilitator.

Opt-in: set A402_KEY (a real provider API key) and, optionally, A402_FACILITATOR
(defaults to staging). Skips when A402_KEY is unset so the default `pytest` run
stays offline/green. Only non-settling paths are exercised — a 402 challenge and
a settle round-trip the facilitator rejects at verify — so it never moves funds.
"""
from __future__ import annotations

import base64
import json
import os
import time

import pytest
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Route
from starlette.testclient import TestClient

from a402 import A402Config, A402Middleware

API_KEY = os.environ.get("A402_KEY")
FACILITATOR = os.environ.get("A402_FACILITATOR", "https://facilitator-testnet.a402.finance")

pytestmark = pytest.mark.skipif(not API_KEY, reason="set A402_KEY to run the live facilitator integration test")


def _client() -> TestClient:
    async def gated(_: Request) -> JSONResponse:
        return JSONResponse({"data": "secret"})

    async def public(_: Request) -> JSONResponse:
        return JSONResponse({"ok": True})

    app = Starlette(routes=[
        Route("/v1/financials", gated, methods=["GET"]),
        Route("/public", public, methods=["GET"]),
    ])
    app.add_middleware(
        A402Middleware,
        # Generous timeout: the multi-chain challenge does per-chain reads and can
        # be slow on a loaded staging box; a client timeout here would look like an
        # SDK failure when it's really latency.
        config=A402Config(facilitator_url=FACILITATOR, api_key=API_KEY, timeout=30.0),
        routes=[{"method": "GET", "path": "/v1/financials", "price": "$0.05"}],
    )
    return TestClient(app, raise_server_exceptions=False)


def _challenge(client: TestClient, attempts: int = 4):
    """GET the gated route, retrying transient facilitator 5xx (staging latency)."""
    resp = None
    for i in range(attempts):
        resp = client.get("/v1/financials")
        if resp.status_code == 402:
            return resp
        time.sleep(1.5 * (i + 1))
    return resp


def test_non_gated_passthrough():
    r = _client().get("/public")
    assert r.status_code == 200
    assert r.json() == {"ok": True}


def test_real_402_challenge_has_populated_accepts():
    r = _challenge(_client())
    assert r.status_code == 402, r.text
    body = r.json()
    assert body["x402Version"] == 1
    accepts = body["accepts"]
    assert isinstance(accepts, list) and len(accepts) >= 1
    for a in accepts:
        assert a["nonce"] and a["network"] and a["asset"] and a["payTo"] and a["value"]


def test_malformed_x_payment_returns_400():
    r = _client().get("/v1/financials", headers={"X-PAYMENT": "!!!not-valid!!!"})
    assert r.status_code == 400
    assert "malformed" in r.json()["error"]


def test_unverifiable_payment_is_forwarded_no_settlement():
    client = _client()
    first = _challenge(client).json()["accepts"][0]
    xpayment = base64.b64encode(json.dumps({
        "apiKeyId": first["apiKeyId"],
        "network": first["network"],
        "authorization": {
            "from": "0x000000000000000000000000000000000000dEaD",
            "to": first["payTo"], "value": first["value"],
            "validAfter": "0", "validBefore": first["validBefore"],
            "nonce": "0x" + "ab" * 32,  # never issued → rejected at verify
        },
        "signature": "0x" + "de" * 65,
    }).encode()).decode()
    r = client.get("/v1/financials", headers={"X-PAYMENT": xpayment})
    assert r.status_code >= 400, r.text
    assert r.json() != {"data": "secret"}, "gated handler must not run on a rejected payment"
