"""EventBus — thread-safe pub/sub with topic-wildcard matching.

Topics use dot-separated hierarchy (e.g. ``bar.AAPL.1Min``, ``order.filled``).
Subscribers can use ``*`` as a single-segment wildcard and ``**`` to match any
remaining segments.

Examples::

    bus = EventBus()
    bus.subscribe("bar.AAPL.*", handler)      # any timeframe for AAPL
    bus.subscribe("order.**", handler)         # all order events
    bus.subscribe("bar.**", handler)           # all bar events
    bus.publish("bar.AAPL.1Min", bar_event)    # matched by both
"""

from __future__ import annotations

import fnmatch
import threading
from collections import defaultdict
from typing import Any, Callable, Dict, List, Set

Handler = Callable[..., Any]


class EventBus:
    """Thread-safe publish/subscribe event bus with wildcard topic matching."""

    def __init__(self) -> None:
        self._lock = threading.Lock()
        self._exact: Dict[str, List[Handler]] = defaultdict(list)
        self._patterns: Dict[str, List[Handler]] = defaultdict(list)
        self._all_topics: Set[str] = set()

    def subscribe(self, topic: str, handler: Handler) -> None:
        """Register *handler* for events published to *topic*.

        *topic* may contain ``*`` (single segment) or ``**`` (remaining segments).
        """
        with self._lock:
            if "*" in topic:
                if handler not in self._patterns[topic]:
                    self._patterns[topic].append(handler)
            else:
                if handler not in self._exact[topic]:
                    self._exact[topic].append(handler)
            self._all_topics.add(topic)

    def unsubscribe(self, topic: str, handler: Handler) -> None:
        """Remove *handler* from *topic*."""
        with self._lock:
            if "*" in topic:
                handlers = self._patterns.get(topic)
            else:
                handlers = self._exact.get(topic)
            if handlers and handler in handlers:
                handlers.remove(handler)

    def publish(self, topic: str, event: Any = None) -> int:
        """Publish *event* on *topic*. Returns number of handlers invoked."""
        with self._lock:
            handlers = list(self._exact.get(topic, []))
            for pattern, phandlers in self._patterns.items():
                if _topic_matches(pattern, topic):
                    handlers.extend(phandlers)

        count = 0
        for h in handlers:
            try:
                h(event)
                count += 1
            except Exception:
                pass
        return count

    def has_subscribers(self, topic: str) -> bool:
        with self._lock:
            if self._exact.get(topic):
                return True
            for pattern in self._patterns:
                if _topic_matches(pattern, topic):
                    return True
        return False

    def clear(self) -> None:
        with self._lock:
            self._exact.clear()
            self._patterns.clear()
            self._all_topics.clear()


def _topic_matches(pattern: str, topic: str) -> bool:
    """Check if *topic* matches *pattern* with ``*`` and ``**`` wildcards."""
    pat_parts = pattern.split(".")
    top_parts = topic.split(".")

    pi = ti = 0
    while pi < len(pat_parts) and ti < len(top_parts):
        if pat_parts[pi] == "**":
            return True
        if pat_parts[pi] == "*" or pat_parts[pi] == top_parts[ti]:
            pi += 1
            ti += 1
        else:
            return False

    return pi == len(pat_parts) and ti == len(top_parts)
