//! `ServerPlugin` helper trait for the common case: a plugin that runs one
//! long-lived background task (HTTP server, queue consumer, etc.) and shuts
//! down when the server signals shutdown.
//!
//! Most plugins should implement [`ServerPlugin`] rather than [`Plugin`]
//! directly, then wrap with [`ServerPluginWrapper`].

use std::sync::{Arc, Mutex};

use anyhow::Result;
use async_trait::async_trait;
use tokio::task::JoinHandle;
use tracing::{error, info, warn};

use crate::context::PluginContext;
use crate::plugin::Plugin;
use crate::rpc::RpcMethodDef;

/// Trait for plugins that run a single long-lived background task.
/// The `run` future should observe `ctx.shutdown` and return when signaled.
#[async_trait]
pub trait ServerPlugin: Send + Sync + 'static {
    /// Stable plugin name (same semantics as [`Plugin::name`]).
    fn name(&self) -> &'static str;

    /// Run the plugin's main loop. Return `Ok(())` on shutdown.
    ///
    /// Observe `ctx.shutdown` to detect the server shutdown signal.
    /// Do **not** discard `watch::RecvError` with `.ok()` — treat it as an
    /// unexpected condition (the sender was dropped without signaling shutdown)
    /// and log an error:
    ///
    /// ```ignore
    /// if let Err(e) = ctx.shutdown.changed().await {
    ///     tracing::error!(error = %e, "shutdown sender dropped unexpectedly");
    /// }
    /// ```
    async fn run(&self, ctx: PluginContext) -> Result<()>;

    /// Optional RPC method advertisements (default empty).
    fn rpc_methods(&self) -> Vec<RpcMethodDef> {
        Vec::new()
    }
}

/// Wraps a [`ServerPlugin`] so it can be used as a [`Plugin`].
/// Spawns `run` at boot, awaits the task during shutdown.
pub struct ServerPluginWrapper<S: ServerPlugin> {
    inner: Arc<S>,
    handle: Mutex<Option<JoinHandle<Result<()>>>>,
}

impl<S: ServerPlugin> ServerPluginWrapper<S> {
    pub fn new(inner: S) -> Self {
        Self {
            inner: Arc::new(inner),
            handle: Mutex::new(None),
        }
    }
}

#[async_trait]
impl<S: ServerPlugin> Plugin for ServerPluginWrapper<S> {
    fn name(&self) -> &'static str {
        self.inner.name()
    }

    async fn boot(&mut self, ctx: PluginContext) -> Result<()> {
        let mut slot = self.handle.lock().expect("mutex");
        if slot.is_some() {
            return Err(anyhow::anyhow!(
                "plugin '{}' is already running; boot() called twice",
                self.inner.name()
            ));
        }
        let inner = self.inner.clone();
        let handle = tokio::spawn(async move { inner.run(ctx).await });
        *slot = Some(handle);
        info!(plugin = self.inner.name(), "server plugin booted");
        Ok(())
    }

    async fn shutdown(&self) -> Result<()> {
        let handle = {
            let mut slot = self
                .handle
                .lock()
                .map_err(|_| anyhow::anyhow!("ServerPluginWrapper handle mutex poisoned"))?;
            slot.take()
        };

        let Some(handle) = handle else {
            warn!(
                plugin = self.inner.name(),
                "shutdown called but no handle present"
            );
            return Ok(());
        };

        // Abort ensures the task stops even if it is not observing ctx.shutdown.
        // The shutdown signal was already broadcast before shutdown() is called,
        // so cooperative tasks have had a chance to react; abort() is the fallback.
        // Without this, a stuck task causes handle.await to deadlock forever.
        handle.abort();

        match handle.await {
            Ok(Ok(())) => {
                info!(plugin = self.inner.name(), "run loop completed cleanly");
                Ok(())
            }
            Ok(Err(err)) => {
                error!(plugin = self.inner.name(), error = ?err, "run loop returned error (shutdown continues)");
                Ok(())
            }
            Err(join_err) if join_err.is_cancelled() => {
                info!(
                    plugin = self.inner.name(),
                    "run loop cancelled during shutdown"
                );
                Ok(())
            }
            Err(join_err) => {
                error!(plugin = self.inner.name(), error = %join_err, "run loop panicked (shutdown continues)");
                Ok(())
            }
        }
    }

    fn rpc_methods(&self) -> Vec<RpcMethodDef> {
        self.inner.rpc_methods()
    }
}
