"""
ConstitutionalWitness - Receipt witnessing layer for the Loom organism.

This module complements the existing WitnessStore (calibration samples) by
providing constitutional witnessing capabilities:
- Witnesses every receipt (preserves ground truth before deliberation)
- Provides case-level history for suggestions and governance
- Powers self-audit loops
- Enables Gatebook Time Machine replay
- Tracks lineage for M1 → M2 continuity

This module sits between:
- Receipts (truth) → ConstitutionalWitness → Mem0 (memory)
- Council (governance) ← ConstitutionalWitness → DriftSentinel (immune system)

Usage:
    from receipts.constitutional_witness import (
        ConstitutionalWitness,
        get_constitutional_witness,
        witness_receipt
    )

    # Get singleton instance
    witness = get_constitutional_witness()

    # Witness a receipt
    witness_id = witness.witness(receipt)

    # Get case history
    history = witness.get_case_history(case_id, limit=10)

    # Sample for self-audit
    samples = witness.sample_recent(hours=24, n=100)

    # Verify a receipt
    is_valid = witness.verify(receipt)

    # Run self-audit
    results = await witness.self_audit(hours=24, sample_size=100)
"""

from __future__ import annotations

import asyncio
import hashlib
import json
import logging
import random
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple

logger = logging.getLogger(__name__)

# Type aliases
ReceiptDict = Dict[str, Any]


