"""VerifiedWallet API client for Python 3.8+ — v1.0.0
https://verifiedwallet.io/developers · MIT licence · standard library only

    from verifiedwallet import VerifiedWallet, construct_event
    vw = VerifiedWallet(os.environ["VERIFIEDWALLET_KEY"])          # vw_live_… or vw_test_…
    v = vw.create_verification(tier=3, reference="user-42")
    # send the customer to v["url"], then:
    result = vw.retrieve_verification(v["id"])

    # webhook handler (raw body bytes/str, header "VerifiedWallet-Signature"):
    event = construct_event(raw_body, signature_header, os.environ["VERIFIEDWALLET_WEBHOOK_SECRET"])
"""
import base64
import hashlib
import hmac
import json
import re
import secrets
import time
import urllib.error
import urllib.parse
import urllib.request

__version__ = "1.0.0"


class VerifiedWalletError(Exception):
    def __init__(self, message, status=None, body=None):
        super().__init__(message)
        self.status = status
        self.body = body


class VerifiedWallet:
    def __init__(self, api_key, base_url="https://verifiedwallet.io", timeout=30):
        if not re.fullmatch(r"vw_(live|test)_[0-9a-f]{48}", api_key or ""):
            raise ValueError("VerifiedWallet: pass an API key (vw_live_… or vw_test_…)")
        self.api_key = api_key
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout

    def _request(self, method, path, body=None, auth=True, form=None, headers=None):
        h = {"User-Agent": "verifiedwallet-python/" + __version__}
        data = None
        if auth:
            h["Authorization"] = "Bearer " + self.api_key
        if body is not None:
            h["Content-Type"] = "application/json"
            data = json.dumps(body).encode()
        if form is not None:
            h["Content-Type"] = "application/x-www-form-urlencoded"
            data = urllib.parse.urlencode(form).encode()
        h.update(headers or {})
        req = urllib.request.Request(self.base_url + path, data=data, method=method, headers=h)
        try:
            with urllib.request.urlopen(req, timeout=self.timeout) as res:
                return json.loads(res.read().decode() or "{}")
        except urllib.error.HTTPError as e:
            try:
                payload = json.loads(e.read().decode() or "{}")
            except ValueError:
                payload = {}
            raise VerifiedWalletError(payload.get("error_description") or payload.get("error") or "HTTP %d" % e.code, e.code, payload)

    # --- verifications ---
    def create_verification(self, **params):
        """tier, reference, metadata, wallet, chain, return_url, business, monitor, claims, proof_of_funds, test_outcome"""
        return self._request("POST", "/api/v1/verifications", params)

    def retrieve_verification(self, verification_id):
        return self._request("GET", "/api/v1/verifications/" + urllib.parse.quote(verification_id))

    def list_verifications(self, limit=20):
        return self._request("GET", "/api/v1/verifications?limit=%d" % limit)

    def evidence(self, verification_id):
        return self._request("GET", "/api/v1/verifications/%s/evidence" % urllib.parse.quote(verification_id))

    def set_monitoring(self, verification_id, monitor):
        return self._request("POST", "/api/v1/verifications/%s/monitoring" % urllib.parse.quote(verification_id), {"monitor": bool(monitor)})

    # --- other endpoints ---
    def screen(self, address, chain=None):
        return self._request("POST", "/api/v1/screen", {"address": address, **({"chain": chain} if chain else {})})

    def usage(self):
        return self._request("GET", "/api/v1/usage")

    def check(self, address):
        """Free check before you send: verdict, summary, directory listing, lookalikes."""
        return self._request("GET", "/api/check/" + urllib.parse.quote(address, safe=""), auth=False)

    # --- Verify with VerifiedWallet (OAuth) ---
    @staticmethod
    def pkce():
        verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).rstrip(b"=").decode()
        challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()
        return {"code_verifier": verifier, "code_challenge": challenge}

    def authorize_url(self, client_id, redirect_uri, scope="openid wallet", state=None, nonce=None, code_challenge=None, mode=None, login_hint=None):
        p = {"response_type": "code", "client_id": client_id, "redirect_uri": redirect_uri, "scope": scope, "state": state, "nonce": nonce, "code_challenge": code_challenge, "code_challenge_method": "S256" if code_challenge else None, "mode": mode, "login_hint": login_hint}
        return self.base_url + "/oauth/authorize?" + urllib.parse.urlencode({k: v for k, v in p.items() if v is not None})

    def exchange_code(self, client_id, code, redirect_uri, code_verifier=None):
        basic = base64.b64encode(("%s:%s" % (client_id, self.api_key)).encode()).decode()
        form = {"grant_type": "authorization_code", "code": code, "redirect_uri": redirect_uri}
        if code_verifier:
            form["code_verifier"] = code_verifier
        return self._request("POST", "/oauth/token", auth=False, form=form, headers={"Authorization": "Basic " + basic})


def construct_event(raw_body, header, secret, tolerance_seconds=300, now=None):
    """Checks the VerifiedWallet-Signature header and returns the parsed event, or raises VerifiedWalletError."""
    body = raw_body.decode() if isinstance(raw_body, (bytes, bytearray)) else raw_body
    parts = dict(p.split("=", 1) for p in (header or "").split(",") if "=" in p)
    if "t" not in parts or "v1" not in parts:
        raise VerifiedWalletError("missing signature", 400)
    if abs((now if now is not None else time.time()) - int(parts["t"])) > tolerance_seconds:
        raise VerifiedWalletError("signature too old", 400)
    expected = hmac.new(secret.encode(), ("%s.%s" % (parts["t"], body)).encode(), hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, parts["v1"]):
        raise VerifiedWalletError("invalid signature", 400)
    return json.loads(body)
