use crate::core::message::{Destination, Envelope, Message, PlayerCommand, WifiCommand};
use crate::core::plugin_ids;

/// 命令解析结果
pub struct DispatchResult {
    pub envelope: Envelope,
}

/// 解析文本命令为 DispatchResult。
///
/// `ssid_hint` / `password_hint` 仅用于不含参数的 `connect` / `ap_start`（BLE 先写
/// SSID/Password 特征，再写 Command）。带参数版本 `connect:SSID:PASS` 直接解析内联值。
pub fn parse_command(
    command: &str,
    from: &str,
    ssid_hint: &str,
    password_hint: &str,
) -> Result<DispatchResult, String> {
    let command = command.trim();
    if command.is_empty() {
        return Err("empty command".into());
    }

    // 尝试按冒号拆分以获取命令名和参数
    let (cmd, rest) = match command.find(':') {
        Some(pos) => (&command[..pos], Some(&command[pos + 1..])),
        None => (command, None),
    };

    match cmd {
        // ── 播放控制 ──
        "play" => ok_video(from, Message::PlayerCommand(PlayerCommand::Play)),
        "pause" => ok_video(from, Message::PlayerCommand(PlayerCommand::Pause)),
        "next" => ok_video(from, Message::PlayerCommand(PlayerCommand::Next)),
        "prev" => ok_video(from, Message::PlayerCommand(PlayerCommand::Previous)),

        "goto" => {
            let index_str = rest.ok_or("goto requires index (e.g. goto:3)")?;
            let index: usize = index_str
                .parse()
                .map_err(|_| format!("invalid index: {index_str}"))?;
            ok_video(from, Message::PlayerCommand(PlayerCommand::Goto(index)))
        }

        "scene" => {
            let name = rest.ok_or("scene requires name (e.g. scene:idle)")?;
            if name.is_empty() {
                return Err("scene name cannot be empty".into());
            }
            ok_video(
                from,
                Message::PlayerCommand(PlayerCommand::ChangeScene(name.to_string())),
            )
        }

        "trigger" => {
            let params = rest.ok_or("trigger requires name:value (e.g. trigger:voice:name)")?;
            let (name, value) = match params.find(':') {
                Some(pos) => (&params[..pos], &params[pos + 1..]),
                None => (params, ""),
            };
            if name.is_empty() {
                return Err("trigger name cannot be empty".into());
            }
            ok_video(
                from,
                Message::Trigger {
                    name: name.to_string(),
                    value: value.to_string(),
                },
            )
        }

        // ── WiFi 命令 ──
        "scan" => ok_wifi(from, Message::WifiCommand(WifiCommand::Scan)),
        "status" => ok_wifi(from, Message::WifiCommand(WifiCommand::Status)),

        "connect" => {
            let (ssid, password) = parse_wifi_credentials(rest, ssid_hint, password_hint);
            if ssid.is_empty() {
                return Err("ssid required for connect".into());
            }
            ok_wifi(
                from,
                Message::WifiCommand(WifiCommand::Connect { ssid, password }),
            )
        }

        "ap_start" => {
            let (ssid, password) = parse_wifi_credentials(rest, ssid_hint, password_hint);
            if ssid.is_empty() {
                return Err("ssid required for ap_start".into());
            }
            ok_wifi(
                from,
                Message::WifiCommand(WifiCommand::ApStart { ssid, password }),
            )
        }

        "ap_stop" => ok_wifi(from, Message::WifiCommand(WifiCommand::ApStop)),

        // ── 配置 ──
        "config_reload" => Ok(DispatchResult {
            envelope: Envelope {
                from: from.to_string(),
                to: Destination::Manager,
                message: Message::ConfigReloadRequest,
            },
        }),

        _ => Err(format!("unsupported command: {cmd}")),
    }
}

/// 解析 WiFi 凭据：优先使用内联参数 `SSID:PASS`，回退到 hint 值。
/// 密码中可能包含冒号，因此用 `splitn(2, ':')` 拆分。
fn parse_wifi_credentials(
    rest: Option<&str>,
    ssid_hint: &str,
    password_hint: &str,
) -> (String, String) {
    match rest {
        Some(params) if !params.is_empty() => {
            let mut parts = params.splitn(2, ':');
            let ssid = parts.next().unwrap_or("").to_string();
            let password = parts.next().unwrap_or("").to_string();
            if ssid.is_empty() {
                (ssid_hint.to_string(), password_hint.to_string())
            } else {
                (ssid, password)
            }
        }
        _ => (ssid_hint.to_string(), password_hint.to_string()),
    }
}

fn ok_video(from: &str, message: Message) -> Result<DispatchResult, String> {
    Ok(DispatchResult {
        envelope: Envelope {
            from: from.to_string(),
            to: Destination::Plugin(plugin_ids::VIDEO.to_string()),
            message,
        },
    })
}

