#!/usr/bin/env python3
"""
GENESIS EVENT BUS
==================
Pub/sub event system for loose coupling between Genesis modules.

Features:
    - Topic-based publish/subscribe
    - Async and sync event handling
    - Event persistence for replay
    - Priority-based delivery
    - Event filtering

Usage:
    bus = EventBus()
    bus.subscribe("task.completed", handler)
    bus.publish("task.completed", {"task_id": "123"})
"""

import asyncio
import json
import threading
import time
from collections import defaultdict
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Any, Optional, Callable, Set
import queue
import hashlib


@dataclass
class Event:
    """An event in the system."""
    topic: str
    data: Dict[str, Any]
    event_id: str = field(default="")
    timestamp: str = field(default="")
    source: Optional[str] = None
    priority: int = 5  # 1-10, higher = more urgent

    def __post_init__(self):
        if not self.event_id:
            content = f"{self.topic}{json.dumps(self.data)}{time.time()}"
            self.event_id = hashlib.md5(content.encode()).hexdigest()[:12]
        if not self.timestamp:
            self.timestamp = datetime.now().isoformat()

    def to_dict(self) -> Dict:
        return {
            "event_id": self.event_id,
            "topic": self.topic,
            "data": self.data,
            "timestamp": self.timestamp,
            "source": self.source,
            "priority": self.priority
        }


@dataclass
class Subscription:
    """A subscription to events."""
    topic: str
    handler: Callable[[Event], None]
    filter_fn: Optional[Callable[[Event], bool]] = None
    async_handler: bool = False
    subscription_id: str = field(default="")

    def __post_init__(self):
        if not self.subscription_id:
            self.subscription_id = hashlib.md5(
                f"{self.topic}{id(self.handler)}{time.time()}".encode()
            ).hexdigest()[:8]


class EventBus:
    """
    Central event bus for Genesis module communication.

    Supports:
    - Wildcard subscriptions (e.g., "task.*")
    - Persistent events for replay
    - Priority-based delivery
    """

    def __init__(self, persist_events: bool = True, max_history: int = 1000):
        self.subscriptions: Dict[str, List[Subscription]] = defaultdict(list)
        self.event_history: List[Event] = []
        self.max_history = max_history
        self.persist_events = persist_events
        self._lock = threading.RLock()
        self._event_queue: queue.Queue = queue.Queue()
        self._running = False
        self._worker_thread: Optional[threading.Thread] = None

        # Persistence
        self.storage_path = Path(__file__).parent.parent / "data" / "events"
        if persist_events:
            self.storage_path.mkdir(parents=True, exist_ok=True)

    def subscribe(
        self,
        topic: str,
        handler: Callable[[Event], None],
        filter_fn: Callable[[Event], bool] = None,
        async_handler: bool = False
    ) -> str:
        """
        Subscribe to events on a topic.

        Args:
            topic: Topic pattern (supports wildcards like "task.*")
            handler: Function to call when event received
            filter_fn: Optional filter function
            async_handler: If True, handler is called asynchronously

        Returns:
            Subscription ID for later unsubscribe
        """
        sub = Subscription(
            topic=topic,
            handler=handler,
            filter_fn=filter_fn,
            async_handler=async_handler
        )

        with self._lock:
            self.subscriptions[topic].append(sub)

        return sub.subscription_id

    def unsubscribe(self, subscription_id: str) -> bool:
        """Unsubscribe by subscription ID."""
        with self._lock:
            for topic, subs in self.subscriptions.items():
                for sub in subs:
                    if sub.subscription_id == subscription_id:
                        subs.remove(sub)
                        return True
        return False

    def publish(
        self,
        topic: str,
        data: Dict[str, Any],
        source: str = None,
        priority: int = 5
    ) -> str:
        """
        Publish an event.

        Args:
            topic: Event topic
            data: Event data
            source: Source module/component
            priority: Delivery priority (1-10)

        Returns:
            Event ID
        """
        event = Event(
            topic=topic,
            data=data,
            source=source,
            priority=priority
        )

        # Store in history
        with self._lock:
            self.event_history.append(event)
            if len(self.event_history) > self.max_history:
                self.event_history.pop(0)

        # Persist if enabled
        if self.persist_events:
            self._persist_event(event)

        # Deliver to subscribers
        self._deliver(event)

        return event.event_id

    def _deliver(self, event: Event):
        """Deliver event to matching subscribers."""
        matching_subs = []

        with self._lock:
            for pattern, subs in self.subscriptions.items():
                if self._matches_pattern(event.topic, pattern):
                    matching_subs.extend(subs)

        # Sort by priority (higher priority first)
        # Note: We could also sort by subscription priority if needed

        for sub in matching_subs:
            try:
                # Apply filter
                if sub.filter_fn and not sub.filter_fn(event):
                    continue

                # Deliver
                if sub.async_handler:
                    threading.Thread(
                        target=sub.handler,
                        args=(event,),
                        daemon=True
                    ).start()
                else:
                    sub.handler(event)

            except Exception as e:
                # Log error but don't stop delivery to other subscribers
                print(f"[EventBus] Error in handler for {event.topic}: {e}")

    def _matches_pattern(self, topic: str, pattern: str) -> bool:
        """Check if topic matches subscription pattern."""
        if pattern == topic:
            return True

        # Wildcard matching
        if "*" in pattern:
            pattern_parts = pattern.split(".")
            topic_parts = topic.split(".")

            if len(pattern_parts) > len(topic_parts) + 1:
                return False

            for i, part in enumerate(pattern_parts):
                if part == "*":
                    continue
                if part == "**":
                    return True  # Match rest
                if i >= len(topic_parts) or part != topic_parts[i]:
                    return False

            return len(pattern_parts) == len(topic_parts) or pattern_parts[-1] in ("*", "**")

        return False

    def _persist_event(self, event: Event):
        """Persist event to storage."""
        try:
            date_str = datetime.now().strftime("%Y-%m-%d")
            log_path = self.storage_path / f"events_{date_str}.jsonl"

            with open(log_path, "a") as f:
                f.write(json.dumps(event.to_dict()) + "\n")
        except Exception:
            pass

    def get_history(
        self,
        topic: str = None,
        since: str = None,
        limit: int = 100
    ) -> List[Event]:
        """Get event history."""
        with self._lock:
            events = self.event_history.copy()

        # Filter by topic
        if topic:
            events = [e for e in events if self._matches_pattern(e.topic, topic)]

        # Filter by time
        if since:
            events = [e for e in events if e.timestamp >= since]

        # Limit
        return events[-limit:]

    def replay(self, topic: str, since: str = None):
        """Replay events from history."""
        events = self.get_history(topic=topic, since=since)
        for event in events:
            self._deliver(event)

    def start_background_processor(self):
        """Start background event processor."""
        if self._running:
            return

        self._running = True
        self._worker_thread = threading.Thread(
            target=self._process_queue,
            daemon=True
        )
        self._worker_thread.start()

    def stop_background_processor(self):
        """Stop background event processor."""
        self._running = False
        if self._worker_thread:
            self._worker_thread.join(timeout=2)

    def _process_queue(self):
        """Process events from queue."""
        while self._running:
            try:
                event = self._event_queue.get(timeout=0.5)
                self._deliver(event)
            except queue.Empty:
                continue

    def get_topics(self) -> Set[str]:
        """Get all subscribed topics."""
        with self._lock:
            return set(self.subscriptions.keys())

    def get_stats(self) -> Dict:
        """Get event bus statistics."""
        with self._lock:
            topics = set(self.subscriptions.keys())
            total_subs = sum(len(subs) for subs in self.subscriptions.values())

            topic_counts = defaultdict(int)
            for event in self.event_history:
                topic_counts[event.topic] += 1

        return {
            "topics": len(topics),
            "subscriptions": total_subs,
            "events_in_history": len(self.event_history),
            "most_active_topics": sorted(
                topic_counts.items(),
                key=lambda x: -x[1]
            )[:5]
        }


