"""VebContentV1 — the universal canonical envelope content (Appendix A.1 / A.6 / A.8).

`VEB ⊃ PAC-RO`. This dataclass is EXACTLY the content that enters `content_hash`: business + evidentiary
facts, identity, time, evidence/policy/relations, and the `hash_profile_id` (A1, inside the hashed content).
Infrastructure / routing / transport fields are NOT modeled here — they live outside the hash by construction
(canonical-hash purity, Appendix A.4). The body is ONE `body_binding` tagged union (A6); a body carrying
personal data MUST be `ENCRYPTED_EXTERNAL_BODY` (A8).
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Optional, Union

# Domain tags (Appendix A.2) — bound into each hash so digests can't be confused across object kinds.
DOMAIN_VEB_CONTENT = "COTRUGLI:VEB:CONTENT:V1"
DOMAIN_VEB_ARTIFACT = "COTRUGLI:VEB:ARTIFACT:V1"
DOMAIN_VEB_PROOFSET = "COTRUGLI:VEB:PROOFSET:V1"

INLINE = "INLINE_CANONICAL_BODY"
ENCRYPTED_EXTERNAL = "ENCRYPTED_EXTERNAL_BODY"


class BodyBindingError(ValueError):
    """An A6/A8 body-binding violation — fail-closed."""


@dataclass(frozen=True)
class InlineCanonicalBody:
    """A6: an inline, canonically-hashed body. A8: permitted ONLY for content the caller can prove is
    NON-personal — `non_personal` must be explicitly asserted True, else construction is rejected."""

    body_schema_ref: str
    body: dict
    non_personal: bool = False

    def canonical(self) -> dict:
        if not self.non_personal:
            raise BodyBindingError(
                "INLINE_CANONICAL_BODY requires non_personal=True (A8: personal data MUST be "
                "ENCRYPTED_EXTERNAL_BODY)"
            )
        return {"kind": INLINE, "body_schema_ref": self.body_schema_ref, "body": self.body}


@dataclass(frozen=True)
class EncryptedExternalBody:
    """A4/A8: the body lives off-envelope as randomized authenticated ciphertext; only a commitment over the
    CIPHERTEXT enters the hash (never a hash of low-entropy plaintext), so crypto-shred (destroying the
    per-subject KEK) makes the personal data unrecoverable and the commitment non-brute-forceable."""

    body_schema_ref: str
    payload_commitment: str  # hex of H(randomized authenticated ciphertext)
    encryption_profile_ref: str

    def canonical(self) -> dict:
        return {
            "kind": ENCRYPTED_EXTERNAL,
            "body_schema_ref": self.body_schema_ref,
            "payload_commitment": self.payload_commitment,
            "encryption_profile_ref": self.encryption_profile_ref,
        }


BodyBinding = Union[InlineCanonicalBody, EncryptedExternalBody]


# INGRESS_ASSURANCE_V1 — the door-provenance registry (spec: neoos-stack/proposals/
# INGRESS_ASSURANCE_LABEL_v0_1.md). Assigned ONLY by the ingest door, never by the sender; a value
# outside the registry REJECTS fail-closed. Additions require the same gate as an ADR.
SEAM_GATED = "seam_gated"
PIPELINE_TRUSTED = "pipeline_trusted"
INGRESS_ASSURANCE_V1 = (SEAM_GATED, PIPELINE_TRUSTED)


@dataclass(frozen=True)
class VebContentV1:
    schema_id: str
    schema_version: int
    hash_profile_id: bytes
    event_type: str
    actor_ref: str
    principal_ref: str
    hai5_level: int
    semantic_time: str
    body_binding: BodyBinding
    evidence_refs: tuple[str, ...] = ()
    relations: tuple[dict, ...] = ()
    credential_ref: Optional[str] = None
    policy_snapshot_ref: Optional[str] = None
    # Door provenance (INGRESS_ASSURANCE_V1); None = unlabeled (pre-V1 history) — never inferred.
    ingress_assurance: Optional[str] = None

    def to_canonical_content(self) -> dict:
        """The canonical content dict that enters `content_hash` — `hash_profile_id` inside (A1), optional
        fields OMITTED when absent (stable canonical form), body via the tagged union (A6). NO infra fields."""
        content: dict[str, Any] = {
            "schema_id": self.schema_id,
            "schema_version": self.schema_version,
            "hash_profile_id": self.hash_profile_id.hex(),  # A1: the id is part of the hashed content
            "event_type": self.event_type,
            "actor_ref": self.actor_ref,
            "principal_ref": self.principal_ref,
            "hai5_level": self.hai5_level,
            "semantic_time": self.semantic_time,
            "body_binding": self.body_binding.canonical(),
            "evidence_refs": list(self.evidence_refs),
            "relations": list(self.relations),
        }
        if self.credential_ref is not None:
            content["credential_ref"] = self.credential_ref
        if self.policy_snapshot_ref is not None:
            content["policy_snapshot_ref"] = self.policy_snapshot_ref
        if self.ingress_assurance is not None:
            content["ingress_assurance"] = self.ingress_assurance
        return content
