"""Crypto-shred erasure for the A8 encrypted-external slot (T5).

A personal-data body lives OFF-envelope as randomized authenticated ciphertext (AES-256-GCM under an ephemeral
`k_record` from the key hierarchy). Only a commitment over the CIPHERTEXT enters the VEB hash — so the VEB's
`content_hash` and the commitment are independent of the plaintext. Destroying the subject key (crypto-shred)
makes the `k_record` un-derivable, so the payload becomes **cryptographically inaccessible**, WHILE the
commitment and every verification record over it still hold (the ciphertext and its hash are unchanged).

This is the erasure PRIMITIVE, not the disclosure channel: it does not, by itself, constitute GDPR Art. 17
erasure or set a retention policy; source-data deletion remains the controller's responsibility.
"""

from __future__ import annotations

import os
from dataclasses import dataclass, field
from typing import Dict, Optional

from cryptography.hazmat.primitives.ciphers.aead import AESGCM

from cl.canonical.hash_profile import DEFAULT_PROFILE, HashProfile
from cl.model.veb import EncryptedExternalBody
from cl.privacy.key_hierarchy import ErasedError, KeyHierarchy

DEFAULT_ENCRYPTION_PROFILE_REF = "enc:aes-256-gcm:hkdf-subject-record:v1"
_NONCE_LEN = 12
ERASED_STATUS = "ERASED"
_ERASED_MEANING = (
    "The subject's key was destroyed (crypto-shred), so the record's openings are no longer derivable; the "
    "cryptographic commitment in the published proof remains, and deletion of the source data is the "
    "controller's responsibility."
)


@dataclass
class SealedStore:
    """Off-ledger store of randomized authenticated ciphertext, keyed by record_uid (Zone-A equivalent)."""

    _sealed: Dict[str, bytes] = field(default_factory=dict)

    def put(self, record_uid: str, sealed: bytes) -> None:
        self._sealed[record_uid] = sealed

    def get(self, record_uid: str) -> Optional[bytes]:
        return self._sealed.get(record_uid)


def _commitment(sealed: bytes, profile: HashProfile) -> str:
    return profile.digest(sealed).hex()


def seal_personal_body(
    hierarchy: KeyHierarchy,
    store: SealedStore,
    subject_uid: str,
    record_uid: str,
    plaintext: bytes,
    *,
    body_schema_ref: str,
    encryption_profile_ref: str = DEFAULT_ENCRYPTION_PROFILE_REF,
    profile: HashProfile = DEFAULT_PROFILE,
) -> EncryptedExternalBody:
    """Encrypt a personal payload under an ephemeral `k_record`, store the ciphertext off-ledger, and return
    the A8 `EncryptedExternalBody` whose `payload_commitment` is the hash of the (randomized) ciphertext."""
    k_record = hierarchy.open_record(subject_uid, record_uid)  # ephemeral; raises ErasedError if shredded
    nonce = os.urandom(_NONCE_LEN)
    ciphertext = AESGCM(k_record).encrypt(nonce, plaintext, record_uid.encode("utf-8"))
    sealed = nonce + ciphertext
    store.put(record_uid, sealed)
    return EncryptedExternalBody(
        body_schema_ref=body_schema_ref,
        payload_commitment=_commitment(sealed, profile),
        encryption_profile_ref=encryption_profile_ref,
    )


def open_personal_body(
    hierarchy: KeyHierarchy, store: SealedStore, subject_uid: str, record_uid: str
) -> bytes:
    """Recover the plaintext. Raises `ErasedError` after the subject is crypto-shredded — there is no other
    path to the opening."""
    sealed = store.get(record_uid)
    if sealed is None:
        raise KeyError(f"no sealed ciphertext for record {record_uid!r}")
    k_record = hierarchy.open_record(subject_uid, record_uid)  # raises ErasedError if shredded
    nonce, ciphertext = sealed[:_NONCE_LEN], sealed[_NONCE_LEN:]
    return AESGCM(k_record).decrypt(nonce, ciphertext, record_uid.encode("utf-8"))


def commitment_holds(
    body: EncryptedExternalBody, store: SealedStore, record_uid: str, *, profile: HashProfile = DEFAULT_PROFILE
) -> bool:
    """Re-derive the commitment over the still-present ciphertext and check it equals the body's commitment.
    This stays True AFTER erasure — the verification record survives the crypto-shred."""
    sealed = store.get(record_uid)
    return sealed is not None and _commitment(sealed, profile) == body.payload_commitment


def erased_response(*, lifecycle_ref: str, subject_uid: str, record_uid: str) -> dict:
    """The honest ERASED response: explicit, not an error/empty — the commitment remains, source deletion is
    the controller's, and a plain-language `meaning` line says what erasure here is."""
    return {
        "status": ERASED_STATUS,
        "lifecycle_ref": lifecycle_ref,
        "subject_uid": subject_uid,
        "record_uid": record_uid,
        "cryptographic_commitment_remains": True,
        "source_deletion_responsibility": "CONTROLLER",
        "meaning": _ERASED_MEANING,
    }
