from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass, field
from datetime import datetime
from typing import Literal, Protocol, runtime_checkable


CAPABILITY_ENCRYPTION_KEY_VAULT = "encryption.key_vault"
CAPABILITY_ENCRYPTION_CONTENT_PROTECTION = "encryption.content_protection"
CAPABILITY_ENCRYPTION_CONTENT_CIPHER = "encryption.content_cipher"
CAPABILITY_ENCRYPTION_RECOVERY = "encryption.recovery_ceremony"
CAPABILITY_ENCRYPTION_DISABLE_PREFLIGHT = "encryption.disable_preflight"
CAPABILITY_ENCRYPTION_KEY_MATERIAL_PROVIDER_PREFIX = "encryption.key_material_provider."
CAPABILITY_ENCRYPTION_CONTENT_CIPHER_PROVIDER_PREFIX = (
    "encryption.content_cipher_provider."
)
ENCRYPTION_CONTRACT_VERSION = "1"

ProtectionProfileKind = Literal[
    "server_envelope",
    "tenant_held",
    "end_to_end",
]
KeyLifecycleState = Literal[
    "active",
    "rotation_pending",
    "revoked",
    "destruction_scheduled",
    "destroyed",
    "unavailable",
]
ProtectionState = Literal[
    "active",
    "migration_pending",
    "migrating",
    "migrated",
    "decrypted",
    "exported",
    "destroyed",
    "unavailable",
]


@dataclass(frozen=True, slots=True)
class KeyMaterialProvisionRequest:
    tenant_id: str
    vault_id: str
    key_version: int
    algorithm_suite: str
    purpose: str
    idempotency_key: str
    provider_policy: Mapping[str, object] = field(default_factory=dict)
    import_reference: str | None = None
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        if self.key_version < 1:
            raise ValueError("Key version must be positive")
        for label, value in (
            ("tenant id", self.tenant_id),
            ("vault id", self.vault_id),
            ("algorithm suite", self.algorithm_suite),
            ("purpose", self.purpose),
            ("idempotency key", self.idempotency_key),
        ):
            _require_text(value, label)


@dataclass(frozen=True, slots=True)
class KeyMaterialDescriptor:
    provider_id: str
    provider_key_ref: str
    algorithm_suite: str
    state: KeyLifecycleState
    created_at: datetime
    public_key_ref: str | None = None
    imported: bool = False
    exportable: bool = False
    provider_version: str | None = None
    provenance: Mapping[str, object] = field(default_factory=dict)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        for label, value in (
            ("provider id", self.provider_id),
            ("provider key reference", self.provider_key_ref),
            ("algorithm suite", self.algorithm_suite),
        ):
            _require_text(value, label)
        _reject_secret_mapping(self.provenance, "Provider provenance")


@dataclass(frozen=True, slots=True)
class KeyVaultCreateRequest:
    tenant_id: str
    vault_id: str
    name: str
    provider_id: str
    purpose: str
    algorithm_suite: str
    scope_type: str
    scope_id: str | None
    policy_ref: str
    idempotency_key: str
    recovery_quorum: int = 2
    profile_kind: ProtectionProfileKind = "server_envelope"
    import_reference: str | None = None
    provider_policy: Mapping[str, object] = field(default_factory=dict)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        if self.recovery_quorum < 1:
            raise ValueError("Recovery quorum must be positive")
        for label, value in (
            ("tenant id", self.tenant_id),
            ("vault id", self.vault_id),
            ("name", self.name),
            ("provider id", self.provider_id),
            ("purpose", self.purpose),
            ("algorithm suite", self.algorithm_suite),
            ("scope type", self.scope_type),
            ("policy reference", self.policy_ref),
            ("idempotency key", self.idempotency_key),
        ):
            _require_text(value, label)
        _reject_secret_mapping(self.provider_policy, "Provider policy")


