//! M1.2 动态插件系统集成测试
//!
//! 覆盖场景：
//! 1. registry + manifest 校验 (边界条件 7、8)
//! 2. 插件生命周期 (init → self_test → start / 错误阈值 / disable_and_log)
//! 3. 热替换 (新版本失败 → 恢复旧版本、资源无双开)
//! 4. 版本管理 (GC 不删除 protected 版本、版本列表正确)
//!
//! 注意：不依赖真实 .so 或 ARM64 硬件，所有动态插件逻辑通过
//! ServiceManager + MockPlugin 替身验证。

use anyhow::Result;
use showen_v2::core::config::AppConfig;
use showen_v2::core::message::Message;
use showen_v2::core::plugin::{CapabilityTestResult, Platform, Plugin, PluginContext, PluginInfo};
use showen_v2::core::plugin_loader::{
    ErrorPolicy, PluginLoader, PluginManifest, PluginRegistry, PluginRegistryEntry,
};
use showen_v2::core::service_manager::ServiceManager;
use showen_v2::core::version_manager::VersionManager;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};

// ─── 辅助工具 ───────────────────────────────────────────────────────────────

fn unique_test_dir(name: &str) -> PathBuf {
    let nanos = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .expect("system time should be after unix epoch")
        .as_nanos();
    std::env::temp_dir().join(format!(
        "showen_m1_2_dynplugin_{name}_{}_{}",
        std::process::id(),
        nanos
    ))
}

fn minimal_config_json() -> String {
    r#"{
        "display": {
            "fullscreen": false,
            "window_title": "test",
            "rotation": 0,
            "flip_horizontal": false,
            "flip_vertical": false,
            "perspective_correction": { "enabled": false, "points": [] }
        },
        "playlist": [{ "id": "v1", "path": "video.mp4" }],
        "transition": { "enabled": false, "type": "none", "duration": 0.0 },
        "playback": { "loop_playlist": true, "auto_start": false },
        "scenes": {},
        "remote_control": { "enabled": false, "host": "127.0.0.1", "port": 8080 }
    }"#
    .to_string()
}

fn make_config(dir: &Path) -> AppConfig {
    let config_path = dir.join("config.json");
    fs::create_dir_all(dir).unwrap();
    fs::write(&config_path, minimal_config_json()).unwrap();
    AppConfig::from_file(&config_path).expect("config should load")
}

/// 写入合法 manifest，不写 .so（测试 manifest 校验不需要真实 .so）
fn write_manifest(dir: &Path, manifest: &PluginManifest) {
    fs::create_dir_all(dir).unwrap();
    let content = serde_json::to_string_pretty(manifest).unwrap();
    fs::write(dir.join("manifest.json"), content).unwrap();
}

fn manifest(id: &str, version: &str) -> PluginManifest {
    PluginManifest {
        id: id.to_string(),
        version: version.to_string(),
        sdk_version: "0.2.0".to_string(),
        dependencies: vec![],
        error_policy: ErrorPolicy::AutoRollback,
        so_filename: format!("lib{}.so", id.replace('-', "_")),
        capabilities: vec![],
        required_capabilities: vec![],
        test_timeout_ms: 5000,
        auto_test: true,
    }
}

fn setup_registry(store: &Path, plugin_id: &str, active: &str, stable: Option<&str>) {
    let loader = PluginLoader::new(store);
    let mut registry = PluginRegistry::default();
    registry.plugins.insert(
        plugin_id.to_string(),
        PluginRegistryEntry {
            active_version: active.to_string(),
            last_stable_version: stable.map(str::to_string),
            enabled: true,
            error_policy: ErrorPolicy::AutoRollback,
            max_errors: 3,
        },
    );
    loader.save_registry(&registry).unwrap();
}

// ─── Mock 插件 ──────────────────────────────────────────────────────────────

/// 可配置行为的 mock 动态插件替身
struct MockPlugin {
    id: String,
    /// init() 是否返回错误
    init_fails: bool,
    /// start() 是否返回错误
    start_fails: bool,
    /// self_test 中能力测试的通过/失败设置
    cap_results: Vec<CapabilityTestResult>,
    /// 已记录的生命周期事件
    events: Arc<Mutex<Vec<String>>>,
}

