"""BLCE Message Bus — lightweight in-process pub/sub for agent communication.

Thread-safe message passing built on ``queue.Queue``.  Agents and the runtime
publish structured messages on named topics; subscribers receive callbacks.

Standard topics:
    agent.started, agent.completed, agent.failed,
    phase.started, phase.completed
"""
from __future__ import annotations

import threading
import uuid
from datetime import datetime, timezone
from typing import Any, Callable, Dict, List, Optional


# ---------------------------------------------------------------------------
# Data model
# ---------------------------------------------------------------------------

class AgentMessage:
    """A single message on the bus."""

    __slots__ = ("message_id", "sender", "topic", "payload", "timestamp")

    def __init__(
        self,
        sender: str,
        topic: str,
        payload: Optional[Dict[str, Any]] = None,
    ):
        self.message_id: str = uuid.uuid4().hex[:12]
        self.sender: str = sender
        self.topic: str = topic
        self.payload: Dict[str, Any] = payload or {}
        self.timestamp: str = datetime.now(timezone.utc).isoformat()

    def to_dict(self) -> Dict[str, Any]:
        return {
            "message_id": self.message_id,
            "sender": self.sender,
            "topic": self.topic,
            "payload": self.payload,
            "timestamp": self.timestamp,
        }


# ---------------------------------------------------------------------------
# Message bus
# ---------------------------------------------------------------------------

class MessageBus:
    """In-process pub/sub message bus (thread-safe).

    Usage::

        bus = MessageBus()
        bus.subscribe("agent.completed", my_callback)
        bus.publish("agent.completed", sender="SemanticAgent", payload={...})
        msgs = bus.drain("agent.completed")
    """

    def __init__(self) -> None:
        self._lock = threading.Lock()
        self._messages: List[AgentMessage] = []
        self._subscribers: Dict[str, List[Callable]] = {}

    # -- publish / subscribe ------------------------------------------------

    def publish(
        self,
        topic: str,
        sender: str,
        payload: Optional[Dict[str, Any]] = None,
    ) -> AgentMessage:
        """Publish a message to *topic*.  Returns the created message."""
        msg = AgentMessage(sender=sender, topic=topic, payload=payload)
        with self._lock:
            self._messages.append(msg)
            callbacks = list(self._subscribers.get(topic, []))
        # Fire callbacks outside the lock to avoid deadlocks
        for cb in callbacks:
            try:
                cb(msg)
            except Exception:
                pass  # Subscribers must not break the bus
        return msg

    def subscribe(self, topic: str, callback: Callable) -> None:
        """Register *callback* for *topic*.

        The callback signature is ``callback(msg: AgentMessage) -> None``.
        """
        with self._lock:
            self._subscribers.setdefault(topic, []).append(callback)

    # -- drain / history ----------------------------------------------------

    def drain(self, topic: Optional[str] = None) -> List[AgentMessage]:
        """Return (and remove) messages, optionally filtered by *topic*.

        If *topic* is ``None``, all messages are drained.
        """
        with self._lock:
            if topic is None:
                out = list(self._messages)
                self._messages.clear()
            else:
                out = [m for m in self._messages if m.topic == topic]
                self._messages = [m for m in self._messages if m.topic != topic]
        return out

    def history(self, limit: int = 100) -> List[Dict[str, Any]]:
        """Return up to *limit* messages (newest-first) **without** draining."""
        with self._lock:
            snapshot = list(self._messages)
        return [m.to_dict() for m in reversed(snapshot)][:limit]

    # -- utility ------------------------------------------------------------

    def clear(self) -> None:
        """Remove all messages and subscribers."""
        with self._lock:
            self._messages.clear()
            self._subscribers.clear()

    @property
    def message_count(self) -> int:
        with self._lock:
            return len(self._messages)