@dataclass(frozen=True, slots=True)
class KeyVersionRef:
    tenant_id: str
    vault_id: str
    version: int
    provider_id: str
    provider_key_ref: str
    algorithm_suite: str
    state: KeyLifecycleState
    created_at: datetime
    activated_at: datetime | None = None
    revoked_at: datetime | None = None
    destruction_scheduled_at: datetime | None = None
    destroyed_at: datetime | None = None
    public_key_ref: str | None = None
    imported: bool = False
    exportable: bool = False
    provenance: Mapping[str, object] = field(default_factory=dict)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION


@dataclass(frozen=True, slots=True)
class KeyVaultRef:
    tenant_id: str
    vault_id: str
    name: str
    provider_id: str
    purpose: str
    profile_kind: ProtectionProfileKind
    scope_type: str
    scope_id: str | None
    policy_ref: str
    recovery_quorum: int
    state: str
    revision: int
    current_key: KeyVersionRef | None
    created_at: datetime
    updated_at: datetime
    provenance: Mapping[str, object] = field(default_factory=dict)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION


@dataclass(frozen=True, slots=True)
class KeyRotationRequest:
    tenant_id: str
    vault_id: str
    expected_revision: int
    reason: str
    policy_decision_ref: str
    assurance_evidence_ref: str
    idempotency_key: str
    algorithm_suite: str | None = None
    provider_policy: Mapping[str, object] = field(default_factory=dict)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        if self.expected_revision < 1:
            raise ValueError("Expected revision must be positive")
        for label, value in (
            ("tenant id", self.tenant_id),
            ("vault id", self.vault_id),
            ("reason", self.reason),
            ("policy decision reference", self.policy_decision_ref),
            ("assurance evidence reference", self.assurance_evidence_ref),
            ("idempotency key", self.idempotency_key),
        ):
            _require_text(value, label)
        _reject_secret_mapping(self.provider_policy, "Provider policy")


@dataclass(frozen=True, slots=True)
class KeyLifecycleRequest:
    tenant_id: str
    vault_id: str
    key_version: int
    expected_revision: int
    reason: str
    policy_decision_ref: str
    assurance_evidence_ref: str
    idempotency_key: str
    effective_at: datetime | None = None
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        if self.key_version < 1 or self.expected_revision < 1:
            raise ValueError("Key version and expected revision must be positive")
        for label, value in (
            ("tenant id", self.tenant_id),
            ("vault id", self.vault_id),
            ("reason", self.reason),
            ("policy decision reference", self.policy_decision_ref),
            ("assurance evidence reference", self.assurance_evidence_ref),
            ("idempotency key", self.idempotency_key),
        ):
            _require_text(value, label)


@dataclass(frozen=True, slots=True)
class ContentProtectionEnvelope:
    envelope_id: str
    tenant_id: str
    owner_module: str
    resource_type: str
    resource_id: str
    profile_kind: ProtectionProfileKind
    profile_id: str
    provider_id: str
    vault_id: str
    key_version: int
    algorithm_suite: str
    ciphertext_ref: str
    ciphertext_digest: str
    authenticated_context_digest: str
    state: ProtectionState
    created_at: datetime
    wrapped_key_refs: tuple[str, ...] = ()
    source_envelope_id: str | None = None
    migration_id: str | None = None
    metadata: Mapping[str, object] = field(default_factory=dict)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        if self.key_version < 1:
            raise ValueError("Envelope key version must be positive")
        for label, value in (
            ("envelope id", self.envelope_id),
            ("tenant id", self.tenant_id),
            ("owner module", self.owner_module),
            ("resource type", self.resource_type),
            ("resource id", self.resource_id),
            ("profile id", self.profile_id),
            ("provider id", self.provider_id),
            ("vault id", self.vault_id),
            ("algorithm suite", self.algorithm_suite),
            ("ciphertext reference", self.ciphertext_ref),
            ("ciphertext digest", self.ciphertext_digest),
            ("authenticated context digest", self.authenticated_context_digest),
        ):
            _require_text(value, label)
        _reject_secret_mapping(self.metadata, "Protection metadata")