@dataclass
class WitnessRecord:
    """A witnessed receipt with metadata."""
    witness_id: str
    receipt_id: str
    receipt_type: str
    receipt_hash: str
    space: str
    case_id: Optional[str]
    witnessed_at: str
    payload: Dict[str, Any]
    verification_status: str = "pending"  # pending, verified, failed
    lineage: Optional[Dict[str, Any]] = None

    def to_dict(self) -> Dict[str, Any]:
        return {
            "witness_id": self.witness_id,
            "receipt_id": self.receipt_id,
            "receipt_type": self.receipt_type,
            "receipt_hash": self.receipt_hash,
            "space": self.space,
            "case_id": self.case_id,
            "witnessed_at": self.witnessed_at,
            "payload": self.payload,
            "verification_status": self.verification_status,
            "lineage": self.lineage,
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "WitnessRecord":
        return cls(
            witness_id=data["witness_id"],
            receipt_id=data["receipt_id"],
            receipt_type=data["receipt_type"],
            receipt_hash=data["receipt_hash"],
            space=data["space"],
            case_id=data.get("case_id"),
            witnessed_at=data["witnessed_at"],
            payload=data["payload"],
            verification_status=data.get("verification_status", "pending"),
            lineage=data.get("lineage"),
        )


@dataclass
class SelfAuditResult:
    """Result of a self-audit run."""
    audit_id: str
    started_at: str
    completed_at: str
    hours_covered: int
    total_sampled: int
    verified: int
    failed: int
    drift_detected: int
    errors: List[Dict[str, Any]]
    stats: Dict[str, Any]

    def to_dict(self) -> Dict[str, Any]:
        return {
            "audit_id": self.audit_id,
            "started_at": self.started_at,
            "completed_at": self.completed_at,
            "hours_covered": self.hours_covered,
            "total_sampled": self.total_sampled,
            "verified": self.verified,
            "failed": self.failed,
            "drift_detected": self.drift_detected,
            "errors": self.errors,
            "stats": self.stats,
            "verification_rate": self.verified / self.total_sampled if self.total_sampled > 0 else 0.0,
        }


def _generate_id(prefix: str = "wit") -> str:
    """Generate a unique ID."""
    try:
        import ulid
        return f"{prefix}_{str(ulid.ULID())}"
    except (ImportError, AttributeError):
        import uuid
        return f"{prefix}_{uuid.uuid4().hex[:26]}"


class ConstitutionalWitness:
    """
    Constitutional witness layer for the Loom organism.

    Witnesses receipts, provides historical context, and powers self-audit.
    """

    def __init__(
        self,
        receipts_dir: Optional[Path] = None,
        witness_log_path: Optional[Path] = None,
        enable_mem0: bool = True,
        enable_drift_check: bool = True,
    ):
        """
        Initialize ConstitutionalWitness.

        Args:
            receipts_dir: Directory containing receipt JSONL files
            witness_log_path: Path to witness log file
            enable_mem0: Enable Mem0 integration for semantic retrieval
            enable_drift_check: Enable DriftSentinel for verification
        """
        self.receipts_dir = receipts_dir or Path(".loom/receipts")
        self.witness_log_path = witness_log_path or self.receipts_dir / "constitutional_witness.jsonl"
        self.enable_mem0 = enable_mem0
        self.enable_drift_check = enable_drift_check

        # In-memory caches
        self._recent_witnesses: List[WitnessRecord] = []
        self._case_index: Dict[str, List[str]] = {}  # case_id -> [witness_ids]
        self._max_recent = 1000

        # Ensure directories exist
        self.receipts_dir.mkdir(parents=True, exist_ok=True)

        logger.info(f"[constitutional_witness] Initialized with receipts_dir={self.receipts_dir}")

    def _compute_hash(self, receipt: ReceiptDict) -> str:
        """Compute deterministic hash of receipt content."""
        # Exclude mutable/signature fields (must match receipts.sign.HASH_EXCLUDE_FIELDS)
        EXCLUDE_FIELDS = {
            "receipt_hash",
            "signature",
            "signatures",
            "verifier_signatures",
            "witnessed_at",
            "trustmark_status",  # Added post-signing
        }
        content = {k: v for k, v in receipt.items() if k not in EXCLUDE_FIELDS}

        # Also strip from nested context.legacy if present (match sign.py behavior)
        if "context" in content and isinstance(content["context"], dict):
            ctx = dict(content["context"])
            if "legacy" in ctx and isinstance(ctx["legacy"], dict):
                legacy = dict(ctx["legacy"])
                legacy.pop("signature", None)
                ctx["legacy"] = legacy
            content["context"] = ctx

        try:
            from receipts.canonicalize import to_jcs_bytes
            canonical = to_jcs_bytes(content)
            return hashlib.sha256(canonical).hexdigest()
        except ImportError:
            # Fallback to sorted JSON
            return hashlib.sha256(
                json.dumps(content, sort_keys=True, default=str).encode()
            ).hexdigest()

    def witness(self, receipt: ReceiptDict) -> str:
        """
        Witness a receipt - record it before any deliberation.

        This is the ground truth preservation step.

        Args:
            receipt: The receipt to witness

        Returns:
            witness_id for the witnessed record
        """
        witness_id = _generate_id("wit")
        receipt_id = receipt.get("receipt_id") or receipt.get("id") or "unknown"
        receipt_type = receipt.get("receipt_type") or receipt.get("type") or "unknown"
        receipt_hash = receipt.get("receipt_hash") or self._compute_hash(receipt)
        space = receipt.get("domain") or receipt.get("space") or "default"
        case_id = receipt.get("case_id") or receipt.get("event_id")

        # Extract lineage if present
        lineage = None
        if "lineage" in receipt:
            lineage = receipt["lineage"]
        elif "parent_receipt_id" in receipt:
            lineage = {"parent": receipt["parent_receipt_id"]}

        # Create witness record
        record = WitnessRecord(
            witness_id=witness_id,
            receipt_id=receipt_id,
            receipt_type=receipt_type,
            receipt_hash=receipt_hash,
            space=space,
            case_id=case_id,
            witnessed_at=datetime.utcnow().isoformat() + "Z",
            payload=receipt,
            verification_status="pending",
            lineage=lineage,
        )

        # Add to recent cache
        self._recent_witnesses.append(record)
        if len(self._recent_witnesses) > self._max_recent:
            self._recent_witnesses.pop(0)

        # Update case index
        if case_id:
            if case_id not in self._case_index:
                self._case_index[case_id] = []
            self._case_index[case_id].append(witness_id)

        # Write to witness log
        self._write_witness_log(record)

        # Ingest to Mem0 if enabled
        if self.enable_mem0:
            self._ingest_to_mem0(record)

        logger.debug(f"[constitutional_witness] Witnessed {receipt_type} as {witness_id}")
        return witness_id

    def _write_witness_log(self, record: WitnessRecord) -> None:
        """Append witness record to log file."""
        try:
            with open(self.witness_log_path, "a", encoding="utf-8") as f:
                f.write(json.dumps(record.to_dict(), default=str) + "\n")
        except Exception as e:
            logger.warning(f"[constitutional_witness] Failed to write witness log: {e}")

    def _ingest_to_mem0(self, record: WitnessRecord) -> None:
        """Ingest witness record to Mem0 semantic memory."""
        try:
            from receipts.mem0_ingest import ingest_receipt
            ingest_receipt(record.payload, space=f"witness:{record.space}")
        except ImportError:
            logger.debug("[constitutional_witness] Mem0 ingest not available")
        except Exception as e:
            logger.debug(f"[constitutional_witness] Mem0 ingest failed: {e}")

    def verify(self, receipt: ReceiptDict) -> bool:
        """
        Verify a receipt's hash matches its content.

        Args:
            receipt: The receipt to verify

        Returns:
            True if hash is valid, False otherwise
        """
        stored_hash = receipt.get("receipt_hash")
        if not stored_hash:
            return False

        computed_hash = self._compute_hash(receipt)
        return stored_hash == computed_hash

    def get_case_history(
        self,
        case_id: str,
        limit: int = 10,
        receipt_types: Optional[Set[str]] = None,
    ) -> List[WitnessRecord]:
        """
        Get witnessed receipts for a case.

        Args:
            case_id: The case ID to query
            limit: Maximum records to return
            receipt_types: Optional filter by receipt type

        Returns:
            List of WitnessRecords for the case (most recent first)
        """
        results: List[WitnessRecord] = []
        seen_ids: Set[str] = set()

        # Search in-memory cache first (most recent)
        for record in reversed(self._recent_witnesses):
            if record.case_id == case_id:
                if receipt_types and record.receipt_type not in receipt_types:
                    continue
                if record.receipt_id not in seen_ids:
                    results.append(record)
                    seen_ids.add(record.receipt_id)
                if len(results) >= limit:
                    return results

        # Search witness log file
        if len(results) < limit and self.witness_log_path.exists():
            try:
                # Read all matching records, then sort by time
                file_records: List[WitnessRecord] = []
                with open(self.witness_log_path, "r", encoding="utf-8") as f:
                    for line in f:
                        try:
                            data = json.loads(line.strip())
                            if data.get("case_id") != case_id:
                                continue
                            if receipt_types and data.get("receipt_type") not in receipt_types:
                                continue
                            if data.get("receipt_id") in seen_ids:
                                continue

                            record = WitnessRecord.from_dict(data)
                            file_records.append(record)
                            seen_ids.add(record.receipt_id)
                        except (json.JSONDecodeError, KeyError, ValueError):
                            continue

                # Sort by witnessed_at descending (most recent first)
                file_records.sort(key=lambda r: r.witnessed_at or "", reverse=True)
                results.extend(file_records)
            except Exception as e:
                logger.warning(f"[constitutional_witness] Failed to search witness log: {e}")

        # If still not enough, search Mem0
        if len(results) < limit and self.enable_mem0:
            try:
                from receipts.mem0_ingest import search_receipts
                mem0_results = search_receipts(
                    query=f"case_id:{case_id}",
                    space=f"witness:*",
                    limit=limit - len(results)
                )
                for item in mem0_results.get("results", []):
                    payload = item.get("metadata", {}).get("payload", {})
                    if payload and payload.get("receipt_id") not in seen_ids:
                        record = WitnessRecord(
                            witness_id=item.get("id", "unknown"),
                            receipt_id=payload.get("receipt_id", "unknown"),
                            receipt_type=payload.get("receipt_type", "unknown"),
                            receipt_hash=payload.get("receipt_hash", ""),
                            space=payload.get("space", "default"),
                            case_id=case_id,
                            witnessed_at=payload.get("witnessed_at", ""),
                            payload=payload,
                        )
                        results.append(record)
                        seen_ids.add(record.receipt_id)
            except Exception as e:
                logger.debug(f"[constitutional_witness] Mem0 search failed: {e}")

        return results[:limit]

    def sample_recent(
        self,
        hours: int = 24,
        n: int = 100,
        receipt_types: Optional[Set[str]] = None,
    ) -> List[WitnessRecord]:
        """
        Sample recent witnessed receipts for self-audit.

        Args:
            hours: How many hours back to sample
            n: Number of samples to return
            receipt_types: Optional filter by receipt type

        Returns:
            Random sample of WitnessRecords
        """
        cutoff = datetime.utcnow() - timedelta(hours=hours)
        candidates = []

        # Sample from in-memory cache
        for record in self._recent_witnesses:
            try:
                witnessed_at = datetime.fromisoformat(
                    record.witnessed_at.replace("Z", "")
                )
                if witnessed_at >= cutoff:
                    if receipt_types and record.receipt_type not in receipt_types:
                        continue
                    candidates.append(record)
            except (ValueError, TypeError):
                continue

        # If not enough in cache, scan log file
        if len(candidates) < n and self.witness_log_path.exists():
            try:
                with open(self.witness_log_path, "r", encoding="utf-8") as f:
                    for line in f:
                        try:
                            data = json.loads(line.strip())
                            witnessed_at = datetime.fromisoformat(
                                data["witnessed_at"].replace("Z", "")
                            )
                            if witnessed_at >= cutoff:
                                if receipt_types and data["receipt_type"] not in receipt_types:
                                    continue
                                record = WitnessRecord.from_dict(data)
                                # Avoid duplicates
                                if record.witness_id not in [c.witness_id for c in candidates]:
                                    candidates.append(record)
                        except (json.JSONDecodeError, KeyError, ValueError):
                            continue
            except Exception as e:
                logger.warning(f"[constitutional_witness] Failed to scan witness log: {e}")

        # Random sample
        if len(candidates) > n:
            return random.sample(candidates, n)
        return candidates

    def iter_receipts(
        self,
        hours: Optional[int] = None,
        receipt_types: Optional[Set[str]] = None,
    ) -> Iterator[ReceiptDict]:
        """
        Iterate over receipts from JSONL files.

        Args:
            hours: Optional filter to last N hours
            receipt_types: Optional filter by receipt type

        Yields:
            Receipt dictionaries
        """
        cutoff = None
        if hours:
            cutoff = datetime.utcnow() - timedelta(hours=hours)

        for jsonl_file in self.receipts_dir.glob("*.jsonl"):
            try:
                with open(jsonl_file, "r", encoding="utf-8") as f:
                    for line in f:
                        try:
                            receipt = json.loads(line.strip())

                            # Time filter
                            if cutoff:
                                ts_str = receipt.get("transaction_time") or receipt.get("timestamp")
                                if ts_str:
                                    try:
                                        ts = datetime.fromisoformat(ts_str.replace("Z", ""))
                                        if ts < cutoff:
                                            continue
                                    except (ValueError, TypeError):
                                        pass

                            # Type filter
                            if receipt_types:
                                rtype = receipt.get("receipt_type") or receipt.get("type")
                                if rtype not in receipt_types:
                                    continue

                            yield receipt
                        except json.JSONDecodeError:
                            continue
            except Exception as e:
                logger.warning(f"[constitutional_witness] Failed to read {jsonl_file}: {e}")

    # =========================================================================
    # LINEAGE EXPANSION
    # =========================================================================

    def get_receipt_by_id(self, receipt_id: str) -> Optional[WitnessRecord]:
        """
        Get a witnessed receipt by its ID.

        Searches in-memory cache first, then disk log, then Mem0.

        Args:
            receipt_id: The receipt ID to find

        Returns:
            WitnessRecord if found, None otherwise
        """
        # Check in-memory cache first
        for record in self._recent_witnesses:
            if record.receipt_id == receipt_id:
                return record

        # Search witness log
        if self.witness_log_path.exists():
            try:
                with open(self.witness_log_path, "r", encoding="utf-8") as f:
                    for line in f:
                        try:
                            data = json.loads(line.strip())
                            if data.get("receipt_id") == receipt_id:
                                return WitnessRecord.from_dict(data)
                        except (json.JSONDecodeError, KeyError):
                            continue
            except Exception as e:
                logger.warning(f"[constitutional_witness] Failed to search witness log: {e}")

        # Try Mem0 if available
        if self.enable_mem0:
            try:
                from receipts.mem0_ingest import search_receipts
                mem0_results = search_receipts(
                    query=f"receipt_id:{receipt_id}",
                    space="witness:*",
                    limit=1
                )
                for item in mem0_results.get("results", []):
                    payload = item.get("metadata", {}).get("payload", {})
                    if payload and payload.get("receipt_id") == receipt_id:
                        return WitnessRecord(
                            witness_id=item.get("id", "unknown"),
                            receipt_id=receipt_id,
                            receipt_type=payload.get("receipt_type", "unknown"),
                            receipt_hash=payload.get("receipt_hash", ""),
                            space=payload.get("space", "default"),
                            case_id=payload.get("case_id"),
                            witnessed_at=payload.get("witnessed_at", ""),
                            payload=payload,
                        )
            except Exception as e:
                logger.debug(f"[constitutional_witness] Mem0 search failed: {e}")

        return None

    def get_lineage(
        self,
        receipt_id: str,
        depth: int = 5,
        include_self: bool = True,
    ) -> List[WitnessRecord]:
        """
        Expand lineage: get a receipt and its ancestors.

        Follows parent_ids recursively up to `depth` levels.
        This provides the causal chain of decisions leading to the current receipt.

        Args:
            receipt_id: Starting receipt ID
            depth: How many parent levels to traverse (default: 5)
            include_self: Whether to include the starting receipt (default: True)

        Returns:
            List of WitnessRecords in causal order (oldest first)
        """
        lineage: List[WitnessRecord] = []
        seen: Set[str] = set()
        queue = [receipt_id]
        current_depth = 0

        while queue and current_depth <= depth:
            next_queue = []

            for rid in queue:
                if rid in seen:
                    continue
                seen.add(rid)

                record = self.get_receipt_by_id(rid)
                if record is None:
                    continue

                # Skip self if not requested
                if rid == receipt_id and not include_self:
                    # Still traverse parents
                    pass
                else:
                    lineage.append(record)

                # Queue parents for next level
                parent_ids = record.payload.get("parent_ids", [])
                for parent_id in parent_ids:
                    if parent_id not in seen:
                        next_queue.append(parent_id)

            queue = next_queue
            current_depth += 1

        # Return in causal order (oldest ancestors first)
        return list(reversed(lineage))

    def get_descendants(
        self,
        receipt_id: str,
        depth: int = 3,
    ) -> List[WitnessRecord]:
        """
        Get receipts that descend from a given receipt.

        Searches for receipts that have this receipt_id in their parent_ids.

        Args:
            receipt_id: The ancestor receipt ID
            depth: How many child levels to traverse (default: 3)

        Returns:
            List of descendant WitnessRecords
        """
        descendants: List[WitnessRecord] = []
        current_ids = {receipt_id}
        seen: Set[str] = set()

        for _ in range(depth):
            if not current_ids:
                break

            next_ids: Set[str] = set()

            # Scan all witnesses for children
            for record in self._recent_witnesses:
                if record.receipt_id in seen:
                    continue

                parent_ids = set(record.payload.get("parent_ids", []))
                if parent_ids & current_ids:  # Has a parent in current set
                    descendants.append(record)
                    next_ids.add(record.receipt_id)
                    seen.add(record.receipt_id)

            current_ids = next_ids

        return descendants

    # =========================================================================
    # MEM0 SEMANTIC SEARCH
    # =========================================================================

    def search_similar(
        self,
        query: str,
        limit: int = 10,
        space: Optional[str] = None,
        receipt_types: Optional[Set[str]] = None,
    ) -> List[WitnessRecord]:
        """
        Semantic search for similar receipts via Mem0.

        Uses vector embeddings to find receipts with similar content/context.
        Falls back to keyword search in witness log if Mem0 unavailable.

        Args:
            query: Natural language query or receipt content
            limit: Maximum results to return
            space: Optional Mem0 space filter
            receipt_types: Optional filter by receipt type

        Returns:
            List of semantically similar WitnessRecords
        """
        results: List[WitnessRecord] = []

        # Try Mem0 first
        if self.enable_mem0:
            try:
                from receipts.mem0_ingest import search_receipts

                mem0_results = search_receipts(
                    query=query,
                    space=space or "witness:*",
                    limit=limit * 2,  # Over-fetch for filtering
                )

                for item in mem0_results.get("results", []):
                    payload = item.get("metadata", {}).get("payload", {})
                    if not payload:
                        continue

                    # Type filter
                    rtype = payload.get("receipt_type", "unknown")
                    if receipt_types and rtype not in receipt_types:
                        continue

                    record = WitnessRecord(
                        witness_id=item.get("id", "unknown"),
                        receipt_id=payload.get("receipt_id", "unknown"),
                        receipt_type=rtype,
                        receipt_hash=payload.get("receipt_hash", ""),
                        space=payload.get("space", "default"),
                        case_id=payload.get("case_id"),
                        witnessed_at=payload.get("witnessed_at", ""),
                        payload=payload,
                    )
                    results.append(record)

                    if len(results) >= limit:
                        break

                if results:
                    return results

            except Exception as e:
                logger.debug(f"[constitutional_witness] Mem0 search failed: {e}")

        # Fallback: keyword search in recent witnesses
        query_lower = query.lower()
        scored: List[Tuple[float, WitnessRecord]] = []

        for record in self._recent_witnesses:
            if receipt_types and record.receipt_type not in receipt_types:
                continue

            # Simple keyword scoring
            text = json.dumps(record.payload, default=str).lower()
            score = sum(1 for word in query_lower.split() if word in text)

            if score > 0:
                scored.append((score, record))

        # Sort by score descending
        scored.sort(key=lambda x: -x[0])
        return [r for _, r in scored[:limit]]

    def get_context_for_case(
        self,
        case_id: str,
        query: Optional[str] = None,
        max_history: int = 10,
        max_similar: int = 5,
    ) -> Dict[str, Any]:
        """
        Get full context for a case: history + semantically similar receipts.

        This is the primary API for the council to retrieve episodic context.

        Args:
            case_id: The case ID
            query: Optional semantic query for similar receipts
            max_history: Maximum case history receipts
            max_similar: Maximum semantically similar receipts

        Returns:
            Dict with:
                - history: List[WitnessRecord] - chronological case history
                - similar: List[WitnessRecord] - semantically similar (if query provided)
                - lineage: List[WitnessRecord] - lineage of most recent receipt
                - stats: Dict with counts and timestamps
        """
        context: Dict[str, Any] = {
            "case_id": case_id,
            "history": [],
            "similar": [],
            "lineage": [],
            "stats": {
                "history_count": 0,
                "similar_count": 0,
                "lineage_depth": 0,
                "earliest_ts": None,
                "latest_ts": None,
            },
        }

        # Get case history
        history = self.get_case_history(case_id, limit=max_history)
        context["history"] = history
        context["stats"]["history_count"] = len(history)

        if history:
            # Get lineage of most recent receipt
            latest = history[0]  # Most recent first
            lineage = self.get_lineage(latest.receipt_id, depth=5, include_self=False)
            context["lineage"] = lineage
            context["stats"]["lineage_depth"] = len(lineage)

            # Compute timestamps
            timestamps = [h.witnessed_at for h in history if h.witnessed_at]
            if timestamps:
                context["stats"]["earliest_ts"] = min(timestamps)
                context["stats"]["latest_ts"] = max(timestamps)

        # Get semantically similar if query provided
        if query:
            similar = self.search_similar(
                query=query,
                limit=max_similar,
                receipt_types={"SuggestionReceipt", "GenesisReceipt"},
            )
            # Filter out ones already in history
            history_ids = {h.receipt_id for h in history}
            similar = [s for s in similar if s.receipt_id not in history_ids]
            context["similar"] = similar[:max_similar]
            context["stats"]["similar_count"] = len(context["similar"])

        return context

    async def self_audit(
        self,
        hours: int = 24,
        sample_size: int = 100,
        run_drift_check: bool = True,
    ) -> SelfAuditResult:
        """
        Run self-audit: verify hashes and optionally check for drift.

        Args:
            hours: How many hours back to audit
            sample_size: Number of receipts to sample
            run_drift_check: Whether to run DriftSentinel checks

        Returns:
            SelfAuditResult with verification statistics
        """
        audit_id = _generate_id("audit")
        started_at = datetime.utcnow().isoformat() + "Z"

        # Get samples
        samples = self.sample_recent(hours=hours, n=sample_size)

        verified = 0
        failed = 0
        drift_detected = 0
        errors: List[Dict[str, Any]] = []
        by_type: Dict[str, Dict[str, int]] = {}

        # Get DriftSentinel if enabled
        sentinel = None
        if run_drift_check and self.enable_drift_check:
            try:
                from engine.drift_sentinel import get_drift_sentinel
                sentinel = get_drift_sentinel()
            except ImportError:
                logger.debug("[constitutional_witness] DriftSentinel not available")

        for record in samples:
            receipt = record.payload
            receipt_type = record.receipt_type

            # Initialize type stats
            if receipt_type not in by_type:
                by_type[receipt_type] = {"total": 0, "verified": 0, "failed": 0, "drift": 0}
            by_type[receipt_type]["total"] += 1

            try:
                # Verify hash
                if self.verify(receipt):
                    verified += 1
                    by_type[receipt_type]["verified"] += 1
                    record.verification_status = "verified"
                else:
                    failed += 1
                    by_type[receipt_type]["failed"] += 1
                    record.verification_status = "failed"
                    errors.append({
                        "witness_id": record.witness_id,
                        "receipt_id": record.receipt_id,
                        "reason": "Hash mismatch"
                    })

                # Run drift check if available
                if sentinel and "verdict" in receipt:
                    try:
                        result = await sentinel.verify_verdict(
                            event=receipt,
                            verdict={
                                "decision": receipt.get("verdict", "unknown"),
                                "reasons": receipt.get("reasons", [])
                            },
                            coherence=receipt.get("coherence"),
                        )
                        if result.hallucination_detected:
                            drift_detected += 1
                            by_type[receipt_type]["drift"] += 1
                            errors.append({
                                "witness_id": record.witness_id,
                                "receipt_id": record.receipt_id,
                                "reason": f"Drift detected: {result.reasoning}"
                            })
                    except Exception as e:
                        logger.debug(f"[constitutional_witness] Drift check failed: {e}")

            except Exception as e:
                failed += 1
                by_type[receipt_type]["failed"] += 1
                errors.append({
                    "witness_id": record.witness_id,
                    "receipt_id": record.receipt_id,
                    "reason": str(e)
                })

        completed_at = datetime.utcnow().isoformat() + "Z"

        result = SelfAuditResult(
            audit_id=audit_id,
            started_at=started_at,
            completed_at=completed_at,
            hours_covered=hours,
            total_sampled=len(samples),
            verified=verified,
            failed=failed,
            drift_detected=drift_detected,
            errors=errors[:50],  # Limit error list
            stats={
                "by_type": by_type,
                "verification_rate": verified / len(samples) if samples else 0.0,
                "drift_rate": drift_detected / len(samples) if samples else 0.0,
            }
        )

        # Emit SelfAuditReceipt
        self._emit_audit_receipt(result)

        return result

    def _emit_audit_receipt(self, result: SelfAuditResult) -> None:
        """Emit a receipt for the self-audit run."""
        try:
            from receipts.writer import write_receipt

            receipt = {
                "receipt_type": "SelfAuditReceipt",
                "receipt_id": result.audit_id,
                "domain": "governance",
                "audit_result": result.to_dict(),
                "transaction_time": result.completed_at,
            }
            write_receipt(receipt)
        except Exception as e:
            logger.debug(f"[constitutional_witness] Failed to emit audit receipt: {e}")

    def get_stats(self) -> Dict[str, Any]:
        """Get ConstitutionalWitness statistics."""
        return {
            "recent_witnesses": len(self._recent_witnesses),
            "cases_tracked": len(self._case_index),
            "witness_log_exists": self.witness_log_path.exists(),
            "receipts_dir": str(self.receipts_dir),
            "mem0_enabled": self.enable_mem0,
            "drift_check_enabled": self.enable_drift_check,
        }


# Singleton instance
_constitutional_witness: Optional[ConstitutionalWitness] = None


def get_constitutional_witness(
    receipts_dir: Optional[Path] = None,
    **kwargs
) -> ConstitutionalWitness:
    """Get singleton ConstitutionalWitness instance."""
    global _constitutional_witness
    if _constitutional_witness is None:
        _constitutional_witness = ConstitutionalWitness(receipts_dir=receipts_dir, **kwargs)
    return _constitutional_witness


def witness_receipt(receipt: ReceiptDict) -> str:
    """Convenience function to witness a receipt."""
    return get_constitutional_witness().witness(receipt)


__all__ = [
    "ConstitutionalWitness",
    "WitnessRecord",
    "SelfAuditResult",
    "get_constitutional_witness",
    "witness_receipt",
]