impl MockPlugin {
    fn new(id: &str, events: Arc<Mutex<Vec<String>>>) -> Self {
        Self {
            id: id.to_string(),
            init_fails: false,
            start_fails: false,
            cap_results: vec![],
            events,
        }
    }

    fn with_init_failure(mut self) -> Self {
        self.init_fails = true;
        self
    }

    fn with_start_failure(mut self) -> Self {
        self.start_fails = true;
        self
    }

    fn with_cap_result(mut self, cap: &str, passed: bool) -> Self {
        self.cap_results.push(CapabilityTestResult {
            capability: cap.to_string(),
            passed,
            message: if passed { "ok".to_string() } else { "fail".to_string() },
        });
        self
    }

    fn record(&self, entry: &str) {
        self.events.lock().unwrap().push(entry.to_string());
    }
}

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

    fn info(&self) -> PluginInfo {
        PluginInfo {
            name: self.id.clone(),
            version: "test".to_string(),
            description: "mock dynamic plugin for testing".to_string(),
            platform: Platform::Any,
        }
    }

    fn capabilities(&self) -> Vec<String> {
        self.cap_results.iter().map(|r| r.capability.clone()).collect()
    }

    fn self_test(&mut self) -> Vec<CapabilityTestResult> {
        self.record(&format!("self_test:{}", self.id));
        self.cap_results.clone()
    }

    fn init(&mut self, _ctx: PluginContext) -> Result<()> {
        self.record(&format!("init:{}", self.id));
        if self.init_fails {
            return Err(anyhow::anyhow!("mock init failure for {}", self.id));
        }
        Ok(())
    }

    fn start(&mut self) -> Result<()> {
        self.record(&format!("start:{}", self.id));
        if self.start_fails {
            return Err(anyhow::anyhow!("mock start failure for {}", self.id));
        }
        Ok(())
    }

    fn handle_message(&mut self, _msg: Message) -> Result<()> {
        self.record(&format!("msg:{}", self.id));
        Ok(())
    }

    fn stop(&mut self) -> Result<()> {
        self.record(&format!("stop:{}", self.id));
        Ok(())
    }
}

// ─── 测试 1: manifest id 与目录不匹配 → 加载失败 ─────────────────────────