@dataclass(frozen=True, slots=True)
class ContentCipherEncryptRequest:
    """Provider request whose key material remains behind the provider boundary."""

    tenant_id: str
    vault_id: str
    key_version: int
    provider_key_ref: str
    algorithm_suite: str
    plaintext: bytes = field(repr=False)
    authenticated_context: bytes = field(repr=False)
    idempotency_key: str
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        if self.key_version < 1:
            raise ValueError("Content cipher key version must be positive")
        for label, value in (
            ("tenant id", self.tenant_id),
            ("vault id", self.vault_id),
            ("provider key reference", self.provider_key_ref),
            ("algorithm suite", self.algorithm_suite),
            ("idempotency key", self.idempotency_key),
        ):
            _require_text(value, label)
        if not self.authenticated_context:
            raise ValueError("Authenticated context is required")


@dataclass(frozen=True, slots=True)
class ContentCipherEncryptResult:
    provider_id: str
    provider_key_ref: str
    wrapped_key_ref: str
    algorithm_suite: str
    ciphertext: bytes = field(repr=False)
    ciphertext_digest: str
    authenticated_context_digest: str
    created_at: datetime
    provenance: Mapping[str, object] = field(default_factory=dict)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        for label, value in (
            ("provider id", self.provider_id),
            ("provider key reference", self.provider_key_ref),
            ("wrapped key reference", self.wrapped_key_ref),
            ("algorithm suite", self.algorithm_suite),
            ("ciphertext digest", self.ciphertext_digest),
            ("authenticated context digest", self.authenticated_context_digest),
        ):
            _require_text(value, label)
        _reject_secret_mapping(self.provenance, "Cipher provenance")


@dataclass(frozen=True, slots=True)
class ContentCipherDecryptRequest:
    tenant_id: str
    provider_key_ref: str
    wrapped_key_ref: str
    algorithm_suite: str
    ciphertext: bytes = field(repr=False)
    ciphertext_digest: str
    authenticated_context: bytes = field(repr=False)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        for label, value in (
            ("tenant id", self.tenant_id),
            ("provider key reference", self.provider_key_ref),
            ("wrapped key reference", self.wrapped_key_ref),
            ("algorithm suite", self.algorithm_suite),
            ("ciphertext digest", self.ciphertext_digest),
        ):
            _require_text(value, label)
        if not self.authenticated_context:
            raise ValueError("Authenticated context is required")


@dataclass(frozen=True, slots=True)
class ContentCipherDecryptResult:
    plaintext: bytes = field(repr=False)
    provider_id: str
    provider_key_ref: str
    wrapped_key_ref: str
    verified_at: datetime
    provenance: Mapping[str, object] = field(default_factory=dict)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        for label, value in (
            ("provider id", self.provider_id),
            ("provider key reference", self.provider_key_ref),
            ("wrapped key reference", self.wrapped_key_ref),
        ):
            _require_text(value, label)
        _reject_secret_mapping(self.provenance, "Cipher provenance")


@dataclass(frozen=True, slots=True)
class ContentKeyRewrapRequest:
    tenant_id: str
    source_provider_key_ref: str
    source_wrapped_key_ref: str
    target_provider_key_ref: str
    algorithm_suite: str
    idempotency_key: str
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        for label, value in (
            ("tenant id", self.tenant_id),
            ("source provider key reference", self.source_provider_key_ref),
            ("source wrapped key reference", self.source_wrapped_key_ref),
            ("target provider key reference", self.target_provider_key_ref),
            ("algorithm suite", self.algorithm_suite),
            ("idempotency key", self.idempotency_key),
        ):
            _require_text(value, label)


@dataclass(frozen=True, slots=True)
class ContentKeyRewrapResult:
    provider_id: str
    source_wrapped_key_ref: str
    target_provider_key_ref: str
    target_wrapped_key_ref: str
    algorithm_suite: str
    completed_at: datetime
    provenance: Mapping[str, object] = field(default_factory=dict)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        for label, value in (
            ("provider id", self.provider_id),
            ("source wrapped key reference", self.source_wrapped_key_ref),
            ("target provider key reference", self.target_provider_key_ref),
            ("target wrapped key reference", self.target_wrapped_key_ref),
            ("algorithm suite", self.algorithm_suite),
        ):
            _require_text(value, label)
        _reject_secret_mapping(self.provenance, "Rewrap provenance")


