"""Romu client: every request is signed for you (Python 3.10 or later; pip install cryptography)."""
import base64
import hashlib
import json as jsonlib
import os
import secrets
import time
import urllib.error
import urllib.request

from cryptography.hazmat.primitives.serialization import load_pem_private_key

BASE_URL = os.environ.get("ROMU_BASE_URL", "https://api.romu.ir")
KEY_ID = os.environ.get("ROMU_KEY_ID", "")
KEY_FILE = os.environ.get("ROMU_KEY_FILE", "romu-api-key.pem")


def string_to_sign(key_id, timestamp, nonce, method, path, query, body, idempotency_key):
    """Nine lines joined with a newline, no newline after the last one."""
    body_hash = hashlib.sha256(body).hexdigest()
    return "\n".join(["custody.api.request.v1", key_id, str(timestamp), nonce, method, path, query, body_hash, idempotency_key])


def b64url(raw):
    return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()


def call(method, path, query="", json=None, idempotency_key=""):
    """Send one signed request. The query goes out exactly as it was signed."""
    with open(KEY_FILE, "rb") as f:
        key = load_pem_private_key(f.read(), password=None)
    body = b"" if json is None else jsonlib.dumps(json, separators=(",", ":")).encode()
    timestamp = int(time.time())
    nonce = secrets.token_urlsafe(18)
    message = string_to_sign(KEY_ID, timestamp, nonce, method, path, query, body, idempotency_key)
    headers = {
        "X-Custody-Key-Id": KEY_ID,
        "X-Custody-Timestamp": str(timestamp),
        "X-Custody-Nonce": nonce,
        "X-Custody-Signature": b64url(key.sign(message.encode())),
    }
    if idempotency_key:
        headers["Idempotency-Key"] = idempotency_key
    if json is not None:
        headers["Content-Type"] = "application/json"
    url = BASE_URL + path + ("?" + query if query else "")
    req = urllib.request.Request(url, data=body if json is not None else None, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req) as res:
            return res.status, jsonlib.load(res)
    except urllib.error.HTTPError as err:
        return err.code, jsonlib.load(err)


def get(path, **options):
    return call("GET", path, **options)


def post(path, **options):
    return call("POST", path, **options)


def put(path, **options):
    return call("PUT", path, **options)


def patch(path, **options):
    return call("PATCH", path, **options)
