use anyhow::Result;
use showen_v2::core::config::AppConfig;
use showen_v2::core::message::{Destination, Envelope, Message, PlayerStatusData};
use showen_v2::core::plugin::{Platform, Plugin, PluginContext, PluginInfo};
use showen_v2::core::service_manager::ServiceManager;
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_service_manager_{name}_{}_{}",
        std::process::id(),
        nanos
    ))
}

fn config_json(window_title: &str) -> String {
    format!(
        r#"{{
            "display": {{
                "fullscreen": false,
                "window_title": "{window_title}",
                "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
            }}
        }}"#
    )
}

fn write_test_config(dir: &Path, window_title: &str) -> PathBuf {
    fs::create_dir_all(dir).expect("test dir should be created");
    let config_path = dir.join("config.json");
    fs::write(&config_path, config_json(window_title)).expect("config should be written");
    config_path
}

fn test_manager(name: &str) -> (ServiceManager, Arc<Mutex<Vec<String>>>, PathBuf) {
    let dir = unique_test_dir(name);
    let config_path = write_test_config(&dir, "initial-title");
    let config = AppConfig::from_file(&config_path).expect("test config should load");
    (
        ServiceManager::new(config),
        Arc::new(Mutex::new(Vec::new())),
        dir,
    )
}

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 event_position(events: &Arc<Mutex<Vec<String>>>, expected: &str) -> usize {
    lock_events(events)
        .iter()
        .position(|event| event == expected)
        .unwrap_or_else(|| panic!("missing expected event: {}", expected))
}

fn message_label(message: &Message) -> String {
    match message {
        Message::Shutdown => "shutdown".to_string(),
        Message::ConfigReloadRequest => "config_reload_request".to_string(),
        Message::ConfigReloaded(config) => {
            format!("config_reloaded:{}", config.display.window_title)
        }
        Message::PlayerStatus(status) => format!(
            "player_status:{}:{}:{}:{}:{}:{}",
            status.running,
            status.paused,
            status.in_transition,
            status.current_index,
            status.playlist_length,
            status.current_video.as_deref().unwrap_or("none")
        ),
        Message::WifiResult(payload) => format!("wifi_result:{payload}"),
        Message::StateChanged {
            old_state,
            new_state,
        } => format!("state_changed:{old_state}->{new_state}"),
        Message::PluginReady(id) => format!("plugin_ready:{id}"),
        Message::Custom { kind, payload } => format!("custom:{kind}:{payload}"),
        other => format!("other:{other:?}"),
    }
}

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

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

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

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

    fn info(&self) -> PluginInfo {
        PluginInfo {
            name: self.id.clone(),
            version: "test".to_string(),
            description: "integration 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 test_startup_order_matches_dependency_sort() {
    let (mut manager, events, dir) = test_manager("startup_order");

    manager.register(Box::new(RecordingPlugin::new(
        "dashboard",
        vec!["http", "screen"],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "screen",
        vec!["device"],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "http",
        vec!["video"],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "wifi",
        vec![],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "video",
        vec![],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "device",
        vec![],
        events.clone(),
    )));

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

    assert!(event_position(&events, "init:device") < event_position(&events, "init:screen"));
    assert!(event_position(&events, "init:video") < event_position(&events, "init:http"));
    assert!(event_position(&events, "init:screen") < event_position(&events, "init:dashboard"));
    assert!(event_position(&events, "init:http") < event_position(&events, "init:dashboard"));
    assert!(event_position(&events, "start:device") < event_position(&events, "start:screen"));
    assert!(event_position(&events, "start:video") < event_position(&events, "start:http"));
    assert!(event_position(&events, "start:screen") < event_position(&events, "start:dashboard"));
    assert!(event_position(&events, "start:http") < event_position(&events, "start:dashboard"));

    fs::remove_dir_all(dir).expect("test dir should be removed");
}

#[test]
fn test_shutdown_stops_all_enabled_plugins() {
    let (mut manager, events, dir) = test_manager("shutdown_stop_all");

    manager.register(Box::new(RecordingPlugin::new(
        "alpha",
        vec![],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "beta",
        vec![],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "gamma",
        vec![],
        events.clone(),
    )));
    manager.start_all().expect("start_all should succeed");

    let sender = manager.sender();
    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:shutdown"));
    assert!(has_event(&events, "msg:beta:shutdown"));
    assert!(has_event(&events, "msg:gamma:shutdown"));
    assert!(event_position(&events, "stop:gamma") < event_position(&events, "stop:beta"));
    assert!(event_position(&events, "stop:beta") < event_position(&events, "stop:alpha"));

    fs::remove_dir_all(dir).expect("test dir should be removed");
}

#[test]
fn test_config_reload_broadcasts_new_config() {
    let (mut manager, events, dir) = test_manager("config_reload");

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

    let config_path = dir.join("config.json");
    fs::write(&config_path, config_json("reloaded-title")).expect("config should be updated");

    let sender = manager.sender();
    sender
        .send(Envelope {
            from: "http".to_string(),
            to: Destination::Manager,
            message: Message::ConfigReloadRequest,
        })
        .expect("config reload request 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:config_reloaded:reloaded-title"
    ));
    assert!(has_event(
        &events,
        "msg:beta:config_reloaded:reloaded-title"
    ));

    fs::remove_dir_all(dir).expect("test dir should be removed");
}