@dataclass(frozen=True, slots=True)
class ContentProtectionRequest:
    tenant_id: str
    owner_module: str
    resource_type: str
    resource_id: str
    profile_id: str
    vault_id: str
    ciphertext_ref: str
    plaintext: bytes = field(repr=False)
    policy_decision_ref: str
    idempotency_key: str
    actor_id: str | None = None
    metadata: Mapping[str, object] = field(default_factory=dict)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        for label, value in (
            ("tenant id", self.tenant_id),
            ("owner module", self.owner_module),
            ("resource type", self.resource_type),
            ("resource id", self.resource_id),
            ("profile id", self.profile_id),
            ("vault id", self.vault_id),
            ("ciphertext reference", self.ciphertext_ref),
            ("policy decision reference", self.policy_decision_ref),
            ("idempotency key", self.idempotency_key),
        ):
            _require_text(value, label)
        _reject_secret_mapping(self.metadata, "Protection metadata")


@dataclass(frozen=True, slots=True)
class ProtectedContent:
    envelope: ContentProtectionEnvelope
    ciphertext: bytes = field(repr=False)
    replayed: bool = False
    contract_version: str = ENCRYPTION_CONTRACT_VERSION


@dataclass(frozen=True, slots=True)
class ContentUnprotectionRequest:
    tenant_id: str
    owner_module: str
    resource_type: str
    resource_id: str
    envelope_id: str
    ciphertext: bytes = field(repr=False)
    actor_id: str | None = None
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        for label, value in (
            ("tenant id", self.tenant_id),
            ("owner module", self.owner_module),
            ("resource type", self.resource_type),
            ("resource id", self.resource_id),
            ("envelope id", self.envelope_id),
        ):
            _require_text(value, label)


@dataclass(frozen=True, slots=True)
class ProtectionRegistrationRequest:
    envelope: ContentProtectionEnvelope
    idempotency_key: str
    policy_decision_ref: str
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        _require_text(self.idempotency_key, "idempotency key")
        _require_text(self.policy_decision_ref, "policy decision reference")


@dataclass(frozen=True, slots=True)
class ProtectionMigrationRequest:
    tenant_id: str
    envelope_id: str
    target_provider_id: str
    target_vault_id: str
    target_key_version: int
    target_algorithm_suite: str
    mode: Literal["rewrap", "reencrypt", "decrypt", "export", "destroy"]
    policy_decision_ref: str
    assurance_evidence_ref: str
    idempotency_key: str
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        if self.target_key_version < 1:
            raise ValueError("Target key version must be positive")
        for label, value in (
            ("tenant id", self.tenant_id),
            ("envelope id", self.envelope_id),
            ("target provider id", self.target_provider_id),
            ("target vault id", self.target_vault_id),
            ("target algorithm suite", self.target_algorithm_suite),
            ("policy decision reference", self.policy_decision_ref),
            ("assurance evidence reference", self.assurance_evidence_ref),
            ("idempotency key", self.idempotency_key),
        ):
            _require_text(value, label)


@dataclass(frozen=True, slots=True)
class ProtectionMigrationResult:
    migration_id: str
    state: Literal[
        "requested",
        "running",
        "succeeded",
        "rejected",
        "outcome_unknown",
        "reconciled",
    ]
    source_envelope_id: str
    target_envelope: ContentProtectionEnvelope | None = None
    error: str | None = None
    evidence_refs: tuple[str, ...] = ()
    provenance: Mapping[str, object] = field(default_factory=dict)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION


@dataclass(frozen=True, slots=True)
class RecoveryRequest:
    tenant_id: str
    vault_id: str
    reason: str
    requested_scope: str
    policy_decision_ref: str
    assurance_evidence_ref: str
    idempotency_key: str
    expires_at: datetime
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        for label, value in (
            ("tenant id", self.tenant_id),
            ("vault id", self.vault_id),
            ("reason", self.reason),
            ("requested scope", self.requested_scope),
            ("policy decision reference", self.policy_decision_ref),
            ("assurance evidence reference", self.assurance_evidence_ref),
            ("idempotency key", self.idempotency_key),
        ):
            _require_text(value, label)


