//! In-memory event bus implementation.

use super::{DomainEvent, EventPublisher};
use async_trait::async_trait;
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

/// Type alias for event handlers.
type BoxedHandler = Box<dyn Fn(&dyn Any) + Send + Sync>;

/// A simple in-memory event bus for domain events.
///
/// This is suitable for single-process applications. For distributed systems,
/// you would implement `EventPublisher` with a message broker like RabbitMQ or Kafka.
///
/// # Example
///
/// ```ignore
/// use soot::ddd::{EventBus, DomainEvent, EventPublisher};
///
/// let bus = EventBus::new();
///
/// // Subscribe to events
/// bus.subscribe(|event: &UserCreated| {
///     println!("User created: {}", event.email);
/// });
///
/// // Publish events
/// bus.publish(UserCreated { ... }).await;
/// ```
#[derive(Default)]
pub struct EventBus {
    handlers: Arc<RwLock<HashMap<TypeId, Vec<BoxedHandler>>>>,
}

impl EventBus {
    /// Create a new event bus.
    #[must_use]
    pub fn new() -> Self {
        Self {
            handlers: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    /// Subscribe to events of a specific type.
    pub async fn subscribe<E, F>(&self, handler: F)
    where
        E: DomainEvent + 'static,
        F: Fn(&E) + Send + Sync + 'static,
    {
        let type_id = TypeId::of::<E>();
        let boxed_handler: BoxedHandler = Box::new(move |event: &dyn Any| {
            if let Some(typed_event) = event.downcast_ref::<E>() {
                handler(typed_event);
            }
        });

        let mut handlers = self.handlers.write().await;
        handlers.entry(type_id).or_default().push(boxed_handler);
    }
}

#[async_trait]
impl EventPublisher for EventBus {
    async fn publish<E: DomainEvent>(&self, event: E) {
        let type_id = TypeId::of::<E>();
        let handlers = self.handlers.read().await;

        if let Some(type_handlers) = handlers.get(&type_id) {
            for handler in type_handlers {
                handler(&event);
            }
        }
    }
}

/// A simple in-memory event bus (alias for `EventBus`).
pub type InMemoryEventBus = EventBus;

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::Utc;
    use std::sync::atomic::{AtomicUsize, Ordering};

    #[derive(Clone, Debug)]
    struct TestEvent {
        message: String,
        occurred_at: chrono::DateTime<Utc>,
    }

    impl DomainEvent for TestEvent {
        fn event_type(&self) -> &'static str {
            "test.event"
        }

        fn occurred_at(&self) -> chrono::DateTime<Utc> {
            self.occurred_at
        }
    }

    #[tokio::test]
    async fn test_event_bus_publish_subscribe() {
        let bus = EventBus::new();
        let counter = Arc::new(AtomicUsize::new(0));
        let counter_clone = counter.clone();

        bus.subscribe(move |_event: &TestEvent| {
            counter_clone.fetch_add(1, Ordering::SeqCst);
        })
        .await;

        bus.publish(TestEvent {
            message: "Hello".to_string(),
            occurred_at: Utc::now(),
        })
        .await;

        assert_eq!(counter.load(Ordering::SeqCst), 1);
    }
}
