use super::config::{parse_str, AppConfig};
use super::message::{Destination, Envelope, Message};
use super::plugin::{CapabilityTestResult, Platform, Plugin, PluginContext, PluginInfo};
use super::plugin_loader::{ErrorPolicy, PluginLoader, PluginRegistry, PluginRegistryEntry};
use super::service_manager::{PluginStateInfo, ServiceManager};
use super::version_manager::VersionManager;
use anyhow::Result;
use std::fs;
use std::path::Path;
use std::sync::{Arc, Mutex};

fn test_config() -> AppConfig {
    parse_str(
        r#"{
            "display": {
                "fullscreen": false,
                "window_title": "test",
                "rotation": 0,
                "flip_horizontal": false,
                "flip_vertical": false,
                "perspective_correction": {
                    "enabled": false,
                    "points": []
                }
            },
            "playlist": [
                {
                    "id": "video-1",
                    "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
            }
        }"#,
        "tests/core-config.json",
    )
    .expect("test config should be valid")
}

fn lock_events(events: &Arc<Mutex<Vec<String>>>) -> std::sync::MutexGuard<'_, Vec<String>> {
    events.lock().expect("events mutex poisoned")
}

fn has_event(events: &Arc<Mutex<Vec<String>>>, expected: &str) -> bool {
    lock_events(events).iter().any(|event| event == expected)
}

fn clear_events(events: &Arc<Mutex<Vec<String>>>) {
    lock_events(events).clear();
}

fn latest_plugin_states(events: &Arc<Mutex<Vec<String>>>, plugin_id: &str) -> Vec<PluginStateInfo> {
    let prefix = format!("msg:{plugin_id}:custom:plugin_states:");
    let payload = lock_events(events)
        .iter()
        .rev()
        .find_map(|event| event.strip_prefix(&prefix).map(str::to_owned))
        .unwrap_or_else(|| panic!("missing plugin_states event for {}", plugin_id));

    serde_json::from_str(&payload).expect("plugin_states payload should deserialize")
}

fn non_plugin_state_events(events: &Arc<Mutex<Vec<String>>>) -> Vec<String> {
    lock_events(events)
        .iter()
        .filter(|event| !event.contains("custom:plugin_states:"))
        .cloned()
        .collect()
}

fn message_label(message: &Message) -> String {
    match message {
        Message::Custom { kind, payload } => format!("custom:{kind}:{payload}"),
        Message::PluginReady(id) => format!("plugin_ready:{id}"),
        Message::WifiResult(payload) => format!("wifi_result:{payload}"),
        Message::Shutdown => "shutdown".to_string(),
        Message::PlayerStatus(_) => "player_status".to_string(),
        Message::StateChanged {
            old_state,
            new_state,
        } => format!("state_changed:{old_state}->{new_state}"),
        Message::WifiProvisioned { ssid, ip } => format!("wifi_provisioned:{ssid}:{ip}"),
        Message::ConfigReloadRequest => "config_reload_request".to_string(),
        Message::ConfigReloaded(_) => "config_reloaded".to_string(),
        Message::PlayerCommand(_) => "player_command".to_string(),
        Message::Trigger { name, value } => format!("trigger:{name}:{value}"),
        Message::ScreenLockRequest(value) => format!("screen_lock:{value}"),
        Message::CursorVisibility(value) => format!("cursor_visibility:{value}"),
        Message::WifiCommand(_) => "wifi_command".to_string(),
        Message::DeviceCommand(_) => "device_command".to_string(),
        Message::DeviceResponse(_) => "device_response".to_string(),
        Message::DeviceEvent(_) => "device_event".to_string(),
        Message::ChatRequest(_) => "chat_request".to_string(),
        Message::ChatResponse(_) => "chat_response".to_string(),
        Message::AiModelEvent(_) => "ai_model_event".to_string(),
    }
}

struct TestPlugin {
    id: String,
    deps: Vec<String>,
    events: Arc<Mutex<Vec<String>>>,
}

#[derive(Clone, Default)]
struct PluginFailurePlan {
    fail_init: bool,
    fail_start: bool,
    fail_stop: bool,
}

struct LifecyclePlugin {
    id: String,
    events: Arc<Mutex<Vec<String>>>,
    plan: Arc<Mutex<PluginFailurePlan>>,
    label: String,
}

impl LifecyclePlugin {
    fn new(
        id: &str,
        label: &str,
        events: Arc<Mutex<Vec<String>>>,
        plan: PluginFailurePlan,
    ) -> (Self, Arc<Mutex<PluginFailurePlan>>) {
        let plan = Arc::new(Mutex::new(plan));
        (
            Self {
                id: id.to_string(),
                events,
                plan: plan.clone(),
                label: label.to_string(),
            },
            plan,
        )
    }

