"""
SigilUseReceipt — proof-carrying macro usage receipt.

Emitted when a sigil is parsed/validated for a decision, binding the glyph to
its grammar version, canonical expansion hash, and registry root. Keeps sigils
out of branching logic while making intent/policy selections auditably
provable.
"""
from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Dict, List, Mapping, Optional, Literal

from pydantic import Field, field_validator, model_validator

from receipts.base import BaseReceipt, _normalise_utc_millis


class SigilUseReceipt(BaseReceipt):
    """
    Proof-carrying macro receipt: binds a sigil ID to a specific grammar version,
    canonical expansion hash, and registry root.
    """

    receipt_type: Literal["SigilUseReceipt"] = "SigilUseReceipt"
    domain: str = "governance"
    organ: str = "sigil"

    # Lineage
    envelope_id: str
    envelope_hash: Optional[str] = None
    parent_ids: List[str] = Field(default_factory=list)

    # Sigil payload
    sigil_id: str
    grammar_version: str  # e.g., "sigil@v1" or "helix@v1"
    expansion_hash: str  # canonical AST hash (e.g., blake3:...)
    registry_root: str  # Merkle root of registry state
    sigil_text: Optional[str] = None
    constraints: Dict[str, Any] = Field(default_factory=dict)
    gloss: Optional[str] = None

    # Context
    profile: Optional[str] = None
    mode: Optional[str] = None
    body: Optional[str] = None  # clinical/markets/etc.

    # Tri-temporal (event_time / observed_at optional; ts required)
    event_time: Optional[datetime] = None
    observed_at: Optional[datetime] = None
    ts: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))

    # Optional trust context
    trust_profile: Optional[Dict[str, Any]] = None

    @model_validator(mode="before")
    @classmethod
    def _autofill_id(cls, values: Dict[str, Any]) -> Dict[str, Any]:
        """Autogenerate receipt_id if not provided."""
        if not values.get("receipt_id"):
            sigil_token = (values.get("sigil_id") or "sigil")[:12]
            ts = values.get("ts") or datetime.now(timezone.utc)
            ts = _normalise_utc_millis(ts)
            values["ts"] = ts
            values["receipt_id"] = f"sigil_{sigil_token}_{ts.strftime('%Y%m%d%H%M%S%f')}"
        return values

    @field_validator("event_time", "observed_at", "ts", mode="before")
    @classmethod
    def _normalize_time(cls, value: Optional[datetime]) -> Optional[datetime]:
        """Ensure timestamps are UTC-aware and millisecond-normalized."""
        if value is None:
            return None
        return _normalise_utc_millis(value)

    @model_validator(mode="after")
    def _enforce_temporal_order(self) -> "SigilUseReceipt":
        """Ensure tri-temporal ordering: event_time <= observed_at <= ts."""
        if self.event_time and self.observed_at and self.event_time > self.observed_at:
            object.__setattr__(self, "event_time", self.observed_at)
        if self.observed_at and self.ts and self.observed_at > self.ts:
            object.__setattr__(self, "observed_at", self.ts)
        return self

    def to_dict(self) -> Dict[str, Any]:
        """Serialize to a dict with ISO timestamps for external consumers."""
        return {
            "receipt_id": self.receipt_id,
            "receipt_type": self.receipt_type,
            "domain": self.domain,
            "organ": self.organ,
            "envelope_id": self.envelope_id,
            "envelope_hash": self.envelope_hash,
            "parent_ids": self.parent_ids,
            "event_time": self.event_time.isoformat() if self.event_time else None,
            "observed_at": self.observed_at.isoformat() if self.observed_at else None,
            "ts": self.ts.isoformat(),
            "sigil_id": self.sigil_id,
            "grammar_version": self.grammar_version,
            "expansion_hash": self.expansion_hash,
            "registry_root": self.registry_root,
            "sigil_text": self.sigil_text,
            "constraints": self.constraints,
            "gloss": self.gloss,
            "profile": self.profile,
            "mode": self.mode,
            "body": self.body,
            "trust_profile": self.trust_profile,
        }


def verify_sigil_use(receipt: SigilUseReceipt, registry: Mapping[str, Mapping[str, str]]) -> bool:
    """
    Verify receipt against registry snapshot.

    Registry shape: sigil_id -> { "grammar_version": ..., "expansion_hash": ..., "registry_root": ... }
    """
    entry = registry.get(receipt.sigil_id)
    if not entry:
        return False
    if entry.get("grammar_version") != receipt.grammar_version:
        return False
    if entry.get("expansion_hash") != receipt.expansion_hash:
        return False
    if "registry_root" in entry and entry["registry_root"] != receipt.registry_root:
        return False
    return True