@dataclass(frozen=True, slots=True)
class RecoveryApprovalRequest:
    tenant_id: str
    recovery_id: str
    decision: Literal["approve", "reject"]
    reason: str
    assurance_evidence_ref: str
    expected_revision: int
    idempotency_key: str
    contract_version: str = ENCRYPTION_CONTRACT_VERSION

    def __post_init__(self) -> None:
        _validate_contract(self.contract_version)
        if self.expected_revision < 1:
            raise ValueError("Expected revision must be positive")
        for label, value in (
            ("tenant id", self.tenant_id),
            ("recovery id", self.recovery_id),
            ("reason", self.reason),
            ("assurance evidence reference", self.assurance_evidence_ref),
            ("idempotency key", self.idempotency_key),
        ):
            _require_text(value, label)


@dataclass(frozen=True, slots=True)
class RecoveryRef:
    tenant_id: str
    recovery_id: str
    vault_id: str
    state: Literal["pending", "approved", "rejected", "expired", "executed"]
    requested_scope: str
    quorum: int
    approvals: int
    rejections: int
    revision: int
    expires_at: datetime
    policy_decision_ref: str
    execution_ref: str | None = None
    provenance: Mapping[str, object] = field(default_factory=dict)
    contract_version: str = ENCRYPTION_CONTRACT_VERSION


@dataclass(frozen=True, slots=True)
class DisablePreflightReport:
    allowed: bool
    protected_count: int
    unresolved_count: int
    state_counts: Mapping[str, int]
    blocking_envelope_refs: tuple[str, ...]
    required_actions: tuple[str, ...]
    generated_at: datetime
    contract_version: str = ENCRYPTION_CONTRACT_VERSION


@runtime_checkable
class EncryptionKeyMaterialProvider(Protocol):
    def provision_key(
        self,
        session: object,
        principal: object,
        *,
        request: KeyMaterialProvisionRequest,
    ) -> KeyMaterialDescriptor: ...

    def schedule_key_destruction(
        self,
        session: object,
        principal: object,
        *,
        provider_key_ref: str,
        effective_at: datetime,
        idempotency_key: str,
    ) -> KeyMaterialDescriptor: ...

    def revoke_key(
        self,
        session: object,
        principal: object,
        *,
        provider_key_ref: str,
        reason: str,
        idempotency_key: str,
    ) -> KeyMaterialDescriptor: ...

    def key_status(
        self,
        session: object,
        *,
        provider_key_ref: str,
    ) -> KeyMaterialDescriptor | None: ...


@runtime_checkable
class EncryptionContentCipherProvider(Protocol):
    """Cryptographic provider that never exports plaintext content keys."""

    def encrypt_content(
        self,
        session: object,
        *,
        request: ContentCipherEncryptRequest,
    ) -> ContentCipherEncryptResult: ...

    def decrypt_content(
        self,
        session: object,
        *,
        request: ContentCipherDecryptRequest,
    ) -> ContentCipherDecryptResult: ...

    def rewrap_content_key(
        self,
        session: object,
        *,
        request: ContentKeyRewrapRequest,
    ) -> ContentKeyRewrapResult: ...

    def destroy_wrapped_content_key(
        self,
        session: object,
        *,
        tenant_id: str,
        wrapped_key_ref: str,
        idempotency_key: str,
    ) -> None: ...


