"""iCryptoNow Gateway — request signing and webhook verification (Python 3.8+, standard library only).

    X-Signature = hex(HMAC-SHA256(secret, f"{timestamp}\\n{METHOD}\\n{path_with_query}\\n{raw_body}"))
    timestamp   = unix milliseconds, within ±5 minutes of our clock

Self-test against the published vectors:  python3 gateway.py ../vectors.json
"""
import hashlib
import hmac
import json
import re
import sys
import time
import urllib.request


def sign(secret: str, timestamp: str, method: str, path_with_query: str, body: str) -> str:
    message = f"{timestamp}\n{method.upper()}\n{path_with_query}\n{body}"
    return hmac.new(secret.encode(), message.encode(), hashlib.sha256).hexdigest()


def request(base_url: str, key_id: str, secret: str, method: str, path_with_query: str, body=None):
    """Call the gateway. `body` is a dict (sent as JSON) or None."""
    raw = "" if body is None else json.dumps(body, separators=(",", ":"))  # sign exactly the bytes you send
    timestamp = str(int(time.time() * 1000))
    headers = {"X-Key-Id": key_id, "X-Timestamp": timestamp, "X-Signature": sign(secret, timestamp, method, path_with_query, raw)}
    if raw:
        headers["Content-Type"] = "application/json"
    req = urllib.request.Request(base_url + path_with_query, data=raw.encode() if raw else None, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req, timeout=15) as res:
            return res.status, json.load(res)
    except urllib.error.HTTPError as err:
        return err.code, json.load(err)


def verify_webhook(secret: str, path_with_query: str, headers: dict, raw_body: str, now_ms=None) -> bool:
    """`raw_body` must be the exact request body (verify BEFORE parsing JSON); `path_with_query` is your callback path."""
    now_ms = int(time.time() * 1000) if now_ms is None else now_ms
    timestamp = headers.get("x-timestamp", "")
    signature = headers.get("x-signature", "")
    if not re.fullmatch(r"\d{13}", timestamp) or abs(now_ms - int(timestamp)) > 5 * 60 * 1000:
        return False
    if not re.fullmatch(r"[0-9a-f]{64}", signature):
        return False
    return hmac.compare_digest(sign(secret, timestamp, "POST", path_with_query, raw_body), signature)


if __name__ == "__main__":
    v = json.load(open(sys.argv[1] if len(sys.argv) > 1 else "../vectors.json"))
    failed = False

    def check(name, ok):
        global failed
        print(("PASS " if ok else "FAIL ") + name)
        failed |= not ok

    for k in ("request_post", "request_get"):
        x = v[k]
        check(k, sign(x["secret"], x["timestamp"], x["method"], x["path"], x["body"]) == x["signature"])
    w = v["webhook"]
    h = {"x-timestamp": w["timestamp"], "x-signature": w["signature"]}
    check("webhook valid", verify_webhook(w["secret"], w["path"], h, w["body"], int(w["timestamp"])))
    check("webhook tampered body rejected", not verify_webhook(w["secret"], w["path"], h, w["body"].replace("100000000", "900000000"), int(w["timestamp"])))
    check("webhook stale timestamp rejected", not verify_webhook(w["secret"], w["path"], h, w["body"], int(w["timestamp"]) + 301_000))
    sys.exit(1 if failed else 0)
