"""Digital signature utilities using Ed25519 algorithm.

This module provides functions for generating Ed25519 key pairs and
performing digital signature operations (signing and verification) using
the cryptography library.
"""

import json
from pathlib import Path
from typing import Any

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


def generate_key_pair() -> tuple[Ed25519PrivateKey, Ed25519PublicKey]:
    """Generate an Ed25519 key pair.

    Returns
    -------
    tuple[Ed25519PrivateKey, Ed25519PublicKey]
        A tuple containing the private key and public key

    Examples
    --------
    >>> private_key, public_key = generate_key_pair()
    >>> isinstance(private_key, Ed25519PrivateKey)
    True
    >>> isinstance(public_key, Ed25519PublicKey)
    True
    """
    private_key = Ed25519PrivateKey.generate()
    public_key = private_key.public_key()
    return private_key, public_key


def serialize_private_key(
    private_key: Ed25519PrivateKey,
    password: bytes | None = None,
) -> bytes:
    """Serialize private key to PEM format.

    Parameters
    ----------
    private_key : Ed25519PrivateKey
        The private key to serialize
    password : bytes | None, optional
        Password for encrypting the private key (default: None, no encryption)

    Returns
    -------
    bytes
        PEM-encoded private key bytes

    Examples
    --------
    >>> private_key, _ = generate_key_pair()
    >>> pem = serialize_private_key(private_key)
    >>> pem.startswith(b'-----BEGIN PRIVATE KEY-----')
    True

    >>> # With password protection
    >>> pem_encrypted = serialize_private_key(private_key, password=b'secret')
    >>> pem_encrypted.startswith(b'-----BEGIN ENCRYPTED PRIVATE KEY-----')
    True
    """
    if password is not None:
        encryption_algorithm = serialization.BestAvailableEncryption(password)
    else:
        encryption_algorithm = serialization.NoEncryption()

    return private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=encryption_algorithm,
    )


def serialize_public_key(public_key: Ed25519PublicKey) -> bytes:
    """Serialize public key to PEM format.

    Parameters
    ----------
    public_key : Ed25519PublicKey
        The public key to serialize

    Returns
    -------
    bytes
        PEM-encoded public key bytes

    Examples
    --------
    >>> _, public_key = generate_key_pair()
    >>> pem = serialize_public_key(public_key)
    >>> pem.startswith(b'-----BEGIN PUBLIC KEY-----')
    True
    """
    return public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo,
    )


def deserialize_private_key(
    pem_data: bytes,
    password: bytes | None = None,
) -> Ed25519PrivateKey:
    """Deserialize private key from PEM format.

    Parameters
    ----------
    pem_data : bytes
        PEM-encoded private key bytes
    password : bytes | None, optional
        Password for decrypting the private key (default: None)

    Returns
    -------
    Ed25519PrivateKey
        The deserialized private key

    Raises
    ------
    ValueError
        If the PEM data is invalid or password is incorrect

    Examples
    --------
    >>> private_key, _ = generate_key_pair()
    >>> pem = serialize_private_key(private_key)
    >>> loaded_key = deserialize_private_key(pem)
    >>> isinstance(loaded_key, Ed25519PrivateKey)
    True
    """
    return serialization.load_pem_private_key(
        pem_data,
        password=password,
    )  # type: ignore


def deserialize_public_key(pem_data: bytes) -> Ed25519PublicKey:
    """Deserialize public key from PEM format.

    Parameters
    ----------
    pem_data : bytes
        PEM-encoded public key bytes

    Returns
    -------
    Ed25519PublicKey
        The deserialized public key

    Raises
    ------
    ValueError
        If the PEM data is invalid

    Examples
    --------
    >>> _, public_key = generate_key_pair()
    >>> pem = serialize_public_key(public_key)
    >>> loaded_key = deserialize_public_key(pem)
    >>> isinstance(loaded_key, Ed25519PublicKey)
    True
    """
    return serialization.load_pem_public_key(pem_data)  # type: ignore


def sign_message(private_key: Ed25519PrivateKey, message: bytes) -> bytes:
    """Sign a message using Ed25519 private key.

    Parameters
    ----------
    private_key : Ed25519PrivateKey
        The private key to use for signing
    message : bytes
        The message to sign

    Returns
    -------
    bytes
        The signature bytes

    Examples
    --------
    >>> private_key, public_key = generate_key_pair()
    >>> message = b"Hello, World!"
    >>> signature = sign_message(private_key, message)
    >>> len(signature)
    64
    """
    return private_key.sign(message)


def verify_signature(
    public_key: Ed25519PublicKey,
    signature: bytes,
    message: bytes,
) -> bool:
    """Verify a message signature using Ed25519 public key.

    Parameters
    ----------
    public_key : Ed25519PublicKey
        The public key to use for verification
    signature : bytes
        The signature to verify
    message : bytes
        The original message

    Returns
    -------
    bool
        True if signature is valid, False otherwise

    Examples
    --------
    >>> private_key, public_key = generate_key_pair()
    >>> message = b"Hello, World!"
    >>> signature = sign_message(private_key, message)
    >>> verify_signature(public_key, signature, message)
    True
    >>> verify_signature(public_key, signature, b"Wrong message")
    False
    """
    try:
        public_key.verify(signature, message)
        return True
    except Exception:
        return False