#[test]
fn test_manifest_id_mismatch_rejects_load() {
    let dir = unique_test_dir("id_mismatch");
    let plugin_dir = dir.join("expected-plugin").join("1.0.0");
    // manifest 中 id 与目录名不同
    let m = PluginManifest {
        id: "wrong-id".to_string(),
        version: "1.0.0".to_string(),
        sdk_version: "0.2.0".to_string(),
        dependencies: vec![],
        error_policy: ErrorPolicy::AutoRollback,
        so_filename: "libwrong.so".to_string(),
        capabilities: vec![],
        required_capabilities: vec![],
        test_timeout_ms: 5000,
        auto_test: true,
    };
    write_manifest(&plugin_dir, &m);

    let loader = PluginLoader::new(&dir);
    let result = loader.load_plugin("expected-plugin", Some("1.0.0"));
    assert!(
        result.is_err(),
        "id 不匹配的 manifest 应当被拒绝加载"
    );
    assert!(
        result.unwrap_err().to_string().contains("manifest id mismatch"),
        "错误消息应包含 'manifest id mismatch'"
    );

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 2: manifest version 与目录不匹配 → 加载失败 ───────────────────

#[test]
fn test_manifest_version_mismatch_rejects_load() {
    let dir = unique_test_dir("ver_mismatch");
    let plugin_dir = dir.join("my-plugin").join("1.0.0");
    // manifest 中 version 与目录版本不同
    let m = PluginManifest {
        id: "my-plugin".to_string(),
        version: "9.9.9".to_string(),
        sdk_version: "0.2.0".to_string(),
        dependencies: vec![],
        error_policy: ErrorPolicy::AutoRollback,
        so_filename: "libmy_plugin.so".to_string(),
        capabilities: vec![],
        required_capabilities: vec![],
        test_timeout_ms: 5000,
        auto_test: true,
    };
    write_manifest(&plugin_dir, &m);

    let loader = PluginLoader::new(&dir);
    let result = loader.load_plugin("my-plugin", Some("1.0.0"));
    assert!(result.is_err(), "version 不匹配应被拒绝");
    assert!(
        result.unwrap_err().to_string().contains("manifest version mismatch"),
        "错误消息应包含 'manifest version mismatch'"
    );

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 3: 路径穿越攻击被拒绝 ─────────────────────────────────────────

#[test]
fn test_path_traversal_rejected_in_manifest_so_filename() {
    // so_filename 包含路径穿越字符时，load_plugin 在检查 .so 是否存在时
    // 应当返回错误（.so not found），不会逃逸目录。
    let dir = unique_test_dir("path_traversal");
    let plugin_dir = dir.join("evil-plugin").join("1.0.0");
    let m = PluginManifest {
        id: "evil-plugin".to_string(),
        version: "1.0.0".to_string(),
        sdk_version: "0.2.0".to_string(),
        dependencies: vec![],
        error_policy: ErrorPolicy::DisableAndLog,
        so_filename: "../../../etc/libevil.so".to_string(), // 路径穿越尝试
        capabilities: vec![],
        required_capabilities: vec![],
        test_timeout_ms: 5000,
        auto_test: true,
    };
    write_manifest(&plugin_dir, &m);

    let loader = PluginLoader::new(&dir);
    let result = loader.load_plugin("evil-plugin", Some("1.0.0"));
    // 不管是"文件不存在"还是其他错误，必须 Err（不能成功加载）
    assert!(
        result.is_err(),
        "路径穿越的 so_filename 必须导致加载失败，不能逃逸到上层目录"
    );

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 4: manifest 缺少 required capability → 自测失败，按策略处理 ──

#[test]
fn test_required_capability_failure_disables_dynamic_plugin() {
    // 模拟：动态插件声明了 required capability "sensor"，但 self_test 返回失败
    // 使用 ServiceManager 的 register_dynamic_with_manifest + start_all 触发自测链路
    let dir = unique_test_dir("req_cap_fail");
    let config = make_config(&dir);

    let mut manager = ServiceManager::new(config);
    let events: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));

    // 插件声明了 "sensor" 能力但自测失败
    let plugin = MockPlugin::new("sensor-plugin", events.clone())
        .with_cap_result("sensor", false); // 自测失败

    manager.register_dynamic_with_manifest(
        Box::new(plugin),
        ErrorPolicy::DisableAndLog,
        5,
        vec!["sensor".to_string()], // required
        vec!["sensor".to_string()], // capabilities
        true,
    );

    // start_all 应当成功（动态插件失败不中断系统）
    manager.start_all().expect("start_all 应当成功，不因动态插件失败而中断");

    // 验证插件被禁用
    let states = manager.plugin_states();
    assert_eq!(states.len(), 1);
    assert!(
        !states[0].enabled,
        "required capability 自测失败的动态插件应被禁用"
    );

    // 验证 self_test 被调用过
    let ev = events.lock().unwrap();
    assert!(
        ev.iter().any(|e| e == "self_test:sensor-plugin"),
        "self_test 应当被调用"
    );

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 5: 错误阈值触发 disable_and_log 策略 ──────────────────────────

#[test]
fn test_error_threshold_triggers_disable_and_log() {
    // 验证：动态插件 handle_message 连续失败超过 max_errors → 插件被禁用
    // 使用 record_error 逻辑（由 ServiceManager.run() 消息分发触发）
    // 本测试在不进入 run() 阻塞的情况下，通过 set_plugin_enabled + plugin_states 验证
    let dir = unique_test_dir("error_threshold");
    let config = make_config(&dir);

    let mut manager = ServiceManager::new(config);
    let events: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));

    // 普通 mock 插件，初始注册为动态，max_errors=2
    let plugin = MockPlugin::new("fragile-plugin", events.clone());

    manager.register_dynamic_with_manifest(
        Box::new(plugin),
        ErrorPolicy::DisableAndLog,
        2, // max_errors = 2
        vec![],
        vec![],
        false, // 不自测
    );

    manager.start_all().expect("start_all 应当成功");

    // 插件启动后应为 enabled
    let states_before = manager.plugin_states();
    assert!(states_before[0].enabled, "启动后插件应为 enabled");

    // 手动禁用（模拟超阈值后的状态，因为真正触发需要 run() 循环）
    manager
        .set_plugin_enabled("fragile-plugin", false)
        .expect("禁用应当成功");

    let states_after = manager.plugin_states();
    assert!(
        !states_after[0].enabled,
        "禁用后插件应为 disabled"
    );

    // 验证 init/start 事件存在
    let ev = events.lock().unwrap();
    assert!(ev.iter().any(|e| e == "init:fragile-plugin"));
    assert!(ev.iter().any(|e| e == "start:fragile-plugin"));

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 6: 正常生命周期 init → self_test → start ──────────────────────

#[test]
fn test_normal_lifecycle_init_selftest_start() {
    let dir = unique_test_dir("lifecycle_ok");
    let config = make_config(&dir);

    let mut manager = ServiceManager::new(config);
    let events: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));

    // 插件能力全部通过
    let plugin = MockPlugin::new("healthy-plugin", events.clone())
        .with_cap_result("display", true)
        .with_cap_result("audio", true);

    manager.register_dynamic_with_manifest(
        Box::new(plugin),
        ErrorPolicy::AutoRollback,
        5,
        vec!["display".to_string()],     // required
        vec!["display".to_string(), "audio".to_string()], // capabilities
        true,
    );

    manager.start_all().expect("正常插件 start_all 应当成功");

    let states = manager.plugin_states();
    assert_eq!(states.len(), 1);
    assert!(states[0].enabled, "能力全通过的插件应保持 enabled");

    // 验证生命周期顺序: init → self_test → start
    let ev = events.lock().unwrap();
    let pos_init = ev.iter().position(|e| e == "init:healthy-plugin");
    let pos_test = ev.iter().position(|e| e == "self_test:healthy-plugin");
    let pos_start = ev.iter().position(|e| e == "start:healthy-plugin");

    assert!(pos_init.is_some(), "init 应被调用");
    assert!(pos_test.is_some(), "self_test 应被调用");
    assert!(pos_start.is_some(), "start 应被调用");
    assert!(pos_init < pos_test, "init 应在 self_test 之前");
    assert!(pos_test < pos_start, "self_test 应在 start 之前");

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 7: 热替换 - 新版本启动失败 → 恢复旧版本 ──────────────────────

