//! DynamicPlugin — 将动态加载的 .so 包装为 Plugin trait 实现
//!
//! 通过 C FFI + JSON 序列化与 .so 插件通信。
//! 对 ServiceManager 而言，DynamicPlugin 与静态插件无区别。

use crate::core::message::{Envelope, Message};
use crate::core::plugin::{CapabilityTestResult, Plugin, PluginContext, PluginInfo};
use crate::core::plugin_abi::{
    ffi_str_to_str, FfiResult, FfiString, PluginHandle, PluginVTable, PLUGIN_VTABLE_SYMBOL,
};
use anyhow::{anyhow, Context, Result};
use libloading::Library;
use std::ffi::CString;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{mpsc, Arc};

struct CallbackState {
    active: AtomicBool,
    tx: mpsc::Sender<Envelope>,
}

impl CallbackState {
    fn new(tx: mpsc::Sender<Envelope>) -> Self {
        Self {
            active: AtomicBool::new(true),
            tx,
        }
    }

    fn deactivate(&self) {
        self.active.store(false, Ordering::Release);
    }

    fn is_active(&self) -> bool {
        self.active.load(Ordering::Acquire)
    }
}

/// 动态加载的插件
pub struct DynamicPlugin {
    /// 保持 .so 文件加载（RAII）
    _library: Library,
    /// FFI 虚函数表
    vtable: &'static PluginVTable,
    /// 插件实例句柄
    handle: PluginHandle,
    /// 缓存的插件信息
    info: PluginInfo,
    /// 插件 ID
    id: String,
    /// 依赖列表
    dependencies: Vec<String>,
    /// .so 文件路径（用于调试/日志）
    so_path: String,
    /// Sender 上下文指针（持有一份 Arc 强引用，供插件跨线程回调期间保活）
    sender_ctx: *mut std::ffi::c_void,
    /// 宿主持有的回调状态，用于卸载前熔断回调并延长 Sender 生命周期
    sender_state: Option<Arc<CallbackState>>,
    /// stop 是否已经执行成功，避免卸载阶段重复 stop
    stopped: bool,
}

impl std::fmt::Debug for DynamicPlugin {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DynamicPlugin")
            .field("id", &self.id)
            .field("so_path", &self.so_path)
            .field("stopped", &self.stopped)
            .finish()
    }
}

// PluginHandle 是 *mut c_void，需要手动声明 Send
// 安全性由插件 SDK 的 ShowenPlugin: Send 约束保证
unsafe impl Send for DynamicPlugin {}

impl DynamicPlugin {
    /// 从 .so 文件加载插件
    ///
    /// # Safety
    /// .so 文件必须是由 showen-plugin-sdk 编译的合法插件
    pub unsafe fn load(so_path: &str, dependencies: Vec<String>) -> Result<Self> {
        let library = unsafe {
            Library::new(so_path)
                .with_context(|| format!("failed to load plugin .so: {so_path}"))?
        };

        // 查找 vtable 符号
        let vtable: &'static PluginVTable = unsafe {
            let symbol = library
                .get::<*const PluginVTable>(PLUGIN_VTABLE_SYMBOL)
                .with_context(|| format!("symbol 'showen_plugin_vtable' not found in {so_path}"))?;
            &**symbol
        };

        // 创建插件实例
        let handle = unsafe { (vtable.create)() };
        if handle.is_null() {
            return Err(anyhow!("plugin create() returned null for {so_path}"));
        }

        // 获取插件信息
        let info_ffi: FfiString = unsafe { (vtable.get_info)(handle) };
        let info_json = unsafe { Self::read_plugin_string(vtable, info_ffi) }
            .ok_or_else(|| anyhow!("plugin get_info() returned null for {so_path}"))?;
        let info: PluginInfo = serde_json::from_str(&info_json)
            .with_context(|| format!("invalid plugin info JSON from {so_path}"))?;

        let id = info.name.clone();

