"""Event logging utilities for the HIEROMEM framework."""

from __future__ import annotations

import json
import time
import uuid
from collections.abc import Callable, Iterable
from dataclasses import asdict, dataclass, field
from datetime import datetime
from enum import Enum
from pathlib import Path
from types import TracebackType
from typing import Any, Literal, cast


# --------------------------------------------------------------------------- #
# ENUM: Event Types
# --------------------------------------------------------------------------- #
class EventType(str, Enum):
    """All operation types the HIEROMEM system can emit."""

    INSERT = "insert"
    RETRIEVE = "retrieve"
    SUMMARISE = "summarise"
    FORGET = "forget"
    LOAD = "load"
    SAVE = "save"
    CLEAR = "clear"
    GOVERNANCE = "governance"
    POLICY = "policy"
    SYSTEM = "system"


# --------------------------------------------------------------------------- #
# EVENT: Core structure
# --------------------------------------------------------------------------- #
@dataclass
class MemoryEvent:
    """Represents a single event generated by the HIEROMEM system."""

    event_type: EventType
    message: str
    timestamp: datetime = field(default_factory=datetime.now)
    duration: float | None = None
    details: dict[str, Any] = field(default_factory=dict)
    source: str | None = None
    level: Literal["info", "warn", "error"] = "info"
    session_id: str | None = None

    def __repr__(self) -> str:
        t = self.timestamp.strftime("%H:%M:%S")
        dur = f" | {self.duration:.3f}s" if self.duration else ""
        return (
            f"<MemoryEvent {self.event_type.value.upper()} ({self.level}) "
            f"at {t}{dur}: {self.message}>"
        )

    def to_dict(self) -> dict[str, Any]:
        data = asdict(self)
        data["timestamp"] = self.timestamp.isoformat()
        data["event_type"] = self.event_type.value
        return data

    def to_json(self, indent: int | None = 2) -> str:
        return json.dumps(self.to_dict(), indent=indent)

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> MemoryEvent:
        payload = data.copy()
        payload["event_type"] = EventType(payload["event_type"])
        payload["timestamp"] = datetime.fromisoformat(payload["timestamp"])
        return cls(**payload)