def sign_json(private_key: Ed25519PrivateKey, data: dict[str, Any]) -> bytes:
    """Sign a JSON-serializable dictionary using Ed25519 private key.

    The dictionary is serialized to JSON with sorted keys for deterministic
    serialization, then encoded to UTF-8 bytes before signing.

    Parameters
    ----------
    private_key : Ed25519PrivateKey
        The private key to use for signing
    data : dict[str, Any]
        The dictionary to sign (must be JSON-serializable)

    Returns
    -------
    bytes
        The signature bytes

    Examples
    --------
    >>> private_key, public_key = generate_key_pair()
    >>> data = {"user": "alice", "action": "login", "timestamp": 1234567890}
    >>> signature = sign_json(private_key, data)
    >>> len(signature)
    64
    """
    message = json.dumps(data, sort_keys=True, ensure_ascii=False).encode("utf-8")
    return sign_message(private_key, message)


def verify_json_signature(
    public_key: Ed25519PublicKey,
    signature: bytes,
    data: dict[str, Any],
) -> bool:
    """Verify a JSON dictionary signature using Ed25519 public key.

    The dictionary is serialized to JSON with sorted keys for deterministic
    serialization, then encoded to UTF-8 bytes before verification.

    Parameters
    ----------
    public_key : Ed25519PublicKey
        The public key to use for verification
    signature : bytes
        The signature to verify
    data : dict[str, Any]
        The original dictionary (must be JSON-serializable)

    Returns
    -------
    bool
        True if signature is valid, False otherwise

    Examples
    --------
    >>> private_key, public_key = generate_key_pair()
    >>> data = {"user": "alice", "action": "login"}
    >>> signature = sign_json(private_key, data)
    >>> verify_json_signature(public_key, signature, data)
    True
    >>> verify_json_signature(public_key, signature, {"user": "bob", "action": "login"})
    False
    """
    message = json.dumps(data, sort_keys=True, ensure_ascii=False).encode("utf-8")
    return verify_signature(public_key, signature, message)


def sign_message_with_pem(
    private_pem: bytes,
    message: bytes,
    password: bytes | None = None,
) -> bytes:
    """Sign a message using PEM-encoded private key.

    This is a convenience function that deserializes the PEM key and signs
    the message in one step.

    Parameters
    ----------
    private_pem : bytes
        PEM-encoded private key bytes
    message : bytes
        The message to sign
    password : bytes | None, optional
        Password for decrypting the private key (default: None)

    Returns
    -------
    bytes
        The signature bytes

    Examples
    --------
    >>> private_key, _ = generate_key_pair()
    >>> private_pem = serialize_private_key(private_key)
    >>> message = b"Hello, World!"
    >>> signature = sign_message_with_pem(private_pem, message)
    >>> len(signature)
    64
    """
    private_key = deserialize_private_key(private_pem, password=password)
    return sign_message(private_key, message)


def verify_signature_with_pem(
    public_pem: bytes,
    signature: bytes,
    message: bytes,
) -> bool:
    """Verify a message signature using PEM-encoded public key.

    This is a convenience function that deserializes the PEM key and verifies
    the signature in one step.

    Parameters
    ----------
    public_pem : bytes
        PEM-encoded public key bytes
    signature : bytes
        The signature to verify
    message : bytes
        The original message

    Returns
    -------
    bool
        True if signature is valid, False otherwise

    Examples
    --------
    >>> private_key, public_key = generate_key_pair()
    >>> public_pem = serialize_public_key(public_key)
    >>> message = b"Hello, World!"
    >>> signature = sign_message(private_key, message)
    >>> verify_signature_with_pem(public_pem, signature, message)
    True
    """
    public_key = deserialize_public_key(public_pem)
    return verify_signature(public_key, signature, message)


def sign_json_with_pem(
    private_pem: bytes,
    data: dict[str, Any],
    password: bytes | None = None,
) -> bytes:
    """Sign a JSON dictionary using PEM-encoded private key.

    This is a convenience function that deserializes the PEM key and signs
    the JSON data in one step.

    Parameters
    ----------
    private_pem : bytes
        PEM-encoded private key bytes
    data : dict[str, Any]
        The dictionary to sign (must be JSON-serializable)
    password : bytes | None, optional
        Password for decrypting the private key (default: None)

    Returns
    -------
    bytes
        The signature bytes

    Examples
    --------
    >>> private_key, _ = generate_key_pair()
    >>> private_pem = serialize_private_key(private_key, password=b"secret")
    >>> data = {"user": "alice", "action": "login"}
    >>> signature = sign_json_with_pem(private_pem, data, password=b"secret")
    >>> len(signature)
    64
    """
    private_key = deserialize_private_key(private_pem, password=password)
    return sign_json(private_key, data)