@runtime_checkable
class EncryptionKeyVault(Protocol):
    def create_vault(
        self,
        session: object,
        principal: object,
        *,
        request: KeyVaultCreateRequest,
    ) -> KeyVaultRef: ...

    def rotate_key(
        self,
        session: object,
        principal: object,
        *,
        request: KeyRotationRequest,
    ) -> KeyVaultRef: ...

    def revoke_key(
        self,
        session: object,
        principal: object,
        *,
        request: KeyLifecycleRequest,
    ) -> KeyVaultRef: ...

    def schedule_destruction(
        self,
        session: object,
        principal: object,
        *,
        request: KeyLifecycleRequest,
    ) -> KeyVaultRef: ...

    def get_vault(
        self,
        session: object,
        *,
        tenant_id: str,
        vault_id: str,
    ) -> KeyVaultRef | None: ...

    def reconcile_vault(
        self,
        session: object,
        principal: object,
        *,
        tenant_id: str,
        vault_id: str,
    ) -> KeyVaultRef: ...


@runtime_checkable
class ContentProtectionRegistry(Protocol):
    def register_envelope(
        self,
        session: object,
        principal: object,
        *,
        request: ProtectionRegistrationRequest,
    ) -> ContentProtectionEnvelope: ...

    def request_migration(
        self,
        session: object,
        principal: object,
        *,
        request: ProtectionMigrationRequest,
    ) -> ProtectionMigrationResult: ...

    def reconcile_migration(
        self,
        session: object,
        principal: object,
        *,
        migration_id: str,
    ) -> ProtectionMigrationResult: ...

    def get_envelope(
        self,
        session: object,
        principal: object,
        *,
        tenant_id: str,
        envelope_id: str,
    ) -> ContentProtectionEnvelope | None: ...

    def record_migration_outcome(
        self,
        session: object,
        principal: object,
        *,
        migration_id: str,
        state: Literal["succeeded", "rejected", "outcome_unknown"],
        evidence_refs: tuple[str, ...],
        target_envelope: ContentProtectionEnvelope | None = None,
        error_code: str | None = None,
    ) -> ProtectionMigrationResult: ...


@runtime_checkable
class EncryptionContentCipher(Protocol):
    """Owner-facing content encryption and decryption boundary."""

    def protect_content(
        self,
        session: object,
        *,
        request: ContentProtectionRequest,
    ) -> ProtectedContent: ...

    def unprotect_content(
        self,
        session: object,
        *,
        request: ContentUnprotectionRequest,
    ) -> bytes: ...

    def execute_rewrap(
        self,
        session: object,
        principal: object,
        *,
        migration_id: str,
    ) -> ProtectionMigrationResult: ...

    def prepare_reencryption(
        self,
        session: object,
        principal: object,
        *,
        migration_id: str,
        source_ciphertext: bytes,
        target_ciphertext_ref: str,
    ) -> ProtectedContent: ...


@runtime_checkable
class EncryptionRecoveryCeremony(Protocol):
    def request_recovery(
        self,
        session: object,
        principal: object,
        *,
        request: RecoveryRequest,
    ) -> RecoveryRef: ...

    def decide_recovery(
        self,
        session: object,
        principal: object,
        *,
        request: RecoveryApprovalRequest,
    ) -> RecoveryRef: ...


@runtime_checkable
class EncryptionDisablePreflight(Protocol):
    def assess_disable(
        self,
        session: object,
        *,
        tenant_id: str | None = None,
        limit: int = 100,
    ) -> DisablePreflightReport: ...


def key_material_provider(
    registry: object | None,
    provider_id: str,
) -> EncryptionKeyMaterialProvider | None:
    capability = _capability(
        registry,
        f"{CAPABILITY_ENCRYPTION_KEY_MATERIAL_PROVIDER_PREFIX}{provider_id}",
    )
    return capability if isinstance(capability, EncryptionKeyMaterialProvider) else None


def content_cipher_provider(
    registry: object | None,
    provider_id: str,
) -> EncryptionContentCipherProvider | None:
    capability = _capability(
        registry,
        f"{CAPABILITY_ENCRYPTION_CONTENT_CIPHER_PROVIDER_PREFIX}{provider_id}",
    )
    return (
        capability if isinstance(capability, EncryptionContentCipherProvider) else None
    )


def encryption_key_vault(registry: object | None) -> EncryptionKeyVault | None:
    value = _capability(registry, CAPABILITY_ENCRYPTION_KEY_VAULT)
    return value if isinstance(value, EncryptionKeyVault) else None


