"""
Ed25519 signing for StemeDB assertions.

Mirrors the Go SDK pattern from sdk/go/steme/signer.go
"""

import os
import time
from dataclasses import dataclass

from nacl.signing import SigningKey, VerifyKey
from nacl.exceptions import BadSignatureError


@dataclass
class SignatureEntry:
    """Signature entry for a StemeDB assertion."""

    agent_id: str  # Hex-encoded 32-byte public key
    signature: str  # Hex-encoded 64-byte Ed25519 signature
    timestamp: int  # Unix epoch timestamp
    version: int  # Signature format version (1 = v1 legacy)


class Signer:
    """
    Ed25519 signer for StemeDB assertions.

    All assertions submitted to StemeDB must be signed by at least one agent.
    This class abstracts the cryptographic details and provides a simple API.
    """

    def __init__(self, signing_key: SigningKey):
        """
        Initialize with an Ed25519 signing key.

        Use class methods (generate, from_hex, from_env) to create instances.
        """
        self._signing_key = signing_key
        self._verify_key = signing_key.verify_key

    @classmethod
    def generate(cls) -> "Signer":
        """
        Generate a new random Ed25519 keypair.

        Useful for testing or creating new agent identities.
        The seed can be retrieved with seed() for persistence.
        """
        return cls(SigningKey.generate())

    @classmethod
    def from_hex(cls, hex_seed: str) -> "Signer":
        """
        Create a Signer from a hex-encoded 32-byte seed.

        The hex string should be exactly 64 characters (32 bytes).

        Raises:
            ValueError: If hex_seed is not valid hex or wrong length.
        """
        try:
            seed_bytes = bytes.fromhex(hex_seed)
        except ValueError as e:
            raise ValueError(f"Invalid hex string: {e}") from e

        if len(seed_bytes) != 32:
            raise ValueError(f"Seed must be 32 bytes, got {len(seed_bytes)}")

        return cls(SigningKey(seed_bytes))

    @classmethod
    def from_env(cls, env_var: str = "STEMEDB_AGENT_SEED") -> "Signer":
        """
        Create a Signer from an environment variable.

        The environment variable should contain a hex-encoded 32-byte seed.

        Raises:
            ValueError: If the variable is not set or contains invalid data.
        """
        hex_seed = os.getenv(env_var)
        if not hex_seed:
            raise ValueError(f"Environment variable {env_var} not set")

        return cls.from_hex(hex_seed)

    @property
    def public_key(self) -> str:
        """
        Return the hex-encoded Ed25519 public key (32 bytes).

        This is the agent_id used in StemeDB.
        """
        return self._verify_key.encode().hex()

    @property
    def seed(self) -> str:
        """
        Return the hex-encoded Ed25519 private key seed (32 bytes).

        Store this securely for key recovery.
        """
        # PyNaCl stores the seed in the first 32 bytes of the signing key
        return bytes(self._signing_key).hex()[:64]  # First 32 bytes as hex

    def sign(self, message: bytes) -> str:
        """
        Create an Ed25519 signature over the message bytes.

        Returns the hex-encoded 64-byte signature.
        """
        signed = self._signing_key.sign(message)
        # signed.signature is the 64-byte signature
        return signed.signature.hex()

    def create_signature(self, subject: str, predicate: str) -> SignatureEntry:
        """
        Create a SignatureEntry for an assertion (v1 legacy format).

        The message is the canonical serialization "{subject}:{predicate}".
        This only covers subject and predicate - other fields can be tampered.

        The timestamp is set to the current Unix epoch.
        """
        message = f"{subject}:{predicate}".encode("utf-8")
        timestamp = int(time.time())

        return SignatureEntry(
            agent_id=self.public_key,
            signature=self.sign(message),
            timestamp=timestamp,
            version=1,
        )


def verify(public_key_hex: str, signature_hex: str, message: bytes) -> bool:
    """
    Verify an Ed25519 signature.

    Useful for validating signatures received from other agents.

    Returns:
        True if signature is valid, False otherwise.

    Raises:
        ValueError: If the public key or signature hex is invalid.
    """
    try:
        public_key_bytes = bytes.fromhex(public_key_hex)
        signature_bytes = bytes.fromhex(signature_hex)
    except ValueError as e:
        raise ValueError(f"Invalid hex: {e}") from e

    if len(public_key_bytes) != 32:
        raise ValueError(f"Public key must be 32 bytes, got {len(public_key_bytes)}")

    if len(signature_bytes) != 64:
        raise ValueError(f"Signature must be 64 bytes, got {len(signature_bytes)}")

    verify_key = VerifyKey(public_key_bytes)
    try:
        verify_key.verify(message, signature_bytes)
        return True
    except BadSignatureError:
        return False