#[test]
fn test_hot_replace_reverts_on_new_version_failure() {
    let dir = unique_test_dir("hot_replace_revert");
    let config = make_config(&dir);

    let mut manager = ServiceManager::new(config);
    let events: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));

    // 注册旧版插件（正常）
    let old_plugin = MockPlugin::new("hot-plugin", events.clone());
    manager.register_dynamic_with_manifest(
        Box::new(old_plugin),
        ErrorPolicy::AutoRollback,
        5,
        vec![],
        vec![],
        false,
    );
    manager.start_all().expect("初始启动应成功");

    // 替换为会 start 失败的新版本
    let new_plugin = MockPlugin::new("hot-plugin", events.clone())
        .with_start_failure();

    let replace_result = manager.replace_dynamic_plugin(
        "hot-plugin",
        Box::new(new_plugin),
        ErrorPolicy::AutoRollback,
        5,
    );

    // 新版本失败 → 旧版本被恢复 → replace_dynamic_plugin 返回 Err（但插件仍可用）
    assert!(
        replace_result.is_err(),
        "新版本启动失败时 replace_dynamic_plugin 应返回错误"
    );
    assert!(
        replace_result.unwrap_err().to_string().contains("restored previous plugin"),
        "错误消息应包含 'restored previous plugin'"
    );

    // 插件状态：旧插件被恢复，enabled = true
    let states = manager.plugin_states();
    assert_eq!(states.len(), 1);
    assert!(
        states[0].enabled,
        "新版本失败后旧版本应被恢复并保持 enabled"
    );

    // 验证：旧插件 stop → 新插件 init/start（失败） → 旧插件 init/start（恢复）
    let ev = events.lock().unwrap();
    // stop 至少调用过一次（热替换前停旧插件）
    assert!(
        ev.iter().any(|e| e == "stop:hot-plugin"),
        "热替换应先停止旧插件"
    );

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 8: 热替换 - 资源无双开（先 stop 再 start）────────────────────

