"""
narrator.py
-----------
Narrative intelligence for the HIEROMEM framework (extended version).

This version includes:
- Real-time narrative generation from MemoryEvents
- Adaptive summaries over recent cognition
- Persistent narrative memory buffer ("meta-memory")
- Re-summarization of its own past stories (reflective awareness)

This enables the system to develop a sense of "continuity of thought"—
it remembers what it once narrated.

Author: Sai Rishi Kiran Mannava
"""

from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Callable, Optional

from hieromem_core.events import EventType, MemoryEvent


# --------------------------------------------------------------------------- #
# Config
# --------------------------------------------------------------------------- #
@dataclass
class NarratorConfig:
    verbose: bool = True
    history_limit: int = 200
    timestamp_format: str = "%H:%M:%S"
    prefix: str = "🧠"
    on_narration: Optional[Callable[[str], None]] = None
    aggregation_window_sec: int = 60
    auto_summarize: bool = True
    meta_memory_limit: int = 10  # number of stored narrative summaries


# --------------------------------------------------------------------------- #
# Narrator
# --------------------------------------------------------------------------- #
class Narrator:
    """Self-reflective storyteller that remembers its own narratives."""

    def __init__(self, config: NarratorConfig | None = None):
        self.config = config or NarratorConfig()
        self.history: list[str] = []  # real-time narrations
        # temporary event aggregation
        self.event_buffer: list[MemoryEvent] = []
        self.meta_memory: list[dict[str, str]] = []  # reflective storage
        self.last_summary_time: Optional[datetime] = None

    # ------------------------------------------------------------------ #
    # Observation Entry Point
    # ------------------------------------------------------------------ #
    def observe(self, event: MemoryEvent) -> None:
        """Accept a MemoryEvent and generate both micro- and macro-narratives."""
        self.event_buffer.append(event)
        line = self._compose_line(event)
        self._record(line)

        if self.config.verbose:
            print(line)
        if self.config.on_narration:
            try:
                self.config.on_narration(line)
            except Exception as e:
                print(f"[Narrator] Callback error: {e}")

        # Periodically generate summary and save to meta-memory
        if self.config.auto_summarize:
            now = datetime.now()
            if (self.last_summary_time is None or now - self.last_summary_time >
                    timedelta(seconds=self.config.aggregation_window_sec)):
                summary = self.summarize_recent()
                if summary:
                    self._record(summary)
                    self._store_in_meta_memory(summary)
                    if self.config.verbose:
                        print(summary)
                self.last_summary_time = now
                self.event_buffer.clear()

    # ------------------------------------------------------------------ #
    # Composition
    # ------------------------------------------------------------------ #
    def _compose_line(self, event: MemoryEvent) -> str:
        ts = event.timestamp.strftime(self.config.timestamp_format)
        msg = event.message
        src = event.source or "Memory"

        if event.event_type == EventType.INSERT:
            return f"{self.config.prefix} [{ts}] ({src}) Recorded a new experience — {msg}."
        elif event.event_type == EventType.RETRIEVE:
            count = event.details.get("count") or len(
                event.details.get("indices", [])) or "some"
            return f"{self.config.prefix} [{ts}] ({src}) Retrieved {count} related memories."
        elif event.event_type == EventType.SUMMARISE:
            merged = event.details.get("merged", [])
            return f"{self.config.prefix} [{ts}] ({src}) Summarised {len(merged)} experiences into a composite."
        elif event.event_type == EventType.FORGET:
            pruned = len(event.details.get("pruned", []))
            return f"{self.config.prefix} [{ts}] ({src}) Released {pruned} outdated memories."
        elif event.event_type == EventType.GOVERNANCE:
            risk = event.details.get("risk_level", "stable")
            return f"{self.config.prefix} [{ts}] ({src}) Governance check: parameters remain {risk}."
        elif event.event_type == EventType.SAVE:
            return f"{self.config.prefix} [{ts}] ({src}) State persisted to long-term storage."
        elif event.event_type == EventType.LOAD:
            count = event.details.get("count", 0)
            return f"{self.config.prefix} [{ts}] ({src}) Reloaded {count} past experiences."
        elif event.event_type == EventType.CLEAR:
            return f"{self.config.prefix} [{ts}] ({src}) Cleared all active memories."
        elif event.event_type == EventType.SYSTEM:
            return f"{self.config.prefix} [{ts}] ({src}) System event — {msg}."
        else:
            return f"{self.config.prefix} [{ts}] ({src}) {msg}"

    # ------------------------------------------------------------------ #
    # Summarization of recent activity
    # ------------------------------------------------------------------ #
    def summarize_recent(self) -> Optional[str]:
        """Aggregate recent events into a higher-level summary."""
        if not self.event_buffer:
            return None

        counts: dict[EventType, int] = {}
        for e in self.event_buffer:
            counts[e.event_type] = counts.get(e.event_type, 0) + 1

        ts = datetime.now().strftime(self.config.timestamp_format)
        fragments = [
            f"{c} {t.value}{'' if c == 1 else 's'}" for t,
            c in counts.items()]
        text = ", ".join(fragments)
        return f"{self.config.prefix} [{ts}] 🧩 Reflective summary: {text} observed recently."

    # ------------------------------------------------------------------ #
    # Meta-memory management
    # ------------------------------------------------------------------ #
    def _store_in_meta_memory(self, summary: str) -> None:
        """Store summarized reflection into narrative meta-memory."""
        record = {
            "timestamp": datetime.now().isoformat(),
            "summary": summary
        }
        self.meta_memory.append(record)

        # Trim meta-memory if too large
        if len(self.meta_memory) > self.config.meta_memory_limit:
            self.meta_memory = self.meta_memory[-self.config.meta_memory_limit:]

    def recall_meta_memory(self, n: int = 5) -> str:
        """Recall the last n reflective summaries."""
        recent = self.meta_memory[-n:]
        if not recent:
            return "🧩 No reflective memory yet."
        lines = [
            f"{self.config.prefix} (reflective) {r['summary']}" for r in recent]
        return "\n".join(lines)

    def re_summarize_meta_memory(self) -> str:
        """Generate a meta-level reflection summarizing all past reflections."""
        if not self.meta_memory:
            return "🧠 No meta-level story yet."

        # Very simple linguistic condensation
        total = len(self.meta_memory)
        first_ts = self.meta_memory[0]["timestamp"].split("T")[1][:8]
        last_ts = self.meta_memory[-1]["timestamp"].split("T")[1][:8]
        return (
            f"{self.config.prefix} [Reflective Loop] Between {first_ts} and {last_ts}, "
            f"the system produced {total} reflective summaries — "
            f"indicating a stable pattern of introspection.")

    # ------------------------------------------------------------------ #
    # Accessors
    # ------------------------------------------------------------------ #
    def story(self, n: int = 20) -> str:
        """Return the most recent n narrative lines."""
        return "\n".join(self.history[-n:])

    def clear(self) -> None:
        """Reset both history and meta-memory."""
        self.history.clear()
        self.event_buffer.clear()
        self.meta_memory.clear()

    # ------------------------------------------------------------------ #
    # Helpers
    # ------------------------------------------------------------------ #
    def _record(self, line: str) -> None:
        self.history.append(line)
        if len(self.history) > self.config.history_limit:
            self.history = self.history[-self.config.history_limit:]
