"""
CertiSigma SDK — Client-side encryption (AES-256-GCM).

Zero-knowledge encryption: the server never sees plaintext extra_data.
Uses the `cryptography` library.

Usage:
    from certisigma.crypto import generate_key, encrypt_metadata, decrypt_metadata

    key = generate_key()
    encrypted = encrypt_metadata({"secret": "data"}, key)
    decrypted = decrypt_metadata(encrypted, key)
"""
from __future__ import annotations

import json
import os
from typing import Any

ENVELOPE_VERSION = 1
NONCE_LENGTH = 12


def generate_key() -> str:
    """Generate a random AES-256 key as 64 hex chars."""
    return os.urandom(32).hex()


def encrypt_metadata(data: dict[str, Any], hex_key: str) -> dict[str, Any]:
    """
    Encrypt metadata dict with AES-256-GCM.

    Returns envelope: {"ciphertext": base64, "nonce": hex, "v": 1}
    """
    from base64 import b64encode
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM

    key_bytes = bytes.fromhex(hex_key)
    if len(key_bytes) != 32:
        raise ValueError(f"Invalid key: expected 32 bytes, got {len(key_bytes)}")

    nonce = os.urandom(NONCE_LENGTH)
    plaintext = json.dumps(data, separators=(",", ":"), sort_keys=True).encode("utf-8")
    ciphertext = AESGCM(key_bytes).encrypt(nonce, plaintext, None)

    return {
        "ciphertext": b64encode(ciphertext).decode("ascii"),
        "nonce": nonce.hex(),
        "v": ENVELOPE_VERSION,
    }


def decrypt_metadata(envelope: dict[str, Any], hex_key: str) -> dict[str, Any]:
    """
    Decrypt an encrypted envelope back to a metadata dict.

    Args:
        envelope: {"ciphertext": base64, "nonce": hex, "v": 1}
        hex_key: 64 hex char AES-256 key
    """
    from base64 import b64decode
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM

    if not envelope or "ciphertext" not in envelope or "nonce" not in envelope:
        raise ValueError("Invalid envelope: missing ciphertext or nonce")

    key_bytes = bytes.fromhex(hex_key)
    nonce = bytes.fromhex(envelope["nonce"])
    ciphertext = b64decode(envelope["ciphertext"])

    plaintext = AESGCM(key_bytes).decrypt(nonce, ciphertext, None)
    return json.loads(plaintext.decode("utf-8"))


def is_encrypted(data: Any) -> bool:
    """Check if data looks like a client-encrypted envelope."""
    return (
        isinstance(data, dict)
        and isinstance(data.get("ciphertext"), str)
        and isinstance(data.get("nonce"), str)
        and data.get("v") == ENVELOPE_VERSION
    )


def rotate_key(
    envelope: dict[str, Any], old_hex_key: str, new_hex_key: str
) -> dict[str, Any]:
    """Decrypt with old key and re-encrypt with new key."""
    plaintext = decrypt_metadata(envelope, old_hex_key)
    return encrypt_metadata(plaintext, new_hex_key)