        Ok(Self {
            _library: library,
            vtable,
            handle,
            info,
            id,
            dependencies,
            so_path: so_path.to_string(),
            sender_ctx: std::ptr::null_mut(),
            sender_state: None,
            stopped: false,
        })
    }

    /// 设置插件 ID（由 manifest 中的 id 字段覆盖）
    pub fn set_id(&mut self, id: String) {
        self.id = id;
    }

    /// 获取 .so 文件路径
    pub fn so_path(&self) -> &str {
        &self.so_path
    }

    /// 将 FfiResult 转为 anyhow::Result
    unsafe fn check_result(&self, result: FfiResult, operation: &str) -> Result<()> {
        if result.code == 0 {
            return Ok(());
        }

        let error = unsafe { Self::read_plugin_string(self.vtable, result.error) }
            .unwrap_or_else(|| "unknown plugin error".to_string());
        Err(anyhow!(
            "plugin '{}' {} failed: {}",
            self.id,
            operation,
            error
        ))
    }

    /// 读取插件返回的字符串，并通过插件提供的 free_string 释放
    unsafe fn read_plugin_string(vtable: &PluginVTable, ffi_str: FfiString) -> Option<String> {
        let string = unsafe { ffi_str.to_string() };
        unsafe { (vtable.free_string)(ffi_str) };
        string
    }

    fn deactivate_callback(&self) {
        if let Some(state) = &self.sender_state {
            state.deactivate();
        }
    }

    fn release_sender_ctx(&mut self) {
        self.deactivate_callback();

        if !self.sender_ctx.is_null() {
            unsafe {
                drop(Arc::from_raw(self.sender_ctx as *const CallbackState));
            }
            self.sender_ctx = std::ptr::null_mut();
        }

        self.sender_state = None;
    }
}

impl Plugin for DynamicPlugin {
    fn id(&self) -> &str {
        &self.id
    }

    fn info(&self) -> PluginInfo {
        self.info.clone()
    }

    fn dependencies(&self) -> Vec<String> {
        self.dependencies.clone()
    }

    fn capabilities(&self) -> Vec<String> {
        let ffi_str: FfiString = unsafe { (self.vtable.get_capabilities)(self.handle) };
        let json = match unsafe { Self::read_plugin_string(self.vtable, ffi_str) } {
            Some(s) => s,
            None => return vec![],
        };
        serde_json::from_str(&json).unwrap_or_default()
    }

    fn self_test(&mut self) -> Vec<CapabilityTestResult> {
        let ffi_str: FfiString = unsafe { (self.vtable.self_test)(self.handle) };
        let json = match unsafe { Self::read_plugin_string(self.vtable, ffi_str) } {
            Some(s) => s,
            None => return vec![],
        };
        serde_json::from_str(&json).unwrap_or_default()
    }

    fn init(&mut self, ctx: PluginContext) -> Result<()> {
        let config_json = serde_json::to_string(ctx.config.as_ref())
            .context("failed to serialize config for dynamic plugin")?;
        let config_cstr = CString::new(config_json).context("config JSON contains null byte")?;

        // 通过 Arc 为回调上下文保活：
        // - 宿主持有一份 Arc，控制 active flag
        // - FFI ctx 持有另一份 Arc，保证插件后台线程回调期间 sender_ctx 不悬空
        let sender_state = Arc::new(CallbackState::new(ctx.tx));
        self.sender_ctx = Arc::into_raw(Arc::clone(&sender_state)) as *mut std::ffi::c_void;
        self.sender_state = Some(sender_state);
        self.stopped = false;

        let result = unsafe {
            (self.vtable.init)(
                self.handle,
                config_cstr.as_ptr(),
                self.sender_ctx,
                ffi_send_callback,
            )
        };
        match unsafe { self.check_result(result, "init") } {
            Ok(()) => Ok(()),
            Err(err) => {
                self.release_sender_ctx();
                Err(err)
            }
        }
    }

    fn start(&mut self) -> Result<()> {
        let result = unsafe { (self.vtable.start)(self.handle) };
        self.stopped = false;
        unsafe { self.check_result(result, "start") }
    }