    fn record(&self, phase: &str) {
        lock_events(&self.events).push(format!("{phase}:{}:{}", self.id, self.label));
    }
}

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

    fn info(&self) -> PluginInfo {
        PluginInfo {
            name: format!("{}-{}", self.id, self.label),
            version: "test".to_string(),
            description: "lifecycle test plugin".to_string(),
            platform: Platform::Any,
        }
    }

    fn init(&mut self, _ctx: PluginContext) -> Result<()> {
        self.record("init");
        if self.plan.lock().expect("plan mutex poisoned").fail_init {
            anyhow::bail!("init failure:{}:{}", self.id, self.label);
        }
        Ok(())
    }

    fn start(&mut self) -> Result<()> {
        self.record("start");
        if self.plan.lock().expect("plan mutex poisoned").fail_start {
            anyhow::bail!("start failure:{}:{}", self.id, self.label);
        }
        Ok(())
    }

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

    fn stop(&mut self) -> Result<()> {
        self.record("stop");
        if self.plan.lock().expect("plan mutex poisoned").fail_stop {
            anyhow::bail!("stop failure:{}:{}", self.id, self.label);
        }
        Ok(())
    }
}

impl TestPlugin {
    fn new(id: &str, deps: Vec<&str>, events: Arc<Mutex<Vec<String>>>) -> Self {
        Self {
            id: id.to_string(),
            deps: deps.into_iter().map(|s| s.to_string()).collect(),
            events,
        }
    }

    fn record(&self, entry: impl Into<String>) {
        lock_events(&self.events).push(entry.into());
    }
}

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

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

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

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

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

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

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

#[test]
fn service_manager_register_start_and_stop_flow() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
    manager.register(Box::new(TestPlugin::new("beta", vec![], events.clone())));

    manager.start_all().expect("start_all should succeed");
    manager.stop_all().expect("stop_all should succeed");

    let events = non_plugin_state_events(&events);
    assert_eq!(
        events,
        vec![
            "init:alpha",
            "init:beta",
            "start:alpha",
            "start:beta",
            "stop:beta",
            "stop:alpha",
        ]
    );
}

#[test]
fn routes_plugin_broadcast_and_manager_messages() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
    manager.register(Box::new(TestPlugin::new("beta", vec![], events.clone())));

    manager.start_all().expect("start_all should succeed");
    let sender = manager.sender();

    sender
        .send(Envelope {
            from: "alpha".to_string(),
            to: Destination::Plugin("beta".to_string()),
            message: Message::Custom {
                kind: "direct".to_string(),
                payload: "hello".to_string(),
            },
        })
        .expect("direct message should send");
    sender
        .send(Envelope {
            from: "alpha".to_string(),
            to: Destination::Broadcast,
            message: Message::Custom {
                kind: "broadcast".to_string(),
                payload: "everyone".to_string(),
            },
        })
        .expect("broadcast message should send");
    sender
        .send(Envelope {
            from: "alpha".to_string(),
            to: Destination::Manager,
            message: Message::PluginReady("alpha".to_string()),
        })
        .expect("manager message should send");
    sender
        .send(Envelope {
            from: "test".to_string(),
            to: Destination::Manager,
            message: Message::Shutdown,
        })
        .expect("shutdown should send");

    manager.run().expect("run should succeed");

    assert!(!has_event(&events, "msg:alpha:custom:direct:hello"));
    assert!(has_event(&events, "msg:beta:custom:direct:hello"));

    assert!(has_event(&events, "msg:alpha:custom:broadcast:everyone"));
    assert!(has_event(&events, "msg:beta:custom:broadcast:everyone"));

    assert!(has_event(&events, "msg:alpha:plugin_ready:alpha"));
    assert!(has_event(&events, "msg:beta:plugin_ready:alpha"));
}

#[test]
fn start_all_rejects_missing_dependencies() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());
    manager.register(Box::new(TestPlugin::new(
        "dependent",
        vec!["missing"],
        events,
    )));

    let error = manager
        .start_all()
        .expect_err("missing dependency should fail");
    assert!(error
        .to_string()
        .contains("plugin 'dependent' depends on missing plugin 'missing'"));
}

#[test]
fn start_all_rejects_dependency_cycles() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());
    manager.register(Box::new(TestPlugin::new(
        "alpha",
        vec!["beta"],
        events.clone(),
    )));
    manager.register(Box::new(TestPlugin::new("beta", vec!["alpha"], events)));

    let error = manager
        .start_all()
        .expect_err("dependency cycle should fail");
    assert!(error
        .to_string()
        .contains("plugin dependency cycle detected among"));
}

#[test]
fn start_all_sorts_plugins_topologically() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    manager.register(Box::new(TestPlugin::new(
        "gamma",
        vec!["beta"],
        events.clone(),
    )));
    manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
    manager.register(Box::new(TestPlugin::new(
        "beta",
        vec!["alpha"],
        events.clone(),
    )));

    manager
        .start_all()
        .expect("start_all should sort dependencies");
    manager.stop_all().expect("stop_all should succeed");

    let events = non_plugin_state_events(&events);
    assert_eq!(
        events,
        vec![
            "init:alpha",
            "init:beta",
            "init:gamma",
            "start:alpha",
            "start:beta",
            "start:gamma",
            "stop:gamma",
            "stop:beta",
            "stop:alpha",
        ]
    );
}

