"""
Cryptographic primitives for Verisoo.

Ed25519 for signatures (fast, small keys, deterministic).
SHA-256 for content hashing.
"""

from __future__ import annotations

import hashlib
import secrets
from datetime import datetime, timezone
from pathlib import Path

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

from .exceptions import IdentityError, SignatureError
from .models import Identity


def generate_keypair() -> tuple[Ed25519PrivateKey, Ed25519PublicKey]:
    """Generate a new Ed25519 keypair."""
    private_key = Ed25519PrivateKey.generate()
    public_key = private_key.public_key()
    return private_key, public_key


def create_identity(
    private_key: Ed25519PrivateKey, metadata: dict | None = None
) -> Identity:
    """
    Create an Identity from a private key.

    The fingerprint is SHA-256(public_key_bytes)[:16] hex-encoded (32 chars).
    """
    public_key = private_key.public_key()
    public_bytes = public_key.public_bytes(
        encoding=serialization.Encoding.Raw,
        format=serialization.PublicFormat.Raw,
    )
    fingerprint = hashlib.sha256(public_bytes).hexdigest()[:32]

    return Identity(
        public_key=public_bytes,
        fingerprint=fingerprint,
        created_at=datetime.now(timezone.utc),
        metadata=metadata or {},
    )


def sign(private_key: Ed25519PrivateKey, data: bytes) -> bytes:
    """Sign data with Ed25519."""
    return private_key.sign(data)


def verify(public_key: bytes | Ed25519PublicKey, signature: bytes, data: bytes) -> bool:
    """
    Verify an Ed25519 signature.

    Returns True if valid, raises SignatureError if invalid.
    """
    if isinstance(public_key, bytes):
        public_key = Ed25519PublicKey.from_public_bytes(public_key)

    try:
        public_key.verify(signature, data)
        return True
    except Exception as e:
        raise SignatureError(f"Signature verification failed: {e}")


def hash_bytes(data: bytes) -> str:
    """SHA-256 hash of bytes, hex-encoded."""
    return hashlib.sha256(data).hexdigest()


def fingerprint_from_pubkey(public_key: bytes) -> str:
    """Compute fingerprint from public key bytes."""
    return hashlib.sha256(public_key).hexdigest()[:32]


def save_private_key(
    private_key: Ed25519PrivateKey,
    path: Path,
    password: bytes | None = None,
) -> None:
    """
    Save private key to file.

    If password is provided, encrypts with scrypt + AES-256-GCM.
    """
    if password:
        encryption = serialization.BestAvailableEncryption(password)
    else:
        encryption = serialization.NoEncryption()

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

    path.write_bytes(pem)
    # Restrict permissions on Unix
    try:
        path.chmod(0o600)
    except OSError:
        pass


def load_private_key(path: Path, password: bytes | None = None) -> Ed25519PrivateKey:
    """Load private key from file."""
    pem = path.read_bytes()

    try:
        private_key = serialization.load_pem_private_key(pem, password=password)
        if not isinstance(private_key, Ed25519PrivateKey):
            raise IdentityError(f"Expected Ed25519 key, got {type(private_key)}")
        return private_key
    except Exception as e:
        raise IdentityError(f"Failed to load private key: {e}")


def save_public_key(public_key: Ed25519PublicKey | bytes, path: Path) -> None:
    """Save public key to file."""
    if isinstance(public_key, bytes):
        public_key = Ed25519PublicKey.from_public_bytes(public_key)

    pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo,
    )
    path.write_bytes(pem)


def load_public_key(path: Path) -> Ed25519PublicKey:
    """Load public key from file."""
    pem = path.read_bytes()

    try:
        public_key = serialization.load_pem_public_key(pem)
        if not isinstance(public_key, Ed25519PublicKey):
            raise IdentityError(f"Expected Ed25519 key, got {type(public_key)}")
        return public_key
    except Exception as e:
        raise IdentityError(f"Failed to load public key: {e}")


def load_public_key_from_bytes(public_key_bytes: bytes) -> Ed25519PublicKey:
    """Load public key from raw 32-byte Ed25519 public key."""
    try:
        return Ed25519PublicKey.from_public_bytes(public_key_bytes)
    except Exception as e:
        raise IdentityError(f"Failed to load public key from bytes: {e}")


def verify_signature(data: bytes, signature: bytes, public_key_bytes: bytes) -> bool:
    """
    Verify an Ed25519 signature.

    Args:
        data: The signed data
        signature: 64-byte Ed25519 signature
        public_key_bytes: 32-byte Ed25519 public key

    Returns:
        True if valid, False otherwise
    """
    try:
        public_key = Ed25519PublicKey.from_public_bytes(public_key_bytes)
        public_key.verify(signature, data)
        return True
    except Exception:
        return False
