"""
Tri-Temporal Proof Chain

Implements E/O/R timestamps + hash chaining for tamper-evident receipts.

Every entry:
  h_i = H(E ‖ O ‖ R ‖ payload_hash ‖ prev_hash)

Where:
  E = Event time (claimed)
  O = Observation time (when Loom witnessed)
  R = Receipt time (when committed)
"""

from __future__ import annotations

import hashlib
import logging
from dataclasses import dataclass, field
from datetime import datetime, timezone, timedelta
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)


@dataclass
class ProofChainEntry:
    """A single entry in the proof chain."""

    # Identity
    id: str                     # h_i (computed)
    prev: Optional[str]         # h_{i-1}
    stream_key: str             # e.g., "clinical/prior_auth/CASE-9271"

    # Tri-temporal
    event_time: datetime        # E - claimed event time
    observed_at: datetime       # O - when Loom witnessed
    receipt_at: datetime        # R - when committed

    # Payload
    payload_hash: str           # H(payload)
    payload_type: str           # Receipt type

    # Coherence
    delta_c: Dict[str, Any] = field(default_factory=dict)

    # Signature (added after creation)
    signature: Optional[Dict[str, Any]] = None

    @classmethod
    def create(
        cls,
        stream_key: str,
        payload: Dict[str, Any],
        event_time: Optional[datetime] = None,
        prev_hash: Optional[str] = None,
    ) -> "ProofChainEntry":
        """Create a new proof chain entry."""
        from receipts.canonicalize import to_jcs_bytes

        now = datetime.now(timezone.utc)

        # Compute payload hash
        payload_bytes = to_jcs_bytes(payload)
        payload_hash = "sha256:" + hashlib.sha256(payload_bytes).hexdigest()

        entry = cls(
            id="",  # Computed below
            prev=prev_hash,
            stream_key=stream_key,
            event_time=event_time or now,
            observed_at=now,
            receipt_at=now,
            payload_hash=payload_hash,
            payload_type=payload.get("receipt_type", "unknown"),
        )

        # Compute entry hash
        entry.id = entry._compute_hash()

        return entry

    def _compute_hash(self) -> str:
        """Compute h_i = H(E ‖ O ‖ R ‖ payload_hash ‖ prev_hash)"""
        components = [
            self.event_time.isoformat(),
            self.observed_at.isoformat(),
            self.receipt_at.isoformat(),
            self.payload_hash,
            self.prev or "genesis",
        ]
        combined = "‖".join(components)
        return "sha256:" + hashlib.sha256(combined.encode()).hexdigest()

    def to_dict(self) -> Dict[str, Any]:
        """Serialize to dict."""
        return {
            "id": self.id,
            "prev": self.prev,
            "stream_key": self.stream_key,
            "event_time": self.event_time.isoformat(),
            "observed_at": self.observed_at.isoformat(),
            "receipt_at": self.receipt_at.isoformat(),
            "payload_hash": self.payload_hash,
            "payload_type": self.payload_type,
            "deltaC": self.delta_c,
            "sign": self.signature,
        }


