"""Immutable evidence handles, lineage and raw transport records."""

from __future__ import annotations

from enum import StrEnum
from typing import Literal, Protocol, runtime_checkable

from pydantic import AwareDatetime, Field, NonNegativeInt, model_validator

from .common import (
    API_VERSION,
    ApiVersion,
    ContractModel,
    Identifier,
    JsonObject,
    Sha256Digest,
    ShortText,
)
from .session import DeviceSessionRef


class EvidenceRetention(StrEnum):
    EPHEMERAL = "ephemeral"
    EXPERIMENT = "experiment"
    REGRESSION_FIXTURE = "regression-fixture"
    AUDIT = "audit"


class RedactionState(StrEnum):
    UNREVIEWED = "unreviewed"
    REDACTED = "redacted"
    CLEARED = "cleared"
    RESTRICTED = "restricted"


class EvidenceHandle(ContractModel):
    evidence_id: Identifier
    store_id: Identifier
    media_type: ShortText
    byte_length: NonNegativeInt
    sha256: Sha256Digest
    object_key: ShortText


class EvidenceRecord(ContractModel):
    api_version: ApiVersion = API_VERSION
    kind: Literal["EvidenceRecord"] = "EvidenceRecord"
    handle: EvidenceHandle
    plugin_id: Identifier
    session: DeviceSessionRef | None = None
    acquisition_id: Identifier | None = None
    operation_id: Identifier | None = None
    created_at: AwareDatetime
    source_kind: Literal[
        "packet-capture",
        "transport-payload",
        "device-file",
        "operator-note",
        "derived",
    ]
    retention: EvidenceRetention
    redaction: RedactionState
    parent_evidence_ids: tuple[Identifier, ...] = Field(default_factory=tuple)
    metadata: JsonObject = Field(default_factory=dict)

    @model_validator(mode="after")
    def lineage_must_be_acyclic_at_record_boundary(self) -> EvidenceRecord:
        if self.handle.evidence_id in self.parent_evidence_ids:
            raise ValueError("an evidence record cannot name itself as a parent")
        if len(self.parent_evidence_ids) != len(set(self.parent_evidence_ids)):
            raise ValueError("parent evidence ids must be unique")
        return self


class RawTransportRecord(ContractModel):
    """A raw-first observation; decoding produces derived evidence, never replacement."""

    api_version: ApiVersion = API_VERSION
    kind: Literal["RawTransportRecord"] = "RawTransportRecord"
    record_id: Identifier
    session: DeviceSessionRef
    profile_id: Identifier | None = None
    observed_at: AwareDatetime
    transport: Identifier
    direction: Literal["device-to-host", "host-to-device", "peer-to-peer", "unknown"]
    channel: ShortText
    sequence: NonNegativeInt | None = None
    payload: EvidenceHandle
    metadata: JsonObject = Field(default_factory=dict)


@runtime_checkable
class EvidenceStore(Protocol):
    """Minimal store boundary; implementations own persistence and access policy."""

    def put_bytes(
        self,
        *,
        evidence_id: str,
        media_type: str,
        payload: bytes,
    ) -> EvidenceHandle: ...

    def register(self, record: EvidenceRecord) -> None: ...

    def get_record(self, evidence_id: str) -> EvidenceRecord: ...

    def open_bytes(self, handle: EvidenceHandle) -> bytes: ...