#[test]
fn wifi_result_sent_to_manager_is_broadcast_to_plugins() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
    manager.register(Box::new(TestPlugin::new("beta", vec![], events.clone())));

    manager.start_all().expect("start_all should succeed");
    let sender = manager.sender();

    sender
        .send(Envelope {
            from: "wifi".to_string(),
            to: Destination::Manager,
            message: Message::WifiResult("connected".to_string()),
        })
        .expect("wifi result should send");
    sender
        .send(Envelope {
            from: "test".to_string(),
            to: Destination::Manager,
            message: Message::Shutdown,
        })
        .expect("shutdown should send");

    manager.run().expect("run should succeed");

    assert!(has_event(&events, "msg:alpha:wifi_result:connected"));
    assert!(has_event(&events, "msg:beta:wifi_result:connected"));
}

#[test]
fn http_plugin_must_depend_on_video() {
    use crate::plugins::http::HttpPlugin;
    let plugin = HttpPlugin::new();
    let deps = plugin.dependencies();
    assert_eq!(deps, vec!["video"], "http plugin must depend on video");
}

#[test]
fn ble_plugin_must_have_no_dependencies() {
    use crate::plugins::ble::BlePlugin;
    let plugin = BlePlugin::new();
    let deps = plugin.dependencies();
    assert!(deps.is_empty(), "ble plugin must have no dependencies");
}

#[test]
fn wifi_plugin_must_have_no_dependencies() {
    use crate::plugins::wifi::WifiPlugin;
    let plugin = WifiPlugin::new();
    let deps = plugin.dependencies();
    assert!(deps.is_empty(), "wifi plugin must have no dependencies");
}

#[test]
fn video_plugin_must_have_no_dependencies() {
    use crate::plugins::video::VideoPlugin;
    let plugin = VideoPlugin::new();
    let deps = plugin.dependencies();
    assert!(deps.is_empty(), "video plugin must have no dependencies");
}

#[test]
fn screen_plugin_must_depend_on_device() {
    use crate::plugins::screen::ScreenPlugin;
    let plugin = ScreenPlugin::new();
    let deps = plugin.dependencies();
    assert_eq!(deps, vec!["device"], "screen plugin must depend on device");
}

#[test]
fn all_plugin_ids_must_be_unique() {
    use crate::plugins::ble::BlePlugin;
    use crate::plugins::http::HttpPlugin;
    use crate::plugins::screen::ScreenPlugin;
    use crate::plugins::video::VideoPlugin;
    use crate::plugins::wifi::WifiPlugin;
    use std::collections::HashSet;

    let plugins: Vec<Box<dyn Plugin>> = vec![
        Box::new(BlePlugin::new()),
        Box::new(HttpPlugin::new()),
        Box::new(ScreenPlugin::new()),
        Box::new(VideoPlugin::new()),
        Box::new(WifiPlugin::new()),
    ];

    let mut ids = HashSet::new();
    for plugin in plugins {
        let id = plugin.id().to_string();
        assert!(
            ids.insert(id.clone()),
            "duplicate plugin id detected: '{}'",
            id
        );
    }
}

#[test]
fn topological_sort_places_http_after_video() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    manager.register(Box::new(TestPlugin::new(
        "http",
        vec!["video"],
        events.clone(),
    )));
    manager.register(Box::new(TestPlugin::new("video", vec![], events.clone())));

    manager
        .start_all()
        .expect("start_all should succeed with http depending on video");

    let event_log = lock_events(&events).clone();
    let http_init_pos = event_log
        .iter()
        .position(|e| e == "init:http")
        .expect("http should be initialized");
    let video_init_pos = event_log
        .iter()
        .position(|e| e == "init:video")
        .expect("video should be initialized");

    assert!(
        video_init_pos < http_init_pos,
        "video must be initialized before http (video at {}, http at {})",
        video_init_pos,
        http_init_pos
    );
}

// ── 自测相关测试 ──

/// 支持自测的 TestPlugin 变体
struct TestPluginWithSelfTest {
    id: String,
    events: Arc<Mutex<Vec<String>>>,
    caps: Vec<String>,
    test_results: Vec<CapabilityTestResult>,
}

impl TestPluginWithSelfTest {
    fn new(
        id: &str,
        events: Arc<Mutex<Vec<String>>>,
        caps: Vec<String>,
        test_results: Vec<CapabilityTestResult>,
    ) -> Self {
        Self {
            id: id.to_string(),
            events,
            caps,
            test_results,
        }
    }

    fn record(&self, entry: impl Into<String>) {
        lock_events(&self.events).push(entry.into());
    }
}

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

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

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

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

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

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

    fn handle_message(&mut self, _msg: Message) -> Result<()> {
        Ok(())
    }

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

struct FailingPlugin {
    id: String,
    events: Arc<Mutex<Vec<String>>>,
}

impl FailingPlugin {
    fn new(id: &str, events: Arc<Mutex<Vec<String>>>) -> Self {
        Self {
            id: id.to_string(),
            events,
        }
    }

