"""Key material for the three PoC identities.

The PoC uses three ES256 (P-256) keys — the common algorithm for both
ACT and ECT per draft-nennemann-act-01 §5 and draft-nennemann-wimse-ect-01 §5.

Identities:
  user        — issues the ACT mandate (iss in Phase 1)
  agent       — subject of the mandate, signs Phase 2 record, signs ECT on
                every MCP tool call (sub in ACT, iss in ECT)
  mcp-server  — audience / verifier (aud in both ACT and ECT)

Keys are written to ``keys/`` as PEM files on first run; subsequent runs
load them. This mimics pre-shared-key deployment per ACT §5.2 Tier 1.
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ec import (
    EllipticCurvePrivateKey,
    EllipticCurvePublicKey,
)

from act.crypto import KeyRegistry, generate_p256_keypair


IDENTITIES = ("user", "agent", "mcp-server")


@dataclass
class Identity:
    name: str
    kid: str
    private_key: EllipticCurvePrivateKey
    public_key: EllipticCurvePublicKey


def _pem_paths(keys_dir: Path, name: str) -> tuple[Path, Path]:
    return keys_dir / f"{name}.priv.pem", keys_dir / f"{name}.pub.pem"


def _load_or_generate(keys_dir: Path, name: str) -> Identity:
    priv_path, pub_path = _pem_paths(keys_dir, name)
    if priv_path.exists() and pub_path.exists():
        priv_bytes = priv_path.read_bytes()
        priv = serialization.load_pem_private_key(priv_bytes, password=None)
        assert isinstance(priv, EllipticCurvePrivateKey), (
            f"{name}.priv.pem is not a P-256 private key"
        )
        pub = priv.public_key()
    else:
        priv, pub = generate_p256_keypair()
        keys_dir.mkdir(parents=True, exist_ok=True)
        priv_path.write_bytes(
            priv.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.PKCS8,
                encryption_algorithm=serialization.NoEncryption(),
            )
        )
        pub_path.write_bytes(
            pub.public_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PublicFormat.SubjectPublicKeyInfo,
            )
        )
    kid = f"kid:{name}:v1"
    return Identity(name=name, kid=kid, private_key=priv, public_key=pub)


def load_identities(keys_dir: str | Path = "keys") -> dict[str, Identity]:
    """Load all three PoC identities, generating key material if missing."""
    keys_dir = Path(keys_dir)
    return {name: _load_or_generate(keys_dir, name) for name in IDENTITIES}


def build_key_registry(identities: dict[str, Identity]) -> KeyRegistry:
    """Assemble an ACT KeyRegistry with every identity's public key."""
    reg = KeyRegistry()
    for ident in identities.values():
        reg.register(ident.kid, ident.public_key)
    return reg


def build_ect_key_resolver(identities: dict[str, Identity]):
    """Return an ECT KeyResolver callable that maps kid → public key."""
    kid_to_pub: dict[str, EllipticCurvePublicKey] = {
        ident.kid: ident.public_key for ident in identities.values()
    }

    def _resolve(kid: str) -> EllipticCurvePublicKey | None:
        return kid_to_pub.get(kid)

    return _resolve
