"""Async pub/sub event bus for live updates across the AgentScout system."""

from __future__ import annotations

import asyncio
import time
import uuid
from collections.abc import Callable, Coroutine
from dataclasses import dataclass, field
from enum import StrEnum
from typing import Any


class EventType(StrEnum):
    """All event types emitted by the agent system."""

    # ReAct loop events
    THOUGHT = "thought"
    ACTION = "action"
    OBSERVATION = "observation"
    REFLECTION = "reflection"

    # Tool events
    TOOL_CALL_START = "tool_call_start"
    TOOL_CALL_END = "tool_call_end"
    TOOL_ERROR = "tool_error"

    # Task lifecycle events
    TASK_CREATED = "task_created"
    TASK_STATE_CHANGE = "task_state_change"
    TASK_COMPLETED = "task_completed"
    TASK_FAILED = "task_failed"

    # User interaction events
    USER_APPROVAL_REQUESTED = "user_approval_requested"
    USER_APPROVAL_RECEIVED = "user_approval_received"

    # Cost events
    COST_UPDATE = "cost_update"
    BUDGET_WARNING = "budget_warning"
    BUDGET_EXCEEDED = "budget_exceeded"

    # System events
    ERROR = "error"
    AGENT_STARTED = "agent_started"
    AGENT_STOPPED = "agent_stopped"

    # Memory events
    MEMORY_STORED = "memory_stored"
    MEMORY_RECALLED = "memory_recalled"

    # Browser events
    SCREENSHOT_CAPTURED = "screenshot_captured"


@dataclass
class Event:
    """A single event emitted by the agent system."""

    event_type: EventType
    data: dict[str, Any]
    event_id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
    timestamp: float = field(default_factory=time.time)
    task_id: str | None = None
    parent_event_id: str | None = None

    def to_dict(self) -> dict[str, Any]:
        return {
            "event_id": self.event_id,
            "event_type": self.event_type.value,
            "data": self.data,
            "timestamp": self.timestamp,
            "task_id": self.task_id,
            "parent_event_id": self.parent_event_id,
        }


# Type alias for event handler callbacks
EventHandler = Callable[[Event], Coroutine[Any, Any, None]]


class EventBus:
    """Async pub/sub event bus.

    Supports subscribing to specific event types or all events.
    Handlers are async coroutines invoked concurrently.
    """

    def __init__(self) -> None:
        self._handlers: dict[EventType | None, list[EventHandler]] = {}
        self._history: list[Event] = []
        self._max_history: int = 1000

    def subscribe(
        self,
        handler: EventHandler,
        event_type: EventType | None = None,
    ) -> Callable[[], None]:
        """Subscribe a handler to an event type (or all events if None).

        Returns an unsubscribe function.
        """
        if event_type not in self._handlers:
            self._handlers[event_type] = []
        self._handlers[event_type].append(handler)

        def unsubscribe() -> None:
            self._handlers[event_type].remove(handler)
            if not self._handlers[event_type]:
                del self._handlers[event_type]

        return unsubscribe

    async def emit(self, event: Event) -> None:
        """Emit an event to all matching handlers."""
        self._history.append(event)
        if len(self._history) > self._max_history:
            self._history = self._history[-self._max_history :]

        # Gather handlers for this specific event type + wildcard handlers
        handlers: list[EventHandler] = []
        handlers.extend(self._handlers.get(event.event_type, []))
        handlers.extend(self._handlers.get(None, []))

        if handlers:
            await asyncio.gather(
                *(h(event) for h in handlers),
                return_exceptions=True,
            )

    def get_history(
        self,
        event_type: EventType | None = None,
        task_id: str | None = None,
        limit: int = 100,
    ) -> list[Event]:
        """Retrieve recent events, optionally filtered."""
        events = self._history
        if event_type is not None:
            events = [e for e in events if e.event_type == event_type]
        if task_id is not None:
            events = [e for e in events if e.task_id == task_id]
        return events[-limit:]

    def clear_history(self) -> None:
        self._history.clear()

    @property
    def handler_count(self) -> int:
        return sum(len(h) for h in self._handlers.values())
