from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Dict, List, Optional
from hashlib import blake2b

from receipts.canonicalize import to_jcs_bytes
from receipts.compat.pyd import BaseModel, ConfigDictLike, Field, model_validator
from receipts.base import Domain, _normalise_utc_millis
from receipts.domains.governance import GovernanceReceipt


class GateWitnessSummary(BaseModel):
    """
    Lightweight representation of a gate witness produced by gate algebra.

    This matches the dictionaries produced by GateWitness.to_dict()
    (engine/gates/algebra.py) while keeping the receipts layer free from
    direct dependencies on gate algebra internals.
    """

    model_config = ConfigDictLike(frozen=True)

    name: str = Field(description="Gate identifier")
    passed: bool = Field(description="Whether the gate predicate passed")
    evidence_hash: str = Field(description="Canonical hash of the gate evidence (JCS → BLAKE2b)")
    summary: str = Field(description="Human-readable one-liner for audit/debug")
    evaluated_at: Optional[datetime] = Field(
        default=None,
        description="Timestamp of evaluation (ISO-8601).",
    )
    path: Optional[str] = Field(
        default=None,
        description="Logical path inside the gate AST (e.g., AND.left.dignity_floor)",
    )

    @model_validator(mode="after")
    def _normalise_time(self) -> "GateWitnessSummary":
        if self.evaluated_at:
            object.__setattr__(self, "evaluated_at", _normalise_utc_millis(self.evaluated_at))
        return self


class GateReceipt(GovernanceReceipt):
    """
    GateReceipt: records the outcome of evaluating a gate expression against a payload.

    This binds a specific gate AST (gate_expr_hash) to its witnesses so the
    decision can be replayed or audited independently of the calling service.
    """

    receipt_type: str = "GateReceipt"
    domain: str = Field(default=Domain.GOVERNANCE.value)

    gate_expr_hash: str = Field(description="BLAKE2b hash of the gate AST (JCS over canonical form).")
    gate_snapshot: Optional[Dict[str, Any]] = Field(
        default=None,
        description="Deterministic snapshot of the gate AST/config used for this evaluation.",
    )
    gate_snapshot_hash: Optional[str] = Field(
        default=None,
        description="BLAKE2b-256 hash of the gate_snapshot (computed if not provided).",
    )
    gate_version: Optional[str] = Field(
        default=None,
        description="Version tag for the gatebook/policy in effect.",
    )
    policy_version: Optional[str] = Field(
        default=None,
        description="Optional policy version (gatebook policy) if distinct from gate_version.",
    )
    payload_ref: Optional[str] = Field(
        default=None,
        description="Optional reference to the upstream payload/receipt gated (e.g., prophecy_id).",
    )
    passed: bool = Field(description="Overall result of the gate expression.")
    witnesses: List[GateWitnessSummary] = Field(
        default_factory=list,
        description="Flattened witness list with evidence hashes (no blobs).",
    )
    evaluated_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="UTC timestamp when the gate was evaluated.",
    )
    meta: Dict[str, Any] = Field(
        default_factory=dict,
        description="Free-form metadata (e.g., gate profile, enforcement mode).",
    )

    @model_validator(mode="after")
    def _normalise_ts(self) -> "GateReceipt":
        object.__setattr__(self, "evaluated_at", _normalise_utc_millis(self.evaluated_at))
        if self.gate_snapshot and not self.gate_snapshot_hash:
            digest = blake2b(to_jcs_bytes(self.gate_snapshot), digest_size=32).hexdigest()
            object.__setattr__(self, "gate_snapshot_hash", digest)
        return self