# Global event bus instance
_global_bus: Optional[EventBus] = None


def get_event_bus() -> EventBus:
    """Get the global event bus instance."""
    global _global_bus
    if _global_bus is None:
        _global_bus = EventBus()
    return _global_bus


# Convenience functions
def publish(topic: str, data: Dict, source: str = None) -> str:
    """Publish to global event bus."""
    return get_event_bus().publish(topic, data, source)


def subscribe(topic: str, handler: Callable[[Event], None]) -> str:
    """Subscribe on global event bus."""
    return get_event_bus().subscribe(topic, handler)


def main():
    """Demo the event bus."""
    import argparse
    parser = argparse.ArgumentParser(description="Genesis Event Bus")
    parser.add_argument("command", choices=["demo", "stats", "history"])
    parser.add_argument("--topic", help="Topic filter")
    args = parser.parse_args()

    bus = EventBus(persist_events=True)

    if args.command == "demo":
        print("Event Bus Demo")
        print("=" * 40)

        # Set up subscribers
        received = []

        def handler(event: Event):
            received.append(event)
            print(f"Received: {event.topic} -> {event.data}")

        bus.subscribe("task.*", handler)
        bus.subscribe("system.health", handler)

        # Publish events
        bus.publish("task.started", {"task_id": "001", "title": "Test"})
        bus.publish("task.completed", {"task_id": "001", "success": True})
        bus.publish("system.health", {"status": "healthy"})
        bus.publish("other.event", {"data": "ignored"})

        print(f"\nReceived {len(received)} events (expected 3)")

    elif args.command == "stats":
        stats = bus.get_stats()
        print("Event Bus Statistics:")
        print(json.dumps(stats, indent=2, default=str))

    elif args.command == "history":
        events = bus.get_history(topic=args.topic)
        print(f"Event History ({len(events)} events):")
        for event in events[-10:]:
            print(f"  [{event.timestamp}] {event.topic}: {event.data}")


if __name__ == "__main__":
    main()