    fn record(&self, entry: impl Into<String>) {
        lock_events(&self.events).push(entry.into());
    }
}

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

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

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

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

    fn handle_message(&mut self, _msg: Message) -> Result<()> {
        self.record(format!("error:{}", self.id));
        Err(anyhow::anyhow!("simulated failure"))
    }

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

fn setup_rollback_store(base: &Path, plugin_id: &str) -> VersionManager {
    let _ = fs::remove_dir_all(base);
    fs::create_dir_all(base.join(plugin_id).join("1.0.0")).unwrap();
    fs::create_dir_all(base.join(plugin_id).join("2.0.0")).unwrap();

    let loader = PluginLoader::new(base);
    let mut registry = PluginRegistry::default();
    registry.plugins.insert(
        plugin_id.to_string(),
        PluginRegistryEntry {
            active_version: "2.0.0".to_string(),
            last_stable_version: Some("1.0.0".to_string()),
            enabled: true,
            error_policy: ErrorPolicy::AutoRollback,
            max_errors: 1,
        },
    );
    loader.save_registry(&registry).unwrap();

    VersionManager::new(loader)
}

#[test]
fn self_test_all_pass_allows_normal_start() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    let plugin = TestPluginWithSelfTest::new(
        "sensor",
        events.clone(),
        vec!["temperature".into()],
        vec![CapabilityTestResult {
            capability: "temperature".into(),
            passed: true,
            message: "ok".into(),
        }],
    );

    manager.register_dynamic_with_manifest(
        Box::new(plugin),
        ErrorPolicy::DisableAndLog,
        5,
        vec!["temperature".into()],
        vec!["temperature".into()],
        true,
    );

    manager
        .start_all()
        .expect("start_all should succeed when all tests pass");

    let log = lock_events(&events);
    assert!(log.contains(&"init:sensor".to_string()));
    assert!(log.contains(&"self_test:sensor".to_string()));
    assert!(log.contains(&"start:sensor".to_string()));
}

#[test]
fn self_test_required_capability_fails_disables_dynamic_plugin() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    let plugin = TestPluginWithSelfTest::new(
        "sensor",
        events.clone(),
        vec!["temperature".into()],
        vec![CapabilityTestResult {
            capability: "temperature".into(),
            passed: false,
            message: "sensor not connected".into(),
        }],
    );

    manager.register_dynamic_with_manifest(
        Box::new(plugin),
        ErrorPolicy::DisableAndLog,
        5,
        vec!["temperature".into()],
        vec!["temperature".into()],
        true,
    );

    // Should succeed (dynamic plugin failure doesn't abort)
    manager.start_all().expect("start_all should succeed");

    let log = lock_events(&events);
    assert!(log.contains(&"init:sensor".to_string()));
    assert!(log.contains(&"self_test:sensor".to_string()));
    // start should NOT have been called
    assert!(!log.contains(&"start:sensor".to_string()));

    // Plugin should be disabled
    let states = manager.plugin_states();
    assert!(
        !states[0].enabled,
        "plugin should be disabled after required capability failure"
    );
}

#[test]
fn self_test_optional_capability_fails_still_starts() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    let plugin = TestPluginWithSelfTest::new(
        "sensor",
        events.clone(),
        vec!["temperature".into(), "humidity".into()],
        vec![
            CapabilityTestResult {
                capability: "temperature".into(),
                passed: true,
                message: "ok".into(),
            },
            CapabilityTestResult {
                capability: "humidity".into(),
                passed: false,
                message: "sensor not calibrated".into(),
            },
        ],
    );

    // Only temperature is required; humidity is optional
    manager.register_dynamic_with_manifest(
        Box::new(plugin),
        ErrorPolicy::DisableAndLog,
        5,
        vec!["temperature".into()],
        vec!["temperature".into(), "humidity".into()],
        true,
    );

    manager.start_all().expect("start_all should succeed");

    let log = lock_events(&events);
    assert!(log.contains(&"self_test:sensor".to_string()));
    assert!(
        log.contains(&"start:sensor".to_string()),
        "plugin should start despite optional failure"
    );

    // Test results should be recorded
    let states = manager.plugin_states();
    assert_eq!(states[0].test_results.len(), 2);
    assert!(states[0].test_results[0].passed);
    assert!(!states[0].test_results[1].passed);
}

#[test]
fn auto_rollback_updates_registry_and_marks_pending_when_reload_fails() {
    let tmp = std::env::temp_dir().join("showen_test_service_manager_autorollback");
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());
    manager.set_version_manager(setup_rollback_store(&tmp, "sensor"));

    manager.register_dynamic(
        Box::new(FailingPlugin::new("sensor", events.clone())),
        ErrorPolicy::AutoRollback,
        1,
    );

    manager.start_all().expect("start_all should succeed");
    let sender = manager.sender();
    sender
        .send(Envelope {
            from: "test".to_string(),
            to: Destination::Plugin("sensor".to_string()),
            message: Message::Custom {
                kind: "tick".to_string(),
                payload: "1".to_string(),
            },
        })
        .expect("failing message should send");
    sender
        .send(Envelope {
            from: "test".to_string(),
            to: Destination::Manager,
            message: Message::Shutdown,
        })
        .expect("shutdown should send");

    manager.run().expect("run should succeed");

    let registry = PluginLoader::new(&tmp).load_registry().unwrap();
    assert_eq!(registry.plugins["sensor"].active_version, "1.0.0");

    let states = manager.plugin_states();
    assert!(
        !states[0].enabled,
        "plugin should be disabled after rollback failure"
    );
    assert!(
        states[0].needs_rollback,
        "plugin should be marked for restart-time reload"
    );
    assert!(has_event(&events, "stop:sensor"));

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