fn ok_wifi(from: &str, message: Message) -> Result<DispatchResult, String> {
    Ok(DispatchResult {
        envelope: Envelope {
            from: from.to_string(),
            to: Destination::Plugin(plugin_ids::WIFI.to_string()),
            message,
        },
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    fn dispatch(cmd: &str) -> Result<DispatchResult, String> {
        parse_command(cmd, "test", "", "")
    }

    fn dispatch_with_hints(cmd: &str, ssid: &str, pass: &str) -> Result<DispatchResult, String> {
        parse_command(cmd, "test", ssid, pass)
    }

    #[test]
    fn test_player_commands() {
        assert!(matches!(
            dispatch("play").unwrap().envelope.message,
            Message::PlayerCommand(PlayerCommand::Play)
        ));
        assert!(matches!(
            dispatch("pause").unwrap().envelope.message,
            Message::PlayerCommand(PlayerCommand::Pause)
        ));
        assert!(matches!(
            dispatch("next").unwrap().envelope.message,
            Message::PlayerCommand(PlayerCommand::Next)
        ));
        assert!(matches!(
            dispatch("prev").unwrap().envelope.message,
            Message::PlayerCommand(PlayerCommand::Previous)
        ));
    }

    #[test]
    fn test_goto() {
        let result = dispatch("goto:5").unwrap();
        assert!(matches!(
            result.envelope.message,
            Message::PlayerCommand(PlayerCommand::Goto(5))
        ));
        assert!(dispatch("goto").is_err());
        assert!(dispatch("goto:abc").is_err());
    }

    #[test]
    fn test_scene() {
        let result = dispatch("scene:idle").unwrap();
        assert!(matches!(
            result.envelope.message,
            Message::PlayerCommand(PlayerCommand::ChangeScene(ref name)) if name == "idle"
        ));
        assert!(dispatch("scene").is_err());
        assert!(dispatch("scene:").is_err());
    }

    #[test]
    fn test_trigger() {
        let result = dispatch("trigger:voice:name").unwrap();
        assert!(matches!(
            result.envelope.message,
            Message::Trigger { ref name, ref value } if name == "voice" && value == "name"
        ));

        // trigger without value
        let result = dispatch("trigger:button").unwrap();
        assert!(matches!(
            result.envelope.message,
            Message::Trigger { ref name, ref value } if name == "button" && value == ""
        ));

        assert!(dispatch("trigger").is_err());
        assert!(dispatch("trigger:").is_err());
    }

    #[test]
    fn test_wifi_scan_status() {
        assert!(matches!(
            dispatch("scan").unwrap().envelope.message,
            Message::WifiCommand(WifiCommand::Scan)
        ));
        assert!(matches!(
            dispatch("status").unwrap().envelope.message,
            Message::WifiCommand(WifiCommand::Status)
        ));
    }

    #[test]
    fn test_connect_with_hint() {
        let result = dispatch_with_hints("connect", "MySSID", "pass123").unwrap();
        assert!(matches!(
            result.envelope.message,
            Message::WifiCommand(WifiCommand::Connect { ref ssid, ref password })
                if ssid == "MySSID" && password == "pass123"
        ));
    }

    #[test]
    fn test_connect_inline() {
        let result = dispatch("connect:MySSID:p@ss:w0rd").unwrap();
        assert!(matches!(
            result.envelope.message,
            Message::WifiCommand(WifiCommand::Connect { ref ssid, ref password })
                if ssid == "MySSID" && password == "p@ss:w0rd"
        ));
    }

    #[test]
    fn test_connect_no_ssid() {
        assert!(dispatch("connect").is_err());
    }

    #[test]
    fn test_ap_start_inline() {
        let result = dispatch("ap_start:showen:12345678").unwrap();
        assert!(matches!(
            result.envelope.message,
            Message::WifiCommand(WifiCommand::ApStart { ref ssid, ref password })
                if ssid == "showen" && password == "12345678"
        ));
    }

    #[test]
    fn test_ap_stop() {
        assert!(matches!(
            dispatch("ap_stop").unwrap().envelope.message,
            Message::WifiCommand(WifiCommand::ApStop)
        ));
    }

    #[test]
    fn test_config_reload() {
        let result = dispatch("config_reload").unwrap();
        assert!(matches!(
            result.envelope.message,
            Message::ConfigReloadRequest
        ));
        assert!(matches!(result.envelope.to, Destination::Manager));
    }

    #[test]
    fn test_routing() {
        let r = dispatch("play").unwrap();
        assert!(matches!(r.envelope.to, Destination::Plugin(ref id) if id == "video"));

        let r = dispatch("scan").unwrap();
        assert!(matches!(r.envelope.to, Destination::Plugin(ref id) if id == "wifi"));
    }

    #[test]
    fn test_unsupported() {
        assert!(dispatch("unknown").is_err());
        assert!(dispatch("").is_err());
    }

    #[test]
    fn test_whitespace_trimmed() {
        assert!(matches!(
            dispatch("  play  ").unwrap().envelope.message,
            Message::PlayerCommand(PlayerCommand::Play)
        ));
    }
}
