"""Trade8 signing example for Python 3.10+. Run only in a trusted server process."""
import base64
import hashlib
import hmac
import json
import os
import re
import time
from urllib.error import HTTPError
from urllib.parse import urlsplit
from urllib.request import Request, HTTPRedirectHandler, build_opener

REST_BASE = "https://api.trade8.xyz"


def sign(secret, timestamp, method, path, body="", idempotency_key=""):
    key = base64.b64decode(secret, validate=True)
    if len(key) != 32 or base64.b64encode(key).decode() != secret:
        raise ValueError("Use the base64-encoded 32-byte API secret.")
    payload = "\n".join([str(timestamp), method.upper(), path, idempotency_key, body])
    return base64.b64encode(hmac.new(key, payload.encode("utf-8"), hashlib.sha256).digest()).decode()


def prepare_request(method, path, data=None, idempotency_key="", credentials=None):
    method = method.upper()
    if method not in {"GET", "POST", "PUT", "PATCH", "DELETE"}:
        raise ValueError("Unsupported HTTP method.")
    parsed = urlsplit(path)
    if not path.startswith("/v1/") or parsed.scheme or parsed.netloc or parsed.fragment or "\\" in path or any(part in {".", ".."} for part in parsed.path.split("/")):
        raise ValueError("Use an encoded /v1/ path and query without a fragment or host.")
    if method == "GET" and (data is not None or idempotency_key):
        raise ValueError("GET requests use an empty body and idempotency key.")
    if method != "GET" and not re.fullmatch(r"[A-Za-z0-9_-]{1,64}", idempotency_key):
        raise ValueError("Supply a unique Idempotency-Key for each mutation.")
    body = "" if data is None else json.dumps(data, separators=(",", ":"), ensure_ascii=False)
    headers = {"Accept": "application/json"}
    if body:
        headers["Content-Type"] = "application/json"
    if method != "GET":
        headers["Idempotency-Key"] = idempotency_key
    public = method == "GET" and re.fullmatch(r"/v1/(markets(?:/[^/]+)?|venues)/?", parsed.path)
    if not public:
        if not credentials or not credentials.get("key") or not credentials.get("secret"):
            raise ValueError("Supply an API key and secret for authenticated REST requests.")
        timestamp = str(credentials.get("timestamp", int(time.time())))
        headers.update({"X-Trade8-API-Key": credentials["key"], "X-Trade8-Timestamp": timestamp,
                        "X-Trade8-Signature": sign(credentials["secret"], timestamp, method, path, body, idempotency_key)})
    return Request(REST_BASE + path, data=body.encode("utf-8") if body else None, headers=headers, method=method)


class RejectRedirects(HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        raise RuntimeError("Redirect rejected. Check the Trade8 API URL.")


def request(method, path, data=None, idempotency_key=""):
    key, secret = os.getenv("TRADE8_API_KEY"), os.getenv("TRADE8_API_SECRET")
    credentials = {"key": key, "secret": secret}
    prepared = prepare_request(method, path, data, idempotency_key, credentials)
    try:
        with build_opener(RejectRedirects()).open(prepared, timeout=10) as response:
            return json.load(response)
    except HTTPError as error:
        detail = error.read().decode("utf-8", errors="replace")
        raise RuntimeError(f"HTTP {error.code}; request ID {error.headers.get('X-Request-ID')}: {detail}") from error


if __name__ == "__main__":
    print(request("GET", "/v1/markets?instrument=BTC-PERP&limit=50"))