#[test]
fn self_test_auto_rollback_updates_registry_and_marks_pending_when_reload_fails() {
    let tmp = std::env::temp_dir().join("showen_test_self_test_autorollback");
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());
    manager.set_version_manager(setup_rollback_store(&tmp, "sensor"));

    let plugin = TestPluginWithSelfTest::new(
        "sensor",
        events.clone(),
        vec!["temperature".into()],
        vec![CapabilityTestResult {
            capability: "temperature".into(),
            passed: false,
            message: "sensor not connected".into(),
        }],
    );

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

    manager.start_all().expect("start_all should succeed");

    let registry = PluginLoader::new(&tmp).load_registry().unwrap();
    assert_eq!(registry.plugins["sensor"].active_version, "1.0.0");

    let states = manager.plugin_states();
    assert!(
        !states[0].enabled,
        "plugin should stay disabled after failed self-test"
    );
    assert!(
        states[0].needs_rollback,
        "plugin should be marked for restart-time reload when rollback reload fails"
    );

    let log = lock_events(&events);
    assert!(log.contains(&"init:sensor".to_string()));
    assert!(log.contains(&"self_test:sensor".to_string()));
    assert!(
        !log.contains(&"start:sensor".to_string()),
        "plugin should not start after failed required capability"
    );

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

#[test]
fn message_config_reload_request_round_trips_through_json() {
    let json = serde_json::to_string(&Message::ConfigReloadRequest)
        .expect("ConfigReloadRequest should serialize");
    let message: Message =
        serde_json::from_str(&json).expect("ConfigReloadRequest should deserialize");

    assert!(matches!(message, Message::ConfigReloadRequest));
}

#[test]
fn message_config_reloaded_round_trips_through_json() {
    let config = test_config();
    let json = serde_json::to_string(&Message::ConfigReloaded(config.clone()))
        .expect("ConfigReloaded should serialize");
    let message: Message = serde_json::from_str(&json).expect("ConfigReloaded should deserialize");

    match message {
        Message::ConfigReloaded(decoded) => {
            assert_eq!(decoded.display.window_title, config.display.window_title);
            assert_eq!(decoded.playlist.len(), config.playlist.len());
            assert_eq!(decoded.remote_control.port, config.remote_control.port);
            assert_eq!(decoded.source_path, config.source_path);
            assert_eq!(decoded.source_dir, config.source_dir);
        }
        other => panic!("unexpected message after round trip: {:?}", other),
    }
}

fn unique_test_dir(name: &str) -> std::path::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_{name}_{}_{}", std::process::id(), nanos))
}

fn assert_message_json_round_trip(message: Message) {
    let expected = serde_json::to_value(&message).expect("message should serialize to value");
    let decoded: Message =
        serde_json::from_value(expected.clone()).expect("message should deserialize from value");
    let actual = serde_json::to_value(decoded).expect("decoded message should serialize");
    assert_eq!(actual, expected);
}

// NOTE: 动态插件 null vtable 测试已移除 —— 在测试环境中编译动态 .so 不可靠
// 该行为已通过 DynamicPlugin::read_plugin_string 的 null 检查保证安全

#[test]
fn discover_plugins_skips_invalid_manifest_and_keeps_valid_entries() {
    let tmp = unique_test_dir("discover_invalid_manifest");
    fs::create_dir_all(tmp.join("valid-plugin").join("1.0.0"))
        .expect("valid plugin dir should be created");
    fs::create_dir_all(tmp.join("broken-plugin").join("1.0.0"))
        .expect("broken plugin dir should be created");

    fs::write(
        tmp.join("valid-plugin").join("1.0.0").join("manifest.json"),
        r#"{
            "id": "valid-plugin",
            "version": "1.0.0",
            "sdk_version": "0.2.0",
            "so_filename": "libvalid_plugin.so"
        }"#,
    )
    .expect("valid manifest should be written");
    fs::write(
        tmp.join("broken-plugin")
            .join("1.0.0")
            .join("manifest.json"),
        r#"{"id": "broken-plugin", "version": }"#,
    )
    .expect("invalid manifest should be written");

    let loader = PluginLoader::new(&tmp);
    let manifests = loader
        .discover_plugins()
        .expect("invalid manifest should be skipped, not returned as error");

    assert_eq!(manifests.len(), 1);
    assert_eq!(manifests[0].id, "valid-plugin");
    assert_eq!(manifests[0].version, "1.0.0");

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