# --------------------------------------------------------------------------- #
# EVENT LOGGER
# --------------------------------------------------------------------------- #
class EventLog:
    """Container and manager for :class:`MemoryEvent` instances."""

    def __init__(self, session_id: str | None = None):
        self._events: list[MemoryEvent] = []
        self._subscribers: list[Callable[[MemoryEvent], None]] = []
        self.session_id = session_id or str(uuid.uuid4())

    # ------------------------------------------------------------------ #
    # Context manager support
    # ------------------------------------------------------------------ #
    def __enter__(self) -> EventLog:
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc: BaseException | None,
        tb: TracebackType | None,
    ) -> None:
        self.flush()

    # ------------------------------------------------------------------ #
    # Core operations
    # ------------------------------------------------------------------ #
    def add(
        self,
        event_type: EventType,
        message: str,
        details: dict[str, Any] | None = None,
        level: Literal["info", "warn", "error"] = "info",
        source: str | None = None,
        duration: float | None = None,
    ) -> MemoryEvent:
        """Create and record a new :class:`MemoryEvent`."""

        evt = MemoryEvent(
            event_type=event_type,
            message=message,
            timestamp=datetime.now(),
            duration=duration,
            details=details or {},
            source=source,
            level=level,
            session_id=self.session_id,
        )
        self._events.append(evt)
        self._notify(evt)
        return evt

    def extend(self, events: Iterable[MemoryEvent]) -> None:
        for evt in events:
            self._events.append(evt)
            self._notify(evt)

    def clear(self) -> None:
        self._events.clear()

    # ------------------------------------------------------------------ #
    # Querying & filtering
    # ------------------------------------------------------------------ #
    def all(self) -> list[MemoryEvent]:
        return list(self._events)

    def recent(self, n: int = 10) -> list[MemoryEvent]:
        return self._events[-n:]

    def filter(
        self,
        event_type: EventType | None = None,
        level: Literal["info", "warn", "error"] | None = None,
        source: str | None = None,
    ) -> list[MemoryEvent]:
        """Filter events by type, level, or source."""

        result = self._events
        if event_type is not None:
            result = [e for e in result if e.event_type == event_type]
        if level is not None:
            result = [e for e in result if e.level == level]
        if source is not None:
            result = [e for e in result if e.source == source]
        return result

    # ------------------------------------------------------------------ #
    # Persistence utilities
    # ------------------------------------------------------------------ #
    def save_to_file(self, path: str | Path) -> None:
        """Write all events as a JSON Lines file."""

        dest = Path(path)
        with dest.open("w", encoding="utf-8") as handle:
            for evt in self._events:
                handle.write(evt.to_json(indent=None) + "\n")

    def load_from_file(self, path: str | Path) -> None:
        """Load events from a JSON Lines file."""

        src = Path(path)
        if not src.exists():
            return
        with src.open("r", encoding="utf-8") as handle:
            for line in handle:
                data = json.loads(line)
                event = MemoryEvent.from_dict(cast(dict[str, Any], data))
                self._events.append(event)

    def flush(self) -> None:
        """Placeholder for potential streaming or batch flush."""

        return None

    # ------------------------------------------------------------------ #
    # Subscriptions (real-time listeners)
    # ------------------------------------------------------------------ #
    def subscribe(self, callback: Callable[[MemoryEvent], None]) -> None:
        """Attach a listener to be notified when new events are added."""

        self._subscribers.append(callback)

    def _notify(self, event: MemoryEvent) -> None:
        for subscriber in self._subscribers:
            try:
                subscriber(event)
            except Exception as exc:  # pragma: no cover - defensive logging
                print(f"[EventLog] Subscriber error: {exc}")

    # ------------------------------------------------------------------ #
    # Visualization & summaries
    # ------------------------------------------------------------------ #
    def to_json(self, indent: int = 2) -> str:
        return json.dumps([e.to_dict() for e in self._events], indent=indent)

    def summary(
        self,
        n: int = 10,
        level: Literal["info", "warn", "error"] | None = None,
    ) -> str:
        """Return a concise textual summary of recent events."""

        events = self.filter(level=level)
        recent = events[-n:]
        lines = [
            f"{e.timestamp.strftime('%H:%M:%S')} "
            f"[{e.level.upper()}:{e.event_type.value}] {e.message}"
            for e in recent
        ]
        return "\n".join(lines)

    # ------------------------------------------------------------------ #
    # Timing helper
    # ------------------------------------------------------------------ #
    def timed_event(
        self,
        event_type: EventType,
        message: str,
        **kwargs: Any,
    ) -> _TimedEventContext:
        """Return a context manager that records a timed event."""

        return _TimedEventContext(self, event_type, message, **kwargs)


# --------------------------------------------------------------------------- #
# INTERNAL: Context manager for timed events
# --------------------------------------------------------------------------- #
class _TimedEventContext:
    """Used by :meth:`EventLog.timed_event` to measure duration automatically."""

    def __init__(
        self,
        log: EventLog,
        event_type: EventType,
        message: str,
        **kwargs: Any,
    ):
        self.log = log
        self.event_type = event_type
        self.message = message
        self.kwargs = kwargs
        self._start: float | None = None

    def __enter__(self) -> _TimedEventContext:
        self._start = time.time()
        return self

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        start = self._start if self._start is not None else time.time()
        duration = time.time() - start
        level: Literal["info", "warn", "error"]
        level = "error" if exc_type else self.kwargs.get("level", "info")
        self.log.add(
            event_type=self.event_type,
            message=self.message,
            duration=duration,
            level=level,
            details=self.kwargs.get("details", {}),
            source=self.kwargs.get("source"),
        )
