import base64
import hashlib
import json
import os
import time
from typing import Any, Dict
from urllib import request as urlrequest

from nacl.signing import SigningKey


def _b64url_encode(data: bytes) -> str:
    return base64.urlsafe_b64encode(data).decode("utf-8").rstrip("=")


def _b64url_decode(data: str) -> bytes:
    pad = "=" * (-len(data) % 4)
    return base64.urlsafe_b64decode(data + pad)


def _sha256_b64url(data: bytes) -> str:
    return _b64url_encode(hashlib.sha256(data).digest())


def _random_nonce() -> str:
    return _b64url_encode(os.urandom(16))


def _build_transcript(
    tenant_id: str,
    api_id: str,
    client_id: str,
    method: str,
    path: str,
    body_hash: str,
    time_bucket: int,
    nonce: str,
) -> str:
    return "\n".join(
        [
            "version=tp1",
            f"tenant_id={tenant_id}",
            f"api_id={api_id}",
            f"client_id={client_id}",
            f"method={method}",
            f"path={path}",
            f"body_hash={body_hash}",
            f"time_bucket={time_bucket}",
            f"nonce={nonce}",
        ]
    )


def sign(
    tenant_id: str,
    api_id: str,
    client_id: str,
    private_key_b64url: str,
    method: str = "GET",
    path: str = "/",
    body: str = "",
    bucket_seconds: int = 20,
    time_bucket_override: int = None,
    nonce_override: str = None,
) -> Dict[str, Any]:
    if not tenant_id or not api_id or not client_id:
        raise ValueError("tenant_id, api_id, and client_id are required")
    if not private_key_b64url:
        raise ValueError("private_key_b64url is required")
    if not path:
        raise ValueError("path is required")

    body_hash = f"sha256:{_sha256_b64url(body.encode('utf-8'))}"
    now_seconds = int(time.time())
    bucket = bucket_seconds if bucket_seconds > 0 else 20
    time_bucket = time_bucket_override if time_bucket_override is not None else (now_seconds // bucket)
    nonce = nonce_override if nonce_override is not None else _random_nonce()

    transcript = _build_transcript(
        tenant_id,
        api_id,
        client_id,
        method.upper(),
        path,
        body_hash,
        time_bucket,
        nonce,
    )

    digest = hashlib.sha256(transcript.encode("utf-8")).digest()
    priv = _b64url_decode(private_key_b64url)
    if len(priv) != 64:
        raise ValueError("private_key_b64url must be ed25519 private key (64 bytes)")
    signing_key = SigningKey(priv[:32])
    signature = signing_key.sign(digest).signature
    signature_b64url = _b64url_encode(signature)

    headers = {
        "x-tp-tenant-id": tenant_id,
        "x-tp-api-id": api_id,
        "x-tp-client-id": client_id,
        "x-tp-time-bucket": str(time_bucket),
        "x-tp-nonce": nonce,
        "x-tp-signature": signature_b64url,
        "x-tp-body-hash": body_hash,
    }

    verify_payload = {
        "tenant_id": tenant_id,
        "api_id": api_id,
        "client_id": client_id,
        "method": method.upper(),
        "path": path,
        "body_hash": body_hash,
        "time_bucket": time_bucket,
        "nonce": nonce,
        "signature": signature_b64url,
    }

    return {
        "headers": headers,
        "verify_payload": verify_payload,
        "transcript": transcript,
        "digest_b64url": _b64url_encode(digest),
    }


class TrustplaneClient:
    def __init__(self, tenant_id: str, api_id: str, client_id: str, bucket_seconds: int = 20):
        self.tenant_id = tenant_id
        self.api_id = api_id
        self.client_id = client_id
        self.bucket_seconds = bucket_seconds

    def sign(self, method: str, path: str, body: str, private_key_b64url: str) -> Dict[str, Any]:
        return sign(
            tenant_id=self.tenant_id,
            api_id=self.api_id,
            client_id=self.client_id,
            private_key_b64url=private_key_b64url,
            method=method,
            path=path,
            body=body,
            bucket_seconds=self.bucket_seconds,
        )

    def blindfold_verify(self, auth_base_url: str, method: str, path: str, body: str, private_key_b64url: str) -> Dict[str, Any]:
        return blindfold_verify(
            auth_base_url=auth_base_url,
            tenant_id=self.tenant_id,
            api_id=self.api_id,
            client_id=self.client_id,
            private_key_b64url=private_key_b64url,
            method=method,
            path=path,
            body=body,
            bucket_seconds=self.bucket_seconds,
        )


def from_file(path: str) -> TrustplaneClient:
    with open(path, "r", encoding="utf-8") as f:
        cfg = json.load(f)
    return TrustplaneClient(
        tenant_id=cfg.get("tenant_id"),
        api_id=cfg.get("api_id"),
        client_id=cfg.get("client_id"),
        bucket_seconds=cfg.get("bucket_seconds", 20),
    )


def _http_json_post(url: str, payload: Dict[str, Any]) -> Dict[str, Any]:
    data = json.dumps(payload).encode("utf-8")
    req = urlrequest.Request(
        url,
        data=data,
        headers={"content-type": "application/json"},
        method="POST",
    )
    try:
        with urlrequest.urlopen(req) as resp:
            body = resp.read().decode("utf-8")
            return {"status": resp.status, "ok": True, "data": json.loads(body)}
    except Exception as exc:
        if hasattr(exc, "read"):
            raw = exc.read().decode("utf-8")
            try:
                parsed = json.loads(raw)
            except Exception:
                parsed = {"raw": raw}
            status = getattr(exc, "code", 0)
            return {"status": status, "ok": False, "data": parsed}
        raise


def blindfold_verify(
    auth_base_url: str,
    tenant_id: str,
    api_id: str,
    client_id: str,
    private_key_b64url: str,
    method: str = "GET",
    path: str = "/",
    body: str = "",
    bucket_seconds: int = 20,
) -> Dict[str, Any]:
    signed = sign(
        tenant_id=tenant_id,
        api_id=api_id,
        client_id=client_id,
        private_key_b64url=private_key_b64url,
        method=method,
        path=path,
        body=body,
        bucket_seconds=bucket_seconds,
    )
    base = auth_base_url.rstrip("/")
    vp = signed["verify_payload"]

    start = _http_json_post(
        f"{base}/auth/blindfold/start",
        {
            "tenant_id": vp["tenant_id"],
            "api_id": vp["api_id"],
            "client_id": vp["client_id"],
            "method": vp["method"],
            "path": vp["path"],
            "body_hash": vp["body_hash"],
            "time_bucket": vp["time_bucket"],
            "nonce": vp["nonce"],
        },
    )
    if not start["ok"]:
        return {"step": "start", **start}

    eval_res = _http_json_post(
        f"{base}/oprf/full-evaluate",
        {"input_b64url": start["data"].get("input_b64url", "")},
    )
    if not eval_res["ok"]:
        return {"step": "evaluate", **eval_res}

    finalize = _http_json_post(
        f"{base}/auth/blindfold/finalize",
        {
            "session_id": start["data"].get("session_id", ""),
            "output_b64url": eval_res["data"].get("output_b64url", ""),
        },
    )
    if not finalize["ok"]:
        return {"step": "finalize", **finalize}

    verify_payload = dict(vp)
    verify_payload["proof_type"] = "blindfold"
    verify_payload["proof_payload"] = (
        finalize.get("data", {}).get("verify_payload", {}).get("proof_payload", "")
    )
    verify = _http_json_post(f"{base}/auth/verify", verify_payload)
    return {
        "step": "verify",
        **verify,
        "verify_payload": verify_payload,
        "digest_b64url": signed["digest_b64url"],
    }
