"""DeepSigmaMemory — LangChain memory backed by the Σ OVERWATCH Memory Graph.

Saves each conversation turn as a mini Decision Episode in the Memory Graph
and retrieves relevant past decisions via the IRIS RECALL query engine.

Usage::

    from langchain_deepsigma import DeepSigmaMemory
    from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

    memory = DeepSigmaMemory(top_k=5)

    prompt = ChatPromptTemplate.from_messages([
        ("system", "You are a helpful assistant."),
        MessagesPlaceholder(variable_name="decision_history"),
        ("human", "{input}"),
    ])

    chain = prompt | llm
    # Manually load + save context (compatible with LCEL)
    history = memory.load_memory_variables({"input": user_input})
    response = chain.invoke({"input": user_input, **history})
    memory.save_context({"input": user_input}, {"output": response.content})
"""
from __future__ import annotations

import abc
import uuid
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from coherence_ops import MemoryGraph


def _now() -> str:
    return datetime.now(timezone.utc).isoformat()


class BaseMemory(abc.ABC):
    """Minimal LangChain-compatible memory interface.

    Compatible with ``langchain_core`` >=0.1 (memory was removed from core
    in favour of ``langchain`` package).  This shim satisfies the same
    duck-type contract so ``DeepSigmaMemory`` works wherever LangChain
    memory is expected.
    """

    @property
    @abc.abstractmethod
    def memory_variables(self) -> List[str]: ...

    @abc.abstractmethod
    def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]: ...

    @abc.abstractmethod
    def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, Any]) -> None: ...

    @abc.abstractmethod
    def clear(self) -> None: ...


class DeepSigmaMemory(BaseMemory):
    """LangChain memory provider backed by the DeepSigma Memory Graph.

    Each ``save_context`` call creates a mini Decision Episode in the Memory
    Graph.  ``load_memory_variables`` uses the IRIS RECALL query to retrieve
    a formatted decision history string that can be injected into prompts.

    Attributes:
        memory_key: The key used in the dict returned by ``load_memory_variables``.
            Defaults to ``"decision_history"``.
        top_k: Maximum number of past episodes to surface in the context string.
            Defaults to 5.
        mg: The underlying MemoryGraph instance. Can be shared with a
            ``DeepSigmaCallbackHandler`` to unify the provenance graph.
    """

    def __init__(
        self,
        memory_key: str = "decision_history",
        top_k: int = 5,
        mg: Optional[MemoryGraph] = None,
    ) -> None:
        self.memory_key = memory_key
        self.top_k = top_k
        self.mg = mg or MemoryGraph()
        # Local list of raw episode dicts for formatting history
        # (MG nodes store only summary properties, not full episode data)
        self._episodes: List[Dict[str, Any]] = []

    @property
    def memory_variables(self) -> List[str]:
        """Keys this memory contributes to the prompt context."""
        return [self.memory_key]

    def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, Any]) -> None:
        """Record an input/output pair as a mini Decision Episode in the Memory Graph."""
        ep_id = f"ep-mem-{uuid.uuid4().hex[:12]}"
        input_text = str(inputs.get("input", inputs))[:500]
        output_text = str(outputs.get("output", outputs))[:500]

        episode = {
            "episodeId": ep_id,
            "decisionType": "LangChainMemory",
            "startedAt": _now(),
            "endedAt": _now(),
            "actor": {"type": "memory", "id": "DeepSigmaMemory"},
            "context": {"inputs": input_text, "evidenceRefs": []},
            "truth": {
                "claims": [
                    {
                        "claimId": f"CLAIM-MEM-{uuid.uuid4().hex[:8].upper()}",
                        "statement": input_text,
                        "confidence": 0.9,
                        "statusLight": "GREEN",
                        "capturedAt": _now(),
                        "ttlMs": 3600000,
                        "role": "user_input",
                    }
                ]
            },
            "actions": [],
            "degrade": {"step": "ideal"},
            "outcome": {"code": "success", "detail": output_text},
            "seal": {"sealedAt": _now(), "version": 1},
        }

        self.mg.add_episode(episode)
        # Keep raw episode for history formatting
        self._episodes.append({
            "episodeId": ep_id,
            "decisionType": "LangChainMemory",
            "startedAt": episode["startedAt"],
            "outcome": episode["outcome"]["code"],
            "inputs": input_text,
            "output": output_text,
        })

        claim = {
            "claimId": f"CLAIM-OUT-{uuid.uuid4().hex[:8].upper()}",
            "statement": output_text,
            "confidence": {"score": 0.9},
            "statusLight": "GREEN",
            "timestampCreated": _now(),
            "role": "assistant_output",
        }
        self.mg.add_claim(claim, episode_id=ep_id)

    def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]:  # noqa: ARG002
        """Return relevant past decisions as a formatted string for prompt injection.

        Formats the top_k most recent episodes into a human-readable decision
        history block ready for prompt injection.
        """
        # Surface from local episode store (most recent first, up to top_k)
        recent = self._episodes[-self.top_k:][::-1]

        if not recent:
            return {self.memory_key: ""}

        total = len(self._episodes)
        lines = [f"[Decision History — {len(recent)} of {total} episode(s) in memory]"]
        for ep in recent:
            started = ep.get("startedAt", "")[:19]
            outcome = ep.get("outcome", "unknown")
            inp = ep.get("inputs", "")[:120]
            out = ep.get("output", "")[:120]
            lines.append(
                f"\n• [{started}] LangChainMemory → {outcome}\n"
                f"  Input:  {inp}\n"
                f"  Output: {out}"
            )

        return {self.memory_key: "\n".join(lines)}

    def clear(self) -> None:
        """Reset the Memory Graph and episode history to a fresh empty state."""
        self.mg = MemoryGraph()
        self._episodes = []