#[test]
fn handle_message_skips_disabled_plugins() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
    manager.register(Box::new(TestPlugin::new("beta", vec![], events.clone())));
    manager.start_all().expect("start_all should succeed");
    manager
        .set_plugin_enabled("beta", false)
        .expect("beta should be disabled");

    let sender = manager.sender();
    sender
        .send(Envelope {
            from: "alpha".to_string(),
            to: Destination::Plugin("beta".to_string()),
            message: Message::Custom {
                kind: "direct".to_string(),
                payload: "ignored".to_string(),
            },
        })
        .expect("direct message should send");
    sender
        .send(Envelope {
            from: "test".to_string(),
            to: Destination::Manager,
            message: Message::Shutdown,
        })
        .expect("shutdown should send");

    manager.run().expect("run should succeed");

    assert!(!has_event(&events, "msg:beta:custom:direct:ignored"));
    assert!(!manager.plugin_states()[1].enabled);
}

#[test]
fn set_plugin_enabled_runs_full_lifecycle() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());
    let (plugin, _plan) =
        LifecyclePlugin::new("beta", "base", events.clone(), PluginFailurePlan::default());

    manager.register(Box::new(plugin));
    manager.start_all().expect("start_all should succeed");

    manager
        .set_plugin_enabled("beta", false)
        .expect("disable should stop plugin");
    manager
        .set_plugin_enabled("beta", true)
        .expect("enable should re-init and restart plugin");

    let events = non_plugin_state_events(&events);
    assert_eq!(
        events,
        vec![
            "init:beta:base",
            "start:beta:base",
            "stop:beta:base",
            "init:beta:base",
            "start:beta:base",
        ]
    );
    assert!(manager.plugin_states()[0].enabled);
}

#[test]
fn set_plugin_enabled_rolls_back_to_disabled_when_restart_fails() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());
    let (plugin, plan) =
        LifecyclePlugin::new("beta", "base", events.clone(), PluginFailurePlan::default());

    manager.register(Box::new(plugin));
    manager.start_all().expect("start_all should succeed");
    manager
        .set_plugin_enabled("beta", false)
        .expect("disable should succeed");
    plan.lock().expect("plan mutex poisoned").fail_start = true;

    let error = manager
        .set_plugin_enabled("beta", true)
        .expect_err("failed start should keep plugin disabled");
    assert!(error.to_string().contains("failed to enable plugin 'beta'"));

    let events = non_plugin_state_events(&events);
    assert_eq!(
        events,
        vec![
            "init:beta:base",
            "start:beta:base",
            "stop:beta:base",
            "init:beta:base",
            "start:beta:base",
            "stop:beta:base",
        ]
    );
    assert!(!manager.plugin_states()[0].enabled);
}

#[test]
fn start_all_broadcasts_initial_plugin_states() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
    manager.register(Box::new(TestPlugin::new("beta", vec![], events.clone())));

    manager.start_all().expect("start_all should succeed");

    let states = latest_plugin_states(&events, "alpha");
    assert_eq!(states.len(), 2);
    assert!(states.iter().all(|state| state.enabled));
}

#[test]
fn custom_plugin_disable_command_disables_plugin_and_broadcasts_states() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
    manager.register(Box::new(
        LifecyclePlugin::new("beta", "base", events.clone(), PluginFailurePlan::default()).0,
    ));
    manager.start_all().expect("start_all should succeed");
    clear_events(&events);

    let sender = manager.sender();
    sender
        .send(Envelope {
            from: "http".to_string(),
            to: Destination::Manager,
            message: Message::Custom {
                kind: "plugin_disable".to_string(),
                payload: "beta".to_string(),
            },
        })
        .expect("disable should send");
    sender
        .send(Envelope {
            from: "test".to_string(),
            to: Destination::Manager,
            message: Message::Shutdown,
        })
        .expect("shutdown should send");

    manager.run().expect("run should succeed");

    let states = latest_plugin_states(&events, "alpha");
    let beta = states.iter().find(|state| state.id == "beta").unwrap();
    assert!(!beta.enabled);
    assert!(has_event(&events, "stop:beta:base"));
}

#[test]
fn custom_plugin_enable_command_enables_plugin_and_broadcasts_states() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());
    let (plugin, _plan) =
        LifecyclePlugin::new("beta", "base", events.clone(), PluginFailurePlan::default());

    manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
    manager.register(Box::new(plugin));
    manager.start_all().expect("start_all should succeed");
    manager
        .set_plugin_enabled("beta", false)
        .expect("disable should succeed");
    clear_events(&events);

    let sender = manager.sender();
    sender
        .send(Envelope {
            from: "http".to_string(),
            to: Destination::Manager,
            message: Message::Custom {
                kind: "plugin_enable".to_string(),
                payload: "beta".to_string(),
            },
        })
        .expect("enable should send");
    sender
        .send(Envelope {
            from: "test".to_string(),
            to: Destination::Manager,
            message: Message::Shutdown,
        })
        .expect("shutdown should send");

    manager.run().expect("run should succeed");

    let states = latest_plugin_states(&events, "alpha");
    let beta = states.iter().find(|state| state.id == "beta").unwrap();
    assert!(beta.enabled);
    assert!(has_event(&events, "init:beta:base"));
    assert!(has_event(&events, "start:beta:base"));
}

