use shaku::{Component, Interface};

use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};

pub mod domain_events;
pub mod presentation_events;

pub trait Event: Send + Sync + 'static {
    fn as_any(&self) -> &dyn Any;
}

pub trait EventHandler<E: Event>: Send + Sync {
    fn handle(&self, event: &E);
}

type BoxedHandler = Arc<dyn Fn(&dyn Event) + Send + Sync>;

pub trait EventBusInterface: Interface {
    fn as_event_bus(&self) -> &EventBus;
}

#[derive(Clone, Component)]
#[shaku(interface = EventBusInterface)]
pub struct EventBus {
    #[shaku(default)]
    subscribers: Arc<RwLock<HashMap<TypeId, Vec<BoxedHandler>>>>,
}

impl EventBus {
    pub fn new() -> Self {
        Self {
            subscribers: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    pub fn get_subscribers_ptr(&self) -> *const RwLock<HashMap<TypeId, Vec<BoxedHandler>>> {
        Arc::as_ptr(&self.subscribers)
    }

    pub fn publish<E: Event>(&self, event: E) {
        let type_id = TypeId::of::<E>();

        let handlers: Vec<BoxedHandler> = {
            let subscribers = self.subscribers.read().unwrap();
            let h = subscribers.get(&type_id).cloned().unwrap_or_default();
            h
        };

        for handler in handlers.iter() {
            handler(&event);
        }
    }

    pub fn subscribe<E: Event, F>(&self, handler: F)
    where
        F: Fn(&E) + Send + Sync + 'static,
    {
        let type_id = TypeId::of::<E>();
        let mut subscribers = self.subscribers.write().unwrap();

        let boxed_handler: BoxedHandler = Arc::new(move |event: &dyn Event| {
            if let Some(concrete_event) = event.as_any().downcast_ref::<E>() {
                handler(concrete_event);
            }
        });

        subscribers.entry(type_id).or_default().push(boxed_handler);
    }
}

impl EventBusInterface for EventBus {
    fn as_event_bus(&self) -> &EventBus {
        self
    }
}

impl Default for EventBus {
    fn default() -> Self {
        Self::new()
    }
}
