"""
Cryptographic operations for step package certification.

Uses Ed25519 for digital signatures - a modern, secure, and fast algorithm
that provides strong security with compact keys and signatures.
"""

import base64
import hashlib
import json
from pathlib import Path
from typing import Tuple, Optional

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


def generate_keypair() -> Tuple[str, str]:
    """
    Generate a new Ed25519 keypair for certification.
    
    Returns:
        Tuple of (private_key_b64, public_key_b64) - base64 encoded keys
        
    The private key should be kept secure and used for signing certificates.
    The public key can be distributed and used for verification.
    """
    private_key = Ed25519PrivateKey.generate()
    public_key = private_key.public_key()
    
    # Serialize to raw bytes and encode as base64
    private_bytes = private_key.private_bytes(
        encoding=serialization.Encoding.Raw,
        format=serialization.PrivateFormat.Raw,
        encryption_algorithm=serialization.NoEncryption()
    )
    public_bytes = public_key.public_bytes(
        encoding=serialization.Encoding.Raw,
        format=serialization.PublicFormat.Raw
    )
    
    return (
        base64.b64encode(private_bytes).decode('ascii'),
        base64.b64encode(public_bytes).decode('ascii')
    )


def sign_data(data: bytes, private_key_b64: str) -> str:
    """
    Sign data with an Ed25519 private key.
    
    Args:
        data: The bytes to sign
        private_key_b64: Base64-encoded Ed25519 private key
        
    Returns:
        Base64-encoded signature
    """
    private_bytes = base64.b64decode(private_key_b64)
    private_key = Ed25519PrivateKey.from_private_bytes(private_bytes)
    signature = private_key.sign(data)
    return base64.b64encode(signature).decode('ascii')


def verify_signature(data: bytes, signature_b64: str, public_key_b64: str) -> bool:
    """
    Verify a signature against data using an Ed25519 public key.
    
    Args:
        data: The original bytes that were signed
        signature_b64: Base64-encoded signature to verify
        public_key_b64: Base64-encoded Ed25519 public key
        
    Returns:
        True if signature is valid, False otherwise
    """
    try:
        public_bytes = base64.b64decode(public_key_b64)
        public_key = Ed25519PublicKey.from_public_bytes(public_bytes)
        signature = base64.b64decode(signature_b64)
        public_key.verify(signature, data)
        return True
    except Exception:
        return False


def compute_distribution_hash(plugin_path: Path, exclude_files: Optional[set] = None) -> str:
    """
    Compute a deterministic hash of the step package distribution.
    
    This hash covers all source files (.py, .cpp, .hpp) in the distribution,
    excluding steps.jsonld to avoid chicken-and-egg problems
    (the certificate is stored in steps.jsonld).
    
    Args:
        plugin_path: Path to the step package root directory
        exclude_files: Additional filenames to exclude (default: {"steps.jsonld"})
        
    Returns:
        Hex-encoded SHA256 hash of the distribution
    """
    if exclude_files is None:
        exclude_files = {"steps.jsonld"}
    else:
        exclude_files = exclude_files | {"steps.jsonld"}
    
    hasher = hashlib.sha256()
    
    # Collect all relevant source files
    source_files = []
    for ext in ("*.py", "*.cpp", "*.hpp"):
        source_files.extend(plugin_path.rglob(ext))
    
    # Sort for deterministic ordering
    source_files = sorted(source_files)
    
    for file_path in source_files:
        if file_path.name in exclude_files:
            continue
        # Skip __pycache__ and build directories
        if "__pycache__" in file_path.parts or "build" in file_path.parts:
            continue
        
        # Include relative path in hash for structure integrity
        rel_path = file_path.relative_to(plugin_path)
        hasher.update(str(rel_path).replace("\\", "/").encode('utf-8'))
        hasher.update(b'\x00')  # Separator
        
        # Include file contents
        hasher.update(file_path.read_bytes())
        hasher.update(b'\x00')  # Separator
    
    return hasher.hexdigest()


def create_signable_json(cert_data: dict) -> bytes:
    """
    Create a deterministic JSON representation for signing.
    
    This ensures the same certificate always produces the same bytes,
    regardless of dict ordering or whitespace.
    
    Args:
        cert_data: Certificate data dictionary (without signature field)
        
    Returns:
        UTF-8 encoded JSON bytes suitable for signing
    """
    # Remove signature if present (we sign everything else)
    data_to_sign = {k: v for k, v in cert_data.items() if k != "signature"}
    return json.dumps(data_to_sign, sort_keys=True, separators=(',', ':')).encode('utf-8')