    fn handle_message(&mut self, msg: Message) -> Result<()> {
        let msg_json = serde_json::to_string(&msg)
            .context("failed to serialize message for dynamic plugin")?;
        let msg_cstr = CString::new(msg_json).context("message JSON contains null byte")?;

        let result = unsafe { (self.vtable.handle_message)(self.handle, msg_cstr.as_ptr()) };
        unsafe { self.check_result(result, "handle_message") }
    }

    fn stop(&mut self) -> Result<()> {
        self.deactivate_callback();
        let result = unsafe { (self.vtable.stop)(self.handle) };
        match unsafe { self.check_result(result, "stop") } {
            Ok(()) => {
                self.stopped = true;
                Ok(())
            }
            Err(err) => Err(err),
        }
    }
}

impl Drop for DynamicPlugin {
    fn drop(&mut self) {
        self.deactivate_callback();

        if !self.handle.is_null() && !self.stopped {
            let result = unsafe { (self.vtable.stop)(self.handle) };
            if let Err(err) = unsafe { self.check_result(result, "stop during unload") } {
                eprintln!(
                    "[DynamicPlugin] plugin '{}' stop during unload failed: {err}",
                    self.id
                );
            }
        }

        if !self.handle.is_null() {
            unsafe { (self.vtable.destroy)(self.handle) };
            self.handle = std::ptr::null_mut();
        }

        self.release_sender_ctx();
    }
}

// ── SendCallback 实现 ──

/// C FFI 回调：插件调用此函数向主程序发消息
///
/// # Safety
/// - `ctx` 必须来自 `DynamicPlugin::init` 传入的 `sender_ctx`。
/// - 动态插件必须在 `stop()` 返回前停止所有可能继续调用该回调的后台线程。
/// - 宿主在卸载前会先把回调熔断为 no-op，再执行 `stop()`/`destroy()`，避免卸载期间 UAF。
unsafe extern "C" fn ffi_send_callback(
    ctx: *mut std::ffi::c_void,
    envelope_json: crate::core::plugin_abi::FfiStr,
) {
    if ctx.is_null() {
        eprintln!("[DynamicPlugin] send callback received null sender ctx");
        return;
    }

    let json_str = match unsafe { ffi_str_to_str(envelope_json) } {
        Some(s) => s,
        None => {
            eprintln!("[DynamicPlugin] send callback received null JSON");
            return;
        }
    };

    let envelope: Envelope = match serde_json::from_str(json_str) {
        Ok(e) => e,
        Err(e) => {
            eprintln!("[DynamicPlugin] invalid envelope JSON: {e}");
            return;
        }
    };

    let state = unsafe { &*(ctx as *const CallbackState) };
    if !state.is_active() {
        return;
    }

    if let Err(e) = state.tx.send(envelope) {
        eprintln!("[DynamicPlugin] failed to send envelope: {e}");
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::message::{Destination, Message};
    use std::time::Duration;

    #[test]
    fn ffi_send_callback_becomes_noop_after_deactivate() {
        let (tx, rx) = mpsc::channel();
        let state = Arc::new(CallbackState::new(tx));
        let ctx = Arc::into_raw(Arc::clone(&state)) as *mut std::ffi::c_void;

        let envelope = Envelope {
            from: "dynamic-test".to_string(),
            to: Destination::Manager,
            message: Message::Shutdown,
        };
        let json = serde_json::to_string(&envelope).expect("envelope should serialize");
        let json = CString::new(json).expect("json should not contain null");

        unsafe { ffi_send_callback(ctx, json.as_ptr()) };
        let received = rx
            .recv_timeout(Duration::from_millis(100))
            .expect("active callback should deliver envelope");
        assert_eq!(received.from, "dynamic-test");

        state.deactivate();
        unsafe { ffi_send_callback(ctx, json.as_ptr()) };
        assert!(rx.recv_timeout(Duration::from_millis(100)).is_err());

        unsafe {
            drop(Arc::from_raw(ctx as *const CallbackState));
        }
    }
}
