"""Ed25519 cryptographic identity for P2P agents."""

from __future__ import annotations

import hashlib
from pathlib import Path

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

_B58 = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"


def _b58encode(data: bytes) -> str:
    n = int.from_bytes(data, "big")
    result = bytearray()
    while n > 0:
        n, r = divmod(n, 58)
        result.append(_B58[r])
    for b in data:
        if b == 0:
            result.append(_B58[0])
        else:
            break
    return bytes(reversed(result)).decode("ascii")


def _b58decode(s: str) -> bytes:
    n = 0
    for c in s:
        n = n * 58 + _B58.index(c.encode("ascii"))
    leading = sum(1 for c in s if c == "1")
    result = n.to_bytes((n.bit_length() + 7) // 8, "big") if n else b""
    return b"\x00" * leading + result


def _derive_peer_id_from_raw(raw: bytes) -> str:
    """Derive PeerID from raw 32-byte Ed25519 public key bytes."""
    return f"p2p:{_b58encode(hashlib.sha256(raw).digest()[:20])}"


def _derive_peer_id(pub: Ed25519PublicKey) -> str:
    raw = pub.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
    return _derive_peer_id_from_raw(raw)


class PeerIdentity:
    """An agent's cryptographic identity backed by an Ed25519 keypair.

    The PeerID is derived deterministically from the public key:
    ``p2p:<base58(sha256(pubkey)[:20])>``
    """

    def __init__(self, private_key: Ed25519PrivateKey) -> None:
        self._priv = private_key
        self._pub = private_key.public_key()
        self._peer_id = _derive_peer_id(self._pub)

    # ── Construction ────────────────────────────────────────────────

    @classmethod
    def generate(cls) -> PeerIdentity:
        return cls(Ed25519PrivateKey.generate())

    @classmethod
    def load(cls, directory: Path, passphrase: str = "") -> PeerIdentity:
        priv_bytes = (directory / "private.pem").read_bytes()
        pwd = passphrase.encode() if passphrase else None
        key = serialization.load_pem_private_key(priv_bytes, password=pwd)
        if not isinstance(key, Ed25519PrivateKey):
            raise TypeError(f"Expected Ed25519, got {type(key).__name__}")
        return cls(key)

    def save(self, directory: Path, passphrase: str = "") -> None:
        directory.mkdir(parents=True, exist_ok=True)
        enc: serialization.KeySerializationEncryption = (
            serialization.BestAvailableEncryption(passphrase.encode())
            if passphrase else serialization.NoEncryption()
        )
        (directory / "private.pem").write_bytes(
            self._priv.private_bytes(
                serialization.Encoding.PEM,
                serialization.PrivateFormat.PKCS8, enc,
            )
        )
        (directory / "public.pem").write_bytes(
            self._pub.public_bytes(
                serialization.Encoding.PEM,
                serialization.PublicFormat.SubjectPublicKeyInfo,
            )
        )

    # ── Properties ──────────────────────────────────────────────────

    @property
    def private_key(self) -> Ed25519PrivateKey:
        return self._priv

    @property
    def public_key(self) -> Ed25519PublicKey:
        return self._pub

    @property
    def peer_id(self) -> str:
        return self._peer_id

    def peer_id_short(self) -> str:
        return self._peer_id[:12]  # "p2p:" + 8 chars

    def public_key_pem(self) -> str:
        return self._pub.public_bytes(
            serialization.Encoding.PEM,
            serialization.PublicFormat.SubjectPublicKeyInfo,
        ).decode("ascii")

    def public_key_raw(self) -> bytes:
        return self._pub.public_bytes(
            serialization.Encoding.Raw, serialization.PublicFormat.Raw,
        )

    # ── Crypto ──────────────────────────────────────────────────────

    def sign(self, data: bytes) -> bytes:
        return self._priv.sign(data)

    @staticmethod
    def verify(data: bytes, signature: bytes, pub: Ed25519PublicKey) -> bool:
        try:
            pub.verify(signature, data)
            return True
        except Exception:
            return False

    @staticmethod
    def load_public_key_pem(pem: str) -> Ed25519PublicKey:
        key = serialization.load_pem_public_key(pem.encode("ascii"))
        if not isinstance(key, Ed25519PublicKey):
            raise TypeError(f"Expected Ed25519, got {type(key).__name__}")
        return key

    def __repr__(self) -> str:
        return f"PeerIdentity({self._peer_id})"
