"""G1 — k-of-n multi-human co-sign of governance decisions (GOVERNANCE_ROOT_EVOLUTION v0.1, faza G1).

The trust root's first step out of single-operator anchoring: a governance decision is admitted only
when at least `k` of the `n` REGISTERED signers have signed its canonical decision content
(`decision_content_of_body` — signers attest the DECISION, transport never enters the signing input).
Signer-agnostic by design: keys live in the registry, so the owner decision "who signs" is key
registration, not a code change. Fail-closed: shortfall, unknown signer, duplicate signer, or a bad
signature never count toward `k`. Verified co-signatures are persisted as artifact proofs (never in
the content hash — a signature over content cannot live inside it).
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Mapping, Sequence

from cl.governance.receipt import decision_content_of_body
from cl.identity.signatures import Ed25519Verifier

COSIGN_METHOD = "governance-cosign-ed25519"


class CoSignShortfall(ValueError):
    """Fewer than `k` distinct, registered, valid co-signatures — fail-closed (routes to REJECTED)."""


@dataclass(frozen=True)
class GovernanceSignerRegistry:
    """The `n` registered governance signers: kid → raw Ed25519 public key (hex, 32 bytes).
    Registration/rotation follows the key runbook (rotation = new kid + registry update, never a
    silent key swap under an existing kid)."""

    signers: Mapping[str, str] = field(default_factory=dict)

    def key_for(self, kid: str) -> str | None:
        return self.signers.get(kid)


@dataclass(frozen=True)
class CoSignPolicy:
    """k-of-n threshold over the registry. `k >= 1`; `k <= n` is validated at use time so a
    misconfigured policy fails loudly, not silently-satisfiable."""

    k: int
    registry: GovernanceSignerRegistry


@dataclass(frozen=True)
class CoSignature:
    signer_kid: str
    signature: bytes


def verify_cosignatures(
    receipt_body: dict,
    cosignatures: Sequence[CoSignature],
    policy: CoSignPolicy,
) -> tuple[CoSignature, ...]:
    """Verify co-signatures over the receipt's canonical decision content; return the DISTINCT valid
    ones (first occurrence per signer). Raises `CoSignShortfall` when fewer than `policy.k` distinct
    registered signers verified — the caller rejects fail-closed."""
    n = len(policy.registry.signers)
    if policy.k < 1 or policy.k > n:
        raise CoSignShortfall(f"misconfigured cosign policy: k={policy.k} with n={n} registered signers")

    signing_input = decision_content_of_body(receipt_body)
    verifier = Ed25519Verifier()
    verified: dict[str, CoSignature] = {}
    for cs in cosignatures:
        if cs.signer_kid in verified:
            continue  # a signer counts ONCE, no matter how many signatures they submit
        key_hex = policy.registry.key_for(cs.signer_kid)
        if key_hex is None:
            continue  # unregistered signer never counts
        if verifier.verify(signing_input, cs.signature, key_hex) is None:
            continue  # invalid signature never counts
        verified[cs.signer_kid] = cs
    if len(verified) < policy.k:
        raise CoSignShortfall(
            f"co-sign shortfall: {len(verified)} valid distinct signer(s), policy requires k={policy.k}"
        )
    return tuple(verified.values())


def cosign_proofs(verified: Sequence[CoSignature], registry: GovernanceSignerRegistry) -> tuple[dict, ...]:
    """Project verified co-signatures into artifact-proof entries (persisted in the proof set, so the
    k-of-n evidence is independently reproducible from the stored event — never hashed into content)."""
    return tuple(
        {
            "identity_method": COSIGN_METHOD,
            "key_ref": registry.key_for(cs.signer_kid),
            "signer_kid": cs.signer_kid,
            "signature": cs.signature.hex(),
        }
        for cs in verified
    )
