use hyper::{Body, Request, Response};
use tokio::sync::broadcast;

use crate::structs::struct_plugin::{Plugin, PluginContext};

/// An event that will be fanned out to all subscribers.
#[derive(Clone, Debug)]
pub enum BroadcastEvent {
    /// Minimal “something changed” message. Extend as needed later.
    UpdateId { id: String },
}

/// Cloneable sender wrapper that plugins can use to broadcast.
#[derive(Clone)]
pub struct BroadcastTx {
    tx: broadcast::Sender<BroadcastEvent>,
}

impl BroadcastTx {
    pub fn new(tx: broadcast::Sender<BroadcastEvent>) -> Self {
        Self { tx }
    }

    /// Send a simple update ID to all subscribers.
    pub fn send_update_id(&self, id: impl Into<String>) {
        // If there are no receivers, this returns Err — which is fine to ignore.
        let _ = self.tx.send(BroadcastEvent::UpdateId { id: id.into() });
    }

    /// Create a new subscription (used by SSE endpoint).
    pub fn subscribe(&self) -> broadcast::Receiver<BroadcastEvent> {
        self.tx.subscribe()
    }
}

/// Manages plugins and exposes a shared broadcast bus.
pub struct PluginManager {
    plugins: Vec<Box<dyn Plugin>>,
    broadcast: BroadcastTx,
}

impl PluginManager {
    /// Create a manager with an existing broadcast bus.
    pub fn with_broadcast(broadcast: BroadcastTx) -> Self {
        Self {
            plugins: Vec::new(),
            broadcast,
        }
    }

    /// Convenience constructor: create a fresh bus.
    pub fn new() -> Self {
        let (tx, _rx) = broadcast::channel::<BroadcastEvent>(1024);
        Self::with_broadcast(BroadcastTx::new(tx))
    }

    /// Add a plugin to the manager.
    pub fn apply_plugin(&mut self, plugin: Box<dyn Plugin>) {
        self.plugins.push(plugin);
    }

    /// Initialize all plugins.
    pub async fn init_plugins(&mut self) {
        for plugin in self.plugins.iter_mut() {
            plugin.plugin_init().await;
        }
    }

    /// Ask each plugin if it can handle the request; return the first response.
    pub async fn handle_request(&self, req: Request<Body>) -> Option<Response<Body>> {
        // Build a context to pass into the plugin (includes broadcaster).
        let ctx = PluginContext {
            broadcast: self.broadcast.clone(),
        };

        for plugin in self.plugins.iter() {
            if plugin.plugin_can_handle(&req) {
                return Some(plugin.plugin_handle(req, &ctx).await.ok()?);
            }
        }
        None
    }

    /// Expose the broadcast handle so other subsystems (e.g., SSE) can subscribe.
    pub fn broadcast(&self) -> &BroadcastTx {
        &self.broadcast
    }
}