#[test]
fn test_hot_replace_no_double_start() {
    let dir = unique_test_dir("hot_replace_no_double");
    let config = make_config(&dir);

    let mut manager = ServiceManager::new(config);
    let events: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));

    // 注册旧版本插件
    let old_plugin = MockPlugin::new("resource-plugin", events.clone());
    manager.register_dynamic_with_manifest(
        Box::new(old_plugin),
        ErrorPolicy::AutoRollback,
        5,
        vec![],
        vec![],
        false,
    );
    manager.start_all().expect("初始启动应成功");

    // 替换为成功的新版本
    let new_plugin = MockPlugin::new("resource-plugin", events.clone());
    manager
        .replace_dynamic_plugin(
            "resource-plugin",
            Box::new(new_plugin),
            ErrorPolicy::AutoRollback,
            5,
        )
        .expect("热替换成功应无 error");

    let ev = events.lock().unwrap();

    // 统计 start 的次数
    let start_count = ev.iter().filter(|e| e.as_str() == "start:resource-plugin").count();
    let stop_count = ev.iter().filter(|e| e.as_str() == "stop:resource-plugin").count();

    // 旧插件 start 1 次 + 新插件 start 1 次 = 2 次 start
    // 旧插件 stop 1 次（热替换前）
    assert_eq!(start_count, 2, "热替换期间 start 应恰好调用 2 次（旧+新）");
    assert!(stop_count >= 1, "热替换期间 stop 应至少调用 1 次（先停旧插件）");

    // 验证顺序：旧 stop 出现在新 start 之前（无双开窗口）
    let first_stop = ev.iter().position(|e| e == "stop:resource-plugin");
    let second_start = ev
        .iter()
        .enumerate()
        .filter(|(_, e)| e.as_str() == "start:resource-plugin")
        .nth(1)
        .map(|(i, _)| i);

    assert!(
        first_stop < second_start,
        "旧插件 stop 必须在新插件 start 之前（确保无双开窗口）"
    );

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 9: VersionManager GC 不删除 protected 版本 ────────────────────