def content_protection_registry(
    registry: object | None,
) -> ContentProtectionRegistry | None:
    value = _capability(registry, CAPABILITY_ENCRYPTION_CONTENT_PROTECTION)
    return value if isinstance(value, ContentProtectionRegistry) else None


def encryption_content_cipher(
    registry: object | None,
) -> EncryptionContentCipher | None:
    value = _capability(registry, CAPABILITY_ENCRYPTION_CONTENT_CIPHER)
    return value if isinstance(value, EncryptionContentCipher) else None


def encryption_recovery_ceremony(
    registry: object | None,
) -> EncryptionRecoveryCeremony | None:
    value = _capability(registry, CAPABILITY_ENCRYPTION_RECOVERY)
    return value if isinstance(value, EncryptionRecoveryCeremony) else None


def encryption_disable_preflight(
    registry: object | None,
) -> EncryptionDisablePreflight | None:
    value = _capability(registry, CAPABILITY_ENCRYPTION_DISABLE_PREFLIGHT)
    return value if isinstance(value, EncryptionDisablePreflight) else None


def _capability(registry: object | None, name: str) -> object | None:
    if (
        registry is None
        or not hasattr(registry, "has_capability")
        or not hasattr(registry, "capability")
        or not registry.has_capability(name)
    ):
        return None
    return registry.capability(name)


def _validate_contract(value: str) -> None:
    if value != ENCRYPTION_CONTRACT_VERSION:
        raise ValueError("Unsupported encryption contract version")


def _require_text(value: str, label: str) -> None:
    if not value.strip():
        raise ValueError(f"{label.capitalize()} is required")


def _reject_secret_mapping(value: Mapping[str, object], label: str) -> None:
    forbidden = {
        "ciphertext",
        "credential",
        "key",
        "key_material",
        "password",
        "plaintext",
        "private_key",
        "secret",
        "token",
    }
    keys = {str(key).strip().casefold() for key in value}
    if forbidden & keys:
        raise ValueError(f"{label} must not contain secret material")


__all__ = [
    "CAPABILITY_ENCRYPTION_CONTENT_CIPHER",
    "CAPABILITY_ENCRYPTION_CONTENT_CIPHER_PROVIDER_PREFIX",
    "CAPABILITY_ENCRYPTION_CONTENT_PROTECTION",
    "CAPABILITY_ENCRYPTION_DISABLE_PREFLIGHT",
    "CAPABILITY_ENCRYPTION_KEY_MATERIAL_PROVIDER_PREFIX",
    "CAPABILITY_ENCRYPTION_KEY_VAULT",
    "CAPABILITY_ENCRYPTION_RECOVERY",
    "ContentCipherDecryptRequest",
    "ContentCipherDecryptResult",
    "ContentCipherEncryptRequest",
    "ContentCipherEncryptResult",
    "ContentKeyRewrapRequest",
    "ContentKeyRewrapResult",
    "ContentProtectionRequest",
    "ContentProtectionEnvelope",
    "ContentProtectionRegistry",
    "ContentUnprotectionRequest",
    "DisablePreflightReport",
    "EncryptionContentCipher",
    "EncryptionContentCipherProvider",
    "EncryptionDisablePreflight",
    "EncryptionKeyMaterialProvider",
    "EncryptionKeyVault",
    "EncryptionRecoveryCeremony",
    "KeyLifecycleRequest",
    "KeyMaterialDescriptor",
    "KeyMaterialProvisionRequest",
    "KeyRotationRequest",
    "KeyVaultCreateRequest",
    "KeyVaultRef",
    "KeyVersionRef",
    "ProtectionMigrationRequest",
    "ProtectionMigrationResult",
    "ProtectionRegistrationRequest",
    "ProtectedContent",
    "RecoveryApprovalRequest",
    "RecoveryRef",
    "RecoveryRequest",
    "content_cipher_provider",
    "content_protection_registry",
    "encryption_content_cipher",
    "encryption_disable_preflight",
    "encryption_key_vault",
    "encryption_recovery_ceremony",
    "key_material_provider",
]