#[test]
fn test_player_status_broadcast() {
    let (mut manager, events, dir) = test_manager("player_status");

    manager.register(Box::new(RecordingPlugin::new(
        "alpha",
        vec![],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "beta",
        vec![],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "gamma",
        vec![],
        events.clone(),
    )));
    manager.start_all().expect("start_all should succeed");

    let sender = manager.sender();
    sender
        .send(Envelope {
            from: "video".to_string(),
            to: Destination::Manager,
            message: Message::PlayerStatus(PlayerStatusData {
                running: true,
                paused: false,
                in_transition: true,
                current_index: 2,
                playlist_length: 5,
                current_video: Some("intro.mp4".to_string()),
            }),
        })
        .expect("player status 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 expected = "player_status:true:false:true:2:5:intro.mp4";
    assert!(has_event(&events, &format!("msg:alpha:{expected}")));
    assert!(has_event(&events, &format!("msg:beta:{expected}")));
    assert!(has_event(&events, &format!("msg:gamma:{expected}")));

    fs::remove_dir_all(dir).expect("test dir should be removed");
}

#[test]
fn test_wifi_result_broadcast() {
    let (mut manager, events, dir) = test_manager("wifi_result");

    manager.register(Box::new(RecordingPlugin::new(
        "alpha",
        vec![],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "beta",
        vec![],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "gamma",
        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:ssid=showen-lab".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");

    let expected = "wifi_result:connected:ssid=showen-lab";
    assert!(has_event(&events, &format!("msg:alpha:{expected}")));
    assert!(has_event(&events, &format!("msg:beta:{expected}")));
    assert!(has_event(&events, &format!("msg:gamma:{expected}")));

    fs::remove_dir_all(dir).expect("test dir should be removed");
}

#[test]
fn test_state_changed_broadcast() {
    let (mut manager, events, dir) = test_manager("state_changed");

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

    let sender = manager.sender();
    sender
        .send(Envelope {
            from: "video".to_string(),
            to: Destination::Manager,
            message: Message::StateChanged {
                old_state: "idle".to_string(),
                new_state: "playing".to_string(),
            },
        })
        .expect("state changed 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:state_changed:idle->playing"));
    assert!(has_event(&events, "msg:beta:state_changed:idle->playing"));

    fs::remove_dir_all(dir).expect("test dir should be removed");
}

#[test]
fn test_plugin_ready_broadcast() {
    let (mut manager, events, dir) = test_manager("plugin_ready");

    manager.register(Box::new(RecordingPlugin::new(
        "alpha",
        vec![],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "beta",
        vec![],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "gamma",
        vec![],
        events.clone(),
    )));
    manager.start_all().expect("start_all should succeed");

    let sender = manager.sender();
    sender
        .send(Envelope {
            from: "video".to_string(),
            to: Destination::Manager,
            message: Message::PluginReady("video".to_string()),
        })
        .expect("plugin ready 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:plugin_ready:video"));
    assert!(has_event(&events, "msg:beta:plugin_ready:video"));
    assert!(has_event(&events, "msg:gamma:plugin_ready:video"));

    fs::remove_dir_all(dir).expect("test dir should be removed");
}

#[test]
fn test_disabled_plugin_skipped_in_message_routing() {
    let (mut manager, events, dir) = test_manager("disabled_plugin_skip");

    manager.register(Box::new(RecordingPlugin::new(
        "alpha",
        vec![],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "beta",
        vec![],
        events.clone(),
    )));
    manager.register(Box::new(RecordingPlugin::new(
        "gamma",
        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: "video".to_string(),
            to: Destination::Manager,
            message: Message::PlayerStatus(PlayerStatusData {
                running: true,
                paused: true,
                in_transition: false,
                current_index: 1,
                playlist_length: 3,
                current_video: Some("paused.mp4".to_string()),
            }),
        })
        .expect("player status 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 expected = "player_status:true:true:false:1:3:paused.mp4";
    assert!(has_event(&events, &format!("msg:alpha:{expected}")));
    assert!(has_event(&events, &format!("msg:gamma:{expected}")));
    assert!(!has_event(&events, &format!("msg:beta:{expected}")));
    assert!(!manager.plugin_states()[1].enabled);

    fs::remove_dir_all(dir).expect("test dir should be removed");
}