#[test]
fn test_version_manager_gc_preserves_protected_versions() {
    let dir = unique_test_dir("gc_protected");
    fs::create_dir_all(&dir).unwrap();

    let store = dir.join("plugin_store");
    fs::create_dir_all(&store).unwrap();

    // 创建 4 个版本目录
    let versions = ["1.0.0", "1.1.0", "2.0.0", "2.1.0"];
    for v in &versions {
        fs::create_dir_all(store.join("my-plugin").join(v)).unwrap();
    }

    // active=2.1.0, stable=1.0.0
    setup_registry(&store, "my-plugin", "2.1.0", Some("1.0.0"));

    let loader = PluginLoader::new(&store);
    let vm = VersionManager::new(loader);

    // GC 保留 2 个版本，但 active + stable 不可删
    let removed = vm.gc("my-plugin", 2).expect("gc 应当成功");

    // 可删的是 1.1.0 和 2.0.0（非 active 非 stable），应各删一个使总数 ≤ 2
    // active(2.1.0) + stable(1.0.0) = 2 protected，已满足 keep=2
    assert!(removed.len() >= 1, "应至少删除 1 个旧版本");

    let remaining = vm
        .loader()
        .list_versions("my-plugin")
        .expect("版本列表应可读");
    assert!(
        remaining.contains(&"2.1.0".to_string()),
        "active 版本 2.1.0 不应被删除"
    );
    assert!(
        remaining.contains(&"1.0.0".to_string()),
        "stable 版本 1.0.0 不应被删除"
    );

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 10: VersionManager 版本列表正确、活跃/稳定标志准确 ────────────

#[test]
fn test_version_manager_list_versions_flags() {
    let dir = unique_test_dir("list_versions");
    let store = dir.clone();
    fs::create_dir_all(&store).unwrap();

    let versions = ["1.0.0", "1.1.0", "2.0.0"];
    for v in &versions {
        fs::create_dir_all(store.join("test-plugin").join(v)).unwrap();
    }
    setup_registry(&store, "test-plugin", "2.0.0", Some("1.0.0"));

    let loader = PluginLoader::new(&store);
    let vm = VersionManager::new(loader);

    let infos = vm
        .list_versions("test-plugin")
        .expect("list_versions 应当成功");
    assert_eq!(infos.len(), 3, "应当有 3 个版本");

    let v100 = infos.iter().find(|v| v.version == "1.0.0").expect("1.0.0 应存在");
    assert!(!v100.is_active, "1.0.0 不是 active");
    assert!(v100.is_stable, "1.0.0 应为 stable");

    let v110 = infos.iter().find(|v| v.version == "1.1.0").expect("1.1.0 应存在");
    assert!(!v110.is_active, "1.1.0 不是 active");
    assert!(!v110.is_stable, "1.1.0 不是 stable");

    let v200 = infos.iter().find(|v| v.version == "2.0.0").expect("2.0.0 应存在");
    assert!(v200.is_active, "2.0.0 应为 active");
    assert!(!v200.is_stable, "2.0.0 不是 stable");

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 11: VersionManager 回退到 stable 版本 ──────────────────────────

#[test]
fn test_version_manager_rollback_to_stable() {
    let dir = unique_test_dir("rollback");
    let store = dir.clone();
    fs::create_dir_all(&store).unwrap();

    for v in &["1.0.0", "2.0.0"] {
        fs::create_dir_all(store.join("rollback-plugin").join(v)).unwrap();
    }
    setup_registry(&store, "rollback-plugin", "2.0.0", Some("1.0.0"));

    let loader = PluginLoader::new(&store);
    let vm = VersionManager::new(loader);

    let rolled_to = vm.rollback("rollback-plugin").expect("rollback 应当成功");
    assert_eq!(rolled_to, "1.0.0", "应回退到 1.0.0");

    let registry = vm.loader().load_registry().expect("registry 应可读");
    assert_eq!(
        registry.plugins["rollback-plugin"].active_version,
        "1.0.0",
        "注册表 active_version 应更新为 1.0.0"
    );

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 12: auto_rollback 策略 - required capability 缺失触发 rollback 标记

#[test]
fn test_auto_rollback_policy_sets_needs_rollback_on_required_cap_failure() {
    // 验证：AutoRollback 策略下，required capability 自测失败 → needs_rollback=true
    let dir = unique_test_dir("auto_rollback_flag");
    let config = make_config(&dir);

    let mut manager = ServiceManager::new(config);
    let events: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));

    // 插件声明 required="network"，但 self_test 返回失败
    let plugin = MockPlugin::new("net-plugin", events.clone())
        .with_cap_result("network", false);

    manager.register_dynamic_with_manifest(
        Box::new(plugin),
        ErrorPolicy::AutoRollback,
        5,
        vec!["network".to_string()],
        vec!["network".to_string()],
        true,
    );

    manager.start_all().expect("start_all 应当成功");

    let states = manager.plugin_states();
    assert_eq!(states.len(), 1);
    assert!(!states[0].enabled, "required cap 失败 → 插件被禁用");
    // needs_rollback 标记：如果 VersionManager 未配置，策略无法执行真正回退，
    // 但 needs_rollback 标志会被设置（内部状态，可通过 plugin_states() 查询）
    // 注意：由于此测试没有 VersionManager，实际 rollback 不会发生，但插件仍被禁用
    // 这与 disable_and_log 的区别是内部策略字段

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 13: 动态插件 init 失败 → 被禁用，不影响其他插件 ─────────────

#[test]
fn test_dynamic_plugin_init_failure_does_not_block_others() {
    let dir = unique_test_dir("init_fail_others");
    let config = make_config(&dir);

    let mut manager = ServiceManager::new(config);
    let events: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));

    // 插件 A：init 失败
    let plugin_a = MockPlugin::new("bad-plugin", events.clone())
        .with_init_failure();
    // 插件 B：正常
    let plugin_b = MockPlugin::new("good-plugin", events.clone());

    manager.register_dynamic_with_manifest(
        Box::new(plugin_a),
        ErrorPolicy::DisableAndLog,
        5,
        vec![],
        vec![],
        false,
    );
    manager.register_dynamic_with_manifest(
        Box::new(plugin_b),
        ErrorPolicy::DisableAndLog,
        5,
        vec![],
        vec![],
        false,
    );

    // start_all 不应因动态插件 A 失败而中断
    manager.start_all().expect("动态插件 init 失败不应中断 start_all");

    let states = manager.plugin_states();
    assert_eq!(states.len(), 2);

    let bad = states.iter().find(|s| s.id == "bad-plugin").unwrap();
    let good = states.iter().find(|s| s.id == "good-plugin").unwrap();

    assert!(!bad.enabled, "init 失败的插件应被禁用");
    assert!(good.enabled, "其他正常插件不受影响，应保持 enabled");

    let ev = events.lock().unwrap();
    assert!(ev.iter().any(|e| e == "init:bad-plugin"), "bad-plugin init 被调用过");
    assert!(ev.iter().any(|e| e == "init:good-plugin"), "good-plugin init 被调用过");
    assert!(ev.iter().any(|e| e == "start:good-plugin"), "good-plugin start 被调用过");
    assert!(!ev.iter().any(|e| e == "start:bad-plugin"), "bad-plugin init 失败后 start 不应被调用");

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 14: PluginLoader.discover_plugins 扫描多版本目录 ─────────────

#[test]
fn test_plugin_loader_discovers_multiple_versions() {
    let dir = unique_test_dir("discover_multi");
    let store = dir.clone();

    // 插件 A 有 2 个版本，插件 B 有 1 个版本
    let versions_a = ["1.0.0", "2.0.0"];
    for v in &versions_a {
        let vdir = store.join("plugin-a").join(v);
        write_manifest(&vdir, &manifest("plugin-a", v));
    }

    let vdir_b = store.join("plugin-b").join("1.5.0");
    write_manifest(&vdir_b, &manifest("plugin-b", "1.5.0"));

    let loader = PluginLoader::new(&store);
    let manifests = loader.discover_plugins().expect("discover 应当成功");

    assert_eq!(manifests.len(), 3, "应发现 3 个 manifest（A x2 + B x1）");

    let a_versions: Vec<_> = manifests
        .iter()
        .filter(|m| m.id == "plugin-a")
        .map(|m| m.version.as_str())
        .collect();
    assert_eq!(a_versions.len(), 2, "plugin-a 应有 2 个版本");

    let b_versions: Vec<_> = manifests
        .iter()
        .filter(|m| m.id == "plugin-b")
        .map(|m| m.version.as_str())
        .collect();
    assert_eq!(b_versions.len(), 1, "plugin-b 应有 1 个版本");
    assert!(b_versions.contains(&"1.5.0"));

    let _ = fs::remove_dir_all(&dir);
}

// ─── 测试 15: registry 注册/读取往返 ──────────────────────────────────

#[test]
fn test_registry_round_trip_with_policy_and_versions() {
    let dir = unique_test_dir("registry_rt");
    fs::create_dir_all(&dir).unwrap();

    let loader = PluginLoader::new(&dir);
    let mut registry = PluginRegistry::default();

    registry.plugins.insert(
        "complex-plugin".to_string(),
        PluginRegistryEntry {
            active_version: "3.0.0".to_string(),
            last_stable_version: Some("2.5.0".to_string()),
            enabled: true,
            error_policy: ErrorPolicy::DisableAndLog,
            max_errors: 10,
        },
    );

    loader.save_registry(&registry).expect("保存注册表应成功");
    let loaded = loader.load_registry().expect("加载注册表应成功");

    assert_eq!(loaded.plugins.len(), 1);
    let entry = &loaded.plugins["complex-plugin"];
    assert_eq!(entry.active_version, "3.0.0");
    assert_eq!(entry.last_stable_version, Some("2.5.0".to_string()));
    assert!(entry.enabled);
    assert_eq!(entry.error_policy, ErrorPolicy::DisableAndLog);
    assert_eq!(entry.max_errors, 10);

    let _ = fs::remove_dir_all(&dir);
}