def verify_json_signature_with_pem(
    public_pem: bytes,
    signature: bytes,
    data: dict[str, Any],
) -> bool:
    """Verify a JSON dictionary signature using PEM-encoded public key.

    This is a convenience function that deserializes the PEM key and verifies
    the JSON signature in one step.

    Parameters
    ----------
    public_pem : bytes
        PEM-encoded public key bytes
    signature : bytes
        The signature to verify
    data : dict[str, Any]
        The original dictionary (must be JSON-serializable)

    Returns
    -------
    bool
        True if signature is valid, False otherwise

    Examples
    --------
    >>> private_key, public_key = generate_key_pair()
    >>> public_pem = serialize_public_key(public_key)
    >>> data = {"user": "alice", "action": "login"}
    >>> signature = sign_json(private_key, data)
    >>> verify_json_signature_with_pem(public_pem, signature, data)
    True
    """
    public_key = deserialize_public_key(public_pem)
    return verify_json_signature(public_key, signature, data)


def sign_message_with_file(
    private_key_path: str | Path,
    message: bytes,
    password: bytes | None = None,
) -> bytes:
    """Sign a message using private key from file.

    This is a convenience function that reads the PEM key from file and signs
    the message in one step.

    Parameters
    ----------
    private_key_path : str | Path
        Path to PEM-encoded private key file
    message : bytes
        The message to sign
    password : bytes | None, optional
        Password for decrypting the private key (default: None)

    Returns
    -------
    bytes
        The signature bytes

    Examples
    --------
    >>> from pathlib import Path
    >>> private_key, _ = generate_key_pair()
    >>> # Save to temporary file
    >>> path = Path("/tmp/test_key.pem")
    >>> path.write_bytes(serialize_private_key(private_key))
    >>> message = b"Hello, World!"
    >>> signature = sign_message_with_file(path, message)
    >>> len(signature)
    64
    """
    private_pem = Path(private_key_path).read_bytes()
    return sign_message_with_pem(private_pem, message, password=password)


def verify_signature_with_file(
    public_key_path: str | Path,
    signature: bytes,
    message: bytes,
) -> bool:
    """Verify a message signature using public key from file.

    This is a convenience function that reads the PEM key from file and verifies
    the signature in one step.

    Parameters
    ----------
    public_key_path : str | Path
        Path to PEM-encoded public key file
    signature : bytes
        The signature to verify
    message : bytes
        The original message

    Returns
    -------
    bool
        True if signature is valid, False otherwise

    Examples
    --------
    >>> from pathlib import Path
    >>> private_key, public_key = generate_key_pair()
    >>> # Save to temporary file
    >>> path = Path("/tmp/test_pub.pem")
    >>> path.write_bytes(serialize_public_key(public_key))
    >>> message = b"Hello, World!"
    >>> signature = sign_message(private_key, message)
    >>> verify_signature_with_file(path, signature, message)
    True
    """
    public_pem = Path(public_key_path).read_bytes()
    return verify_signature_with_pem(public_pem, signature, message)


def sign_json_with_file(
    private_key_path: str | Path,
    data: dict[str, Any],
    password: bytes | None = None,
) -> bytes:
    """Sign a JSON dictionary using private key from file.

    This is a convenience function that reads the PEM key from file and signs
    the JSON data in one step.

    Parameters
    ----------
    private_key_path : str | Path
        Path to PEM-encoded private key file
    data : dict[str, Any]
        The dictionary to sign (must be JSON-serializable)
    password : bytes | None, optional
        Password for decrypting the private key (default: None)

    Returns
    -------
    bytes
        The signature bytes

    Examples
    --------
    >>> from pathlib import Path
    >>> private_key, _ = generate_key_pair()
    >>> # Save to temporary file
    >>> path = Path("/tmp/test_key.pem")
    >>> path.write_bytes(serialize_private_key(private_key, password=b"secret"))
    >>> data = {"user": "alice", "action": "login"}
    >>> signature = sign_json_with_file(path, data, password=b"secret")
    >>> len(signature)
    64
    """
    private_pem = Path(private_key_path).read_bytes()
    return sign_json_with_pem(private_pem, data, password=password)


def verify_json_signature_with_file(
    public_key_path: str | Path,
    signature: bytes,
    data: dict[str, Any],
) -> bool:
    """Verify a JSON dictionary signature using public key from file.

    This is a convenience function that reads the PEM key from file and verifies
    the JSON signature in one step.

    Parameters
    ----------
    public_key_path : str | Path
        Path to PEM-encoded public key file
    signature : bytes
        The signature to verify
    data : dict[str, Any]
        The original dictionary (must be JSON-serializable)

    Returns
    -------
    bool
        True if signature is valid, False otherwise

    Examples
    --------
    >>> from pathlib import Path
    >>> private_key, public_key = generate_key_pair()
    >>> # Save to temporary file
    >>> path = Path("/tmp/test_pub.pem")
    >>> path.write_bytes(serialize_public_key(public_key))
    >>> data = {"user": "alice", "action": "login"}
    >>> signature = sign_json(private_key, data)
    >>> verify_json_signature_with_file(path, signature, data)
    True
    """
    public_pem = Path(public_key_path).read_bytes()
    return verify_json_signature_with_pem(public_pem, signature, data)


# Convenient aliases for common operations
sign = sign_json_with_file
verify = verify_json_signature_with_file
