"""Pluggable signing backends — the key-custody seam.

The strategy review flagged the hard blocker: a plaintext PEM at ``~/.abom/`` is
rejected by a regulated buyer's security team on sight, and it makes the
neutrality claim false (whoever holds the key can forge tree heads). This module
puts every signature behind a ``Signer`` protocol so the *key never has to live
as a file we control*:

  * ``LocalSigner``  — dev / CI only. Wraps the existing ed25519 file key.
  * ``KMSSigner``    — production seam. ``sign()`` delegates to a cloud KMS / HSM
    that holds the private key and never exposes it. The ``_kms_sign`` hook is
    where an AWS KMS / GCP KMS / Vault Transit / PKCS#11 call goes; the rest
    (key id, public-key embedding, verification) is identical.

Verification is backend-independent: an ed25519 public key + signature is the
same wire format regardless of where the private key lived. That is what lets a
third party (auditor, regulator) verify a signed tree head without trusting us.
"""
from __future__ import annotations

import base64
from typing import Protocol, runtime_checkable

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
    Ed25519PublicKey,
)

from . import sign as _sign


@runtime_checkable
class Signer(Protocol):
    """A signing backend. The private key may live anywhere; only these escape."""

    def sign(self, message: bytes) -> bytes: ...
    def public_key_b64(self) -> str: ...
    def key_id(self) -> str: ...


def _pub_b64(pub: Ed25519PublicKey) -> str:
    raw = pub.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
    return base64.b64encode(raw).decode()


class LocalSigner:
    """ed25519 with the key held locally. Dev / CI only — NOT for production.

    Reuses ``sign.load_or_create_key`` so existing behaviour is unchanged, but
    callers now go through the ``Signer`` interface, so swapping in ``KMSSigner``
    requires no code change at the call site.
    """

    backend = "local"

    def __init__(self, key=None):
        self._key = key or _sign.load_or_create_key()
        self._pub = self._key.public_key()
        self._pub_b64 = _pub_b64(self._pub)

    def sign(self, message: bytes) -> bytes:
        return self._key.sign(message)

    def public_key_b64(self) -> str:
        return self._pub_b64

    def key_id(self) -> str:
        return _sign.key_id(self._pub_b64)


class KMSSigner:
    """Production seam: the private key lives in a KMS/HSM and never leaves it.

    Construct with the KMS key reference (ARN / resource name / PKCS#11 URI) and
    the corresponding ed25519 public key (KMS hands out the public half). The
    ``_kms_sign`` method is the single integration point for a real provider;
    until wired, it raises so we never silently fall back to a local key in
    production.
    """

    backend = "kms"

    def __init__(self, key_ref: str, public_key_b64: str, kms_sign=None):
        self.key_ref = key_ref
        self._pub_b64 = public_key_b64
        # Validate the public key is a real ed25519 key (fail fast on misconfig).
        Ed25519PublicKey.from_public_bytes(base64.b64decode(public_key_b64))
        self._kms_sign = kms_sign  # callable(key_ref, message) -> signature bytes

    def _do_kms_sign(self, message: bytes) -> bytes:
        if self._kms_sign is None:
            raise NotImplementedError(
                "KMSSigner requires a kms_sign callable wired to AWS KMS / GCP KMS / "
                "Vault Transit / PKCS#11. Refusing to fall back to a local key."
            )
        return self._kms_sign(self.key_ref, message)

    def sign(self, message: bytes) -> bytes:
        return self._do_kms_sign(message)

    def public_key_b64(self) -> str:
        return self._pub_b64

    def key_id(self) -> str:
        return _sign.key_id(self._pub_b64)


def sign_payload(signer: Signer, payload: bytes) -> dict:
    """Produce a detached signature envelope over ``payload`` using any backend."""
    sig = signer.sign(payload)
    return {
        "alg": "ed25519",
        "key_id": signer.key_id(),
        "pub": signer.public_key_b64(),
        "value": base64.b64encode(sig).decode(),
        "backend": getattr(signer, "backend", "unknown"),
    }


def verify_payload(payload: bytes, envelope: dict, trusted_key_ids: set[str] | None = None) -> bool:
    """Verify a detached signature envelope (backend-independent)."""
    try:
        pub_b64 = envelope["pub"]
        if trusted_key_ids is not None and _sign.key_id(pub_b64) not in trusted_key_ids:
            return False
        pub = Ed25519PublicKey.from_public_bytes(base64.b64decode(pub_b64))
        pub.verify(base64.b64decode(envelope["value"]), payload)
        return True
    except Exception:
        return False
