#!/usr/bin/env python3
"""
CDR-Echo Bridge: Persist CDR state summaries to crash-resilient storage.

What this persists (safe, minimal):
- cdr_state_hash (full hash)
- step_count
- last N checkpoint IDs (names only)
- timestamp + component tag

What this does NOT persist (privacy/safety):
- user prompts
- model outputs
- raw absorbed messages

This gives you a "cryptographic flight recorder" for streamed inference:
- Cross-session continuity after crash/reboot
- Tamper-evidence (hash chain verification)
- Searchable execution fingerprint

Usage:
    from echo_memory import EchoMemory
    from helix_cdc.memory.cdr_echo_bridge import CDREchoBridge, build_record

    mem = EchoMemory()
    bridge = CDREchoBridge(mem)

    # After CDR operations:
    bridge.save("cdr:basin:last", build_record(
        component="basin",
        state_hash=mgr.cdr_state_hash(),
        step_count=mgr.cdr_state.step_count,
        checkpoints=list(mgr.checkpoints.keys())[-10:],
    ))

    # After restart:
    last = bridge.load("cdr:basin:last")
    print(f"Last known CDR state: step={last['step_count']}, hash={last['state_hash'][:16]}...")
"""

from __future__ import annotations

import json
import time
from dataclasses import dataclass, asdict
from typing import Any, Dict, List, Optional, TYPE_CHECKING

if TYPE_CHECKING:
    # Avoid circular imports - EchoMemory duck-typed
    pass


@dataclass
class CDRPersistRecord:
    """
    Minimal CDR state summary for persistence.

    Contains ONLY metadata - no prompts, outputs, or raw messages.
    """
    ts: float                    # Unix timestamp
    component: str               # "basin", "qpu", "cdna", etc.
    state_hash: str              # Full CDR sponge hash (64 hex chars)
    step_count: int              # CDR absorb count
    checkpoints: List[str]       # Last N checkpoint IDs (names only)
    extra: Dict[str, Any]        # Optional extra metadata

    def to_dict(self) -> Dict[str, Any]:
        return asdict(self)

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> "CDRPersistRecord":
        return cls(
            ts=d.get("ts", 0.0),
            component=d.get("component", "unknown"),
            state_hash=d.get("state_hash", ""),
            step_count=d.get("step_count", 0),
            checkpoints=d.get("checkpoints", []),
            extra=d.get("extra", {}),
        )


def build_record(
    component: str,
    state_hash: str,
    step_count: int,
    checkpoints: List[str],
    **extra,
) -> CDRPersistRecord:
    """Build a CDR persist record with current timestamp."""
    return CDRPersistRecord(
        ts=time.time(),
        component=component,
        state_hash=state_hash,
        step_count=step_count,
        checkpoints=checkpoints,
        extra=dict(extra),
    )


class CDREchoBridge:
    """
    Adapter bridge between CDR state and persistent memory.

    Duck-types to common memory APIs:
    - checkpoint(key, obj) / recall(key)  -- EchoMemory
    - put(key, json) / get(key)           -- generic KV
    - set(key, obj) / get(key)            -- Redis-style

    Persists ONLY summary metadata (hash/step_count/checkpoint IDs).
    Does NOT persist prompts, outputs, or raw absorbed messages.
    """

    def __init__(self, echo_mem: Any):
        """
        Initialize bridge with a memory backend.

        Args:
            echo_mem: Memory object with checkpoint/recall or put/get methods
        """
        self.mem = echo_mem
        self._api = self._detect_api()

    def _detect_api(self) -> str:
        """Detect which API the memory backend supports."""
        if hasattr(self.mem, "checkpoint") and hasattr(self.mem, "recall"):
            return "checkpoint"
        if hasattr(self.mem, "put") and hasattr(self.mem, "get"):
            return "put"
        if hasattr(self.mem, "set") and hasattr(self.mem, "get"):
            return "set"
        raise RuntimeError(
            "Echo memory object has no supported API. "
            "Expected one of: checkpoint/recall, put/get, set/get"
        )

    def save(self, key: str, record: CDRPersistRecord) -> bool:
        """
        Save CDR state summary to persistent storage.

        Args:
            key: Storage key (e.g., "cdr:basin:last")
            record: CDR persist record

        Returns:
            True on success
        """
        payload = record.to_dict()

        if self._api == "checkpoint":
            self.mem.checkpoint(key, payload)
            return True

        if self._api == "put":
            self.mem.put(key, json.dumps(payload, sort_keys=True))
            return True

        if self._api == "set":
            self.mem.set(key, payload)
            return True

        return False

    def load(self, key: str) -> Optional[Dict[str, Any]]:
        """
        Load CDR state summary from persistent storage.

        Args:
            key: Storage key

        Returns:
            Dict with ts, component, state_hash, step_count, checkpoints, extra
            or None if not found
        """
        if self._api == "checkpoint":
            return self.mem.recall(key)

        if self._api in ("put", "set"):
            v = self.mem.get(key)
            if v is None:
                return None
            if isinstance(v, str):
                try:
                    return json.loads(v)
                except Exception:
                    return {"raw": v}
            return v

        return None

    def load_as_record(self, key: str) -> Optional[CDRPersistRecord]:
        """Load and parse as CDRPersistRecord."""
        d = self.load(key)
        if d is None:
            return None
        return CDRPersistRecord.from_dict(d)

    def save_history(self, key_prefix: str, record: CDRPersistRecord, max_history: int = 100) -> bool:
        """
        Save to history list (append-style, keeps last N).

        Args:
            key_prefix: Key prefix (e.g., "cdr:basin:history")
            record: CDR persist record
            max_history: Max records to keep

        Returns:
            True on success
        """
        history_key = f"{key_prefix}:list"
        existing = self.load(history_key) or []

        if not isinstance(existing, list):
            existing = []

        # Append new record
        existing.append(record.to_dict())

        # Trim to max_history
        if len(existing) > max_history:
            existing = existing[-max_history:]

        # Save back
        if self._api == "checkpoint":
            self.mem.checkpoint(history_key, existing)
            return True

        if self._api == "put":
            self.mem.put(history_key, json.dumps(existing, sort_keys=True))
            return True

        if self._api == "set":
            self.mem.set(history_key, existing)
            return True

        return False

    def get_history(self, key_prefix: str, limit: int = 10) -> List[Dict[str, Any]]:
        """
        Get recent history entries.

        Args:
            key_prefix: Key prefix used in save_history
            limit: Max entries to return

        Returns:
            List of dicts (most recent last)
        """
        history_key = f"{key_prefix}:list"
        existing = self.load(history_key) or []

        if not isinstance(existing, list):
            return []

        return existing[-limit:]


# Convenience function for one-shot persistence
def persist_cdr_summary(
    echo_mem: Any,
    component: str,
    state_hash: str,
    step_count: int,
    checkpoints: List[str],
    key: Optional[str] = None,
    **extra,
) -> bool:
    """
    One-shot convenience function to persist CDR state summary.

    Args:
        echo_mem: Memory backend with checkpoint/recall
        component: Component name ("basin", "qpu", "cdna", etc.)
        state_hash: CDR sponge state hash
        step_count: CDR step count
        checkpoints: List of checkpoint IDs
        key: Optional custom key (default: "cdr:{component}:last")
        **extra: Additional metadata

    Returns:
        True on success
    """
    bridge = CDREchoBridge(echo_mem)
    record = build_record(
        component=component,
        state_hash=state_hash,
        step_count=step_count,
        checkpoints=checkpoints,
        **extra,
    )
    key = key or f"cdr:{component}:last"
    return bridge.save(key, record)