class ProofChain:
    """
    Manages proof chains per stream_key.

    Enforces monotonicity and computes ΔC scores.
    """

    RETROACTIVE_TOLERANCE_SECONDS = 300  # 5 minutes

    def __init__(self):
        self._heads: Dict[str, ProofChainEntry] = {}  # stream_key -> latest entry
        self._entries: Dict[str, ProofChainEntry] = {}  # id -> entry

    def append(
        self,
        stream_key: str,
        payload: Dict[str, Any],
        event_time: Optional[datetime] = None,
    ) -> ProofChainEntry:
        """
        Append a new entry to the chain.

        Returns the entry with computed hash and ΔC flags.
        """
        prev_entry = self._heads.get(stream_key)
        prev_hash = prev_entry.id if prev_entry else None

        # Create entry
        entry = ProofChainEntry.create(
            stream_key=stream_key,
            payload=payload,
            event_time=event_time,
            prev_hash=prev_hash,
        )

        # Check monotonicity
        if prev_entry:
            entry.delta_c = self._check_monotonicity(entry, prev_entry)
        else:
            entry.delta_c = {"monotone_ok": True, "flags": ["genesis"], "score": 1.0}

        # Update chain
        self._heads[stream_key] = entry
        self._entries[entry.id] = entry

        logger.debug(
            "Proof chain append: stream=%s, id=%s, deltaC=%s",
            stream_key,
            entry.id[:16],
            entry.delta_c,
        )

        return entry

    def _check_monotonicity(
        self,
        entry: ProofChainEntry,
        prev: ProofChainEntry,
    ) -> Dict[str, Any]:
        """Check tri-temporal monotonicity."""
        flags = []
        score = 1.0

        # O must be >= previous O
        if entry.observed_at < prev.observed_at:
            flags.append("observation_time_travel")
            score -= 0.2
            logger.warning(
                "Observation time travel: stream=%s, O=%s < prev_O=%s",
                entry.stream_key,
                entry.observed_at,
                prev.observed_at,
            )

        # R must be >= previous R
        if entry.receipt_at < prev.receipt_at:
            flags.append("receipt_time_travel")
            score -= 0.2
            logger.warning(
                "Receipt time travel: stream=%s, R=%s < prev_R=%s",
                entry.stream_key,
                entry.receipt_at,
                prev.receipt_at,
            )

        # E retroactive beyond tolerance
        tolerance = timedelta(seconds=self.RETROACTIVE_TOLERANCE_SECONDS)
        if entry.event_time < prev.event_time - tolerance:
            flags.append("retroactive_event")
            score -= 0.1
            logger.info(
                "Retroactive event: stream=%s, E=%s < prev_E=%s - %ss",
                entry.stream_key,
                entry.event_time,
                prev.event_time,
                self.RETROACTIVE_TOLERANCE_SECONDS,
            )

        if not flags:
            flags.append("monotone_ok")

        return {
            "monotone_ok": "monotone_ok" in flags,
            "flags": flags,
            "score": max(0.0, score),
        }

    def get_entry(self, entry_id: str) -> Optional[ProofChainEntry]:
        """Get entry by ID."""
        return self._entries.get(entry_id)

    def get_head(self, stream_key: str) -> Optional[ProofChainEntry]:
        """Get the latest entry for a stream."""
        return self._heads.get(stream_key)

    def get_chain(self, stream_key: str, limit: int = 100) -> List[ProofChainEntry]:
        """Get entries in a chain, newest first."""
        result = []
        entry = self._heads.get(stream_key)

        while entry and len(result) < limit:
            result.append(entry)
            entry = self._entries.get(entry.prev) if entry.prev else None

        return result

    def verify_chain(self, stream_key: str) -> Dict[str, Any]:
        """Verify integrity of a chain."""
        chain = self.get_chain(stream_key, limit=10000)

        if not chain:
            return {"valid": True, "reason": "empty_chain", "chain_length": 0}

        # Verify each link
        for i, entry in enumerate(chain[:-1]):
            next_entry = chain[i + 1]

            # Check prev pointer
            if entry.prev != next_entry.id:
                return {
                    "valid": False,
                    "reason": "broken_link",
                    "entry_id": entry.id,
                    "expected_prev": next_entry.id,
                    "actual_prev": entry.prev,
                }

            # Verify hash
            computed = entry._compute_hash()
            if computed != entry.id:
                return {
                    "valid": False,
                    "reason": "hash_mismatch",
                    "entry_id": entry.id,
                    "computed": computed,
                }

        return {
            "valid": True,
            "chain_length": len(chain),
            "head": chain[0].id if chain else None,
            "tail": chain[-1].id if chain else None,
        }

    def list_streams(self) -> List[str]:
        """List all stream keys."""
        return list(self._heads.keys())

    def stats(self) -> Dict[str, Any]:
        """Get chain statistics."""
        return {
            "stream_count": len(self._heads),
            "entry_count": len(self._entries),
            "streams": {
                key: {
                    "head": entry.id[:16] + "...",
                    "type": entry.payload_type,
                }
                for key, entry in self._heads.items()
            },
        }


# Global proof chain instance
_proof_chain: Optional[ProofChain] = None


def get_proof_chain() -> ProofChain:
    """Get the global proof chain instance."""
    global _proof_chain
    if _proof_chain is None:
        _proof_chain = ProofChain()
    return _proof_chain


def reset_proof_chain() -> None:
    """Reset the global proof chain (for testing)."""
    global _proof_chain
    _proof_chain = None


__all__ = [
    "ProofChainEntry",
    "ProofChain",
    "get_proof_chain",
    "reset_proof_chain",
]