#[test]
fn custom_plugin_rollback_command_rolls_back_registry_and_broadcasts_states() {
    let tmp = unique_test_dir("custom_plugin_rollback");
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());
    manager.set_version_manager(setup_rollback_store(&tmp, "sensor"));

    manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
    manager.register_dynamic(
        Box::new(FailingPlugin::new("sensor", events.clone())),
        ErrorPolicy::AutoRollback,
        1,
    );
    manager.start_all().expect("start_all should succeed");
    clear_events(&events);

    let sender = manager.sender();
    sender
        .send(Envelope {
            from: "http".to_string(),
            to: Destination::Manager,
            message: Message::Custom {
                kind: "plugin_rollback".to_string(),
                payload: "sensor".to_string(),
            },
        })
        .expect("rollback should send");
    sender
        .send(Envelope {
            from: "test".to_string(),
            to: Destination::Manager,
            message: Message::Shutdown,
        })
        .expect("shutdown should send");

    manager.run().expect("run should succeed");

    let registry = PluginLoader::new(&tmp)
        .load_registry()
        .expect("registry should load");
    assert_eq!(registry.plugins["sensor"].active_version, "1.0.0");

    let states = latest_plugin_states(&events, "alpha");
    let sensor = states.iter().find(|state| state.id == "sensor").unwrap();
    assert!(sensor.needs_rollback);

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

#[test]
fn custom_plugin_switch_command_with_invalid_payload_still_broadcasts_states() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
    manager.start_all().expect("start_all should succeed");
    clear_events(&events);

    let sender = manager.sender();
    sender
        .send(Envelope {
            from: "http".to_string(),
            to: Destination::Manager,
            message: Message::Custom {
                kind: "plugin_switch".to_string(),
                payload: "not-json".to_string(),
            },
        })
        .expect("switch should send");
    sender
        .send(Envelope {
            from: "test".to_string(),
            to: Destination::Manager,
            message: Message::Shutdown,
        })
        .expect("shutdown should send");

    manager.run().expect("run should succeed");

    let states = latest_plugin_states(&events, "alpha");
    assert_eq!(states.len(), 1);
    assert_eq!(states[0].id, "alpha");
}

#[test]
fn custom_plugin_install_command_without_version_manager_still_broadcasts_states() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
    manager.start_all().expect("start_all should succeed");
    clear_events(&events);

    let sender = manager.sender();
    sender
        .send(Envelope {
            from: "http".to_string(),
            to: Destination::Manager,
            message: Message::Custom {
                kind: "plugin_install".to_string(),
                payload: serde_json::json!({ "id": "weather" }).to_string(),
            },
        })
        .expect("install should send");
    sender
        .send(Envelope {
            from: "test".to_string(),
            to: Destination::Manager,
            message: Message::Shutdown,
        })
        .expect("shutdown should send");

    manager.run().expect("run should succeed");

    let states = latest_plugin_states(&events, "alpha");
    assert_eq!(states.len(), 1);
    assert_eq!(states[0].id, "alpha");
}

#[test]
fn custom_plugin_check_updates_command_without_version_manager_still_broadcasts_states() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());

    manager.register(Box::new(TestPlugin::new("alpha", vec![], events.clone())));
    manager.start_all().expect("start_all should succeed");
    clear_events(&events);

    let sender = manager.sender();
    sender
        .send(Envelope {
            from: "http".to_string(),
            to: Destination::Manager,
            message: Message::Custom {
                kind: "plugin_check_updates".to_string(),
                payload: String::new(),
            },
        })
        .expect("check updates should send");
    sender
        .send(Envelope {
            from: "test".to_string(),
            to: Destination::Manager,
            message: Message::Shutdown,
        })
        .expect("shutdown should send");

    manager.run().expect("run should succeed");

    let states = latest_plugin_states(&events, "alpha");
    assert_eq!(states.len(), 1);
    assert_eq!(states[0].id, "alpha");
}

#[test]
fn replace_dynamic_plugin_stops_old_before_starting_new() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());
    let (old_plugin, _old_plan) = LifecyclePlugin::new(
        "sensor",
        "old",
        events.clone(),
        PluginFailurePlan::default(),
    );
    let (new_plugin, _new_plan) = LifecyclePlugin::new(
        "sensor",
        "new",
        events.clone(),
        PluginFailurePlan::default(),
    );

    manager.register_dynamic(Box::new(old_plugin), ErrorPolicy::DisableAndLog, 5);
    manager.start_all().expect("start_all should succeed");

    manager
        .replace_dynamic_plugin(
            "sensor",
            Box::new(new_plugin),
            ErrorPolicy::DisableAndLog,
            5,
        )
        .expect("replace should succeed");

    let events = non_plugin_state_events(&events);
    assert_eq!(
        events,
        vec![
            "init:sensor:old",
            "start:sensor:old",
            "stop:sensor:old",
            "init:sensor:new",
            "start:sensor:new",
        ]
    );
}

#[test]
fn replace_dynamic_plugin_restores_old_plugin_when_new_start_fails() {
    let events = Arc::new(Mutex::new(Vec::new()));
    let mut manager = ServiceManager::new(test_config());
    let (old_plugin, _old_plan) = LifecyclePlugin::new(
        "sensor",
        "old",
        events.clone(),
        PluginFailurePlan::default(),
    );
    let (new_plugin, _new_plan) = LifecyclePlugin::new(
        "sensor",
        "new",
        events.clone(),
        PluginFailurePlan {
            fail_start: true,
            ..PluginFailurePlan::default()
        },
    );

    manager.register_dynamic(Box::new(old_plugin), ErrorPolicy::DisableAndLog, 5);
    manager.start_all().expect("start_all should succeed");

    let error = manager
        .replace_dynamic_plugin(
            "sensor",
            Box::new(new_plugin),
            ErrorPolicy::DisableAndLog,
            5,
        )
        .expect_err("failed replacement should restore old plugin");
    assert!(error.to_string().contains("restored previous plugin"));

    let events = non_plugin_state_events(&events);
    assert_eq!(
        events,
        vec![
            "init:sensor:old",
            "start:sensor:old",
            "stop:sensor:old",
            "init:sensor:new",
            "start:sensor:new",
            "stop:sensor:new",
            "init:sensor:old",
            "start:sensor:old",
        ]
    );
    assert!(manager.plugin_states()[0].enabled);
}

#[test]
fn rollback_without_stable_version_returns_error_and_keeps_active_version() {
    let tmp = unique_test_dir("rollback_without_stable");
    fs::create_dir_all(tmp.join("sensor").join("2.0.0")).expect("version dir should be created");

    let loader = PluginLoader::new(&tmp);
    let mut registry = PluginRegistry::default();
    registry.plugins.insert(
        "sensor".to_string(),
        PluginRegistryEntry {
            active_version: "2.0.0".to_string(),
            last_stable_version: None,
            enabled: true,
            error_policy: ErrorPolicy::AutoRollback,
            max_errors: 1,
        },
    );
    loader
        .save_registry(&registry)
        .expect("registry should be written");

    let version_manager = VersionManager::new(loader);
    let error = version_manager
        .rollback("sensor")
        .expect_err("missing stable version should fail");
    assert!(error
        .to_string()
        .contains("plugin 'sensor' has no stable version to rollback to"));

    let registry = version_manager
        .loader()
        .load_registry()
        .expect("registry should still load");
    assert_eq!(registry.plugins["sensor"].active_version, "2.0.0");
    assert!(registry.plugins["sensor"].last_stable_version.is_none());

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

#[test]
fn all_message_variants_round_trip_through_json() {
    let config = test_config();
    let messages = vec![
        Message::PlayerCommand(super::message::PlayerCommand::Play),
        Message::PlayerCommand(super::message::PlayerCommand::Pause),
        Message::PlayerCommand(super::message::PlayerCommand::Next),
        Message::PlayerCommand(super::message::PlayerCommand::Previous),
        Message::PlayerCommand(super::message::PlayerCommand::Goto(3)),
        Message::PlayerCommand(super::message::PlayerCommand::ChangeScene(
            "intro".to_string(),
        )),
        Message::PlayerStatus(super::message::PlayerStatusData {
            running: true,
            paused: false,
            in_transition: true,
            current_index: 2,
            playlist_length: 5,
            current_video: Some("video.mp4".to_string()),
        }),
        Message::Trigger {
            name: "motion".to_string(),
            value: "detected".to_string(),
        },
        Message::StateChanged {
            old_state: "idle".to_string(),
            new_state: "playing".to_string(),
        },
        Message::ScreenLockRequest(true),
        Message::CursorVisibility(false),
        Message::WifiCommand(super::message::WifiCommand::Scan),
        Message::WifiCommand(super::message::WifiCommand::Connect {
            ssid: "lab".to_string(),
            password: "secret".to_string(),
        }),
        Message::WifiCommand(super::message::WifiCommand::Status),
        Message::WifiCommand(super::message::WifiCommand::ApStart {
            ssid: "showen-ap".to_string(),
            password: "password".to_string(),
        }),
        Message::WifiCommand(super::message::WifiCommand::ApStop),
        Message::WifiResult("connected".to_string()),
        Message::WifiProvisioned {
            ssid: "lab".to_string(),
            ip: "192.168.1.10".to_string(),
        },
        Message::ConfigReloaded(config),
        Message::ConfigReloadRequest,
        Message::Shutdown,
        Message::PluginReady("sensor".to_string()),
        Message::Custom {
            kind: "health".to_string(),
            payload: "ok".to_string(),
        },
    ];

    for message in messages {
        assert_message_json_round_trip(message);
    }
}
