use night_fury_core::{BrowserSession, MouseButton};
use serde_json::json;

use night_fury_cli_core::output::{output_err, output_ok, OutputMode};

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(super) enum MouseSub {
    Move,
    Wheel,
    Down,
    Up,
    Unknown,
}

pub(super) fn resolve_mouse_sub(s: &str) -> MouseSub {
    match s {
        "move" => MouseSub::Move,
        "wheel" => MouseSub::Wheel,
        "down" => MouseSub::Down,
        "up" => MouseSub::Up,
        _ => MouseSub::Unknown,
    }
}

pub async fn dispatch(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let sub = parts.first().copied().unwrap_or("");
    match resolve_mouse_sub(sub) {
        MouseSub::Move => mouse_move(session, &parts[1..], mode).await,
        MouseSub::Wheel => mouse_wheel(session, &parts[1..], mode).await,
        MouseSub::Down => mouse_down(session, &parts[1..], mode).await,
        MouseSub::Up => mouse_up(session, &parts[1..], mode).await,
        MouseSub::Unknown => output_err(
            mode,
            &format!("Unknown mouse subcommand: {sub}. Use 'move', 'wheel', 'down', or 'up'"),
        ),
    }
}

fn parse_button(s: &str) -> MouseButton {
    match s.to_lowercase().as_str() {
        "right" => MouseButton::Right,
        "middle" => MouseButton::Middle,
        _ => MouseButton::Left,
    }
}

async fn mouse_move(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    if parts.len() < 2 {
        output_err(mode, "Usage: mouse move <x> <y>");
        return;
    }
    let x: f64 = match parts[0].parse() {
        Ok(v) => v,
        Err(_) => {
            output_err(mode, "x must be a number");
            return;
        }
    };
    let y: f64 = match parts[1].parse() {
        Ok(v) => v,
        Err(_) => {
            output_err(mode, "y must be a number");
            return;
        }
    };
    match session.mouse_move(x, y).await {
        Ok(msg) => output_ok(mode, &msg, json!({"action": "mouse_move", "x": x, "y": y})),
        Err(e) => output_err(mode, &e.to_string()),
    }
}

async fn mouse_wheel(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    if parts.is_empty() {
        output_err(mode, "Usage: mouse wheel <delta>");
        return;
    }
    let delta: f64 = match parts[0].parse() {
        Ok(v) => v,
        Err(_) => {
            output_err(mode, "delta must be a number");
            return;
        }
    };
    match session.mouse_wheel(0.0, delta).await {
        Ok(msg) => output_ok(mode, &msg, json!({"action": "mouse_wheel", "delta": delta})),
        Err(e) => output_err(mode, &e.to_string()),
    }
}

async fn mouse_down(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let button = parts
        .first()
        .map(|s| parse_button(s))
        .unwrap_or(MouseButton::Left);
    match session.mouse_down(button).await {
        Ok(msg) => output_ok(mode, &msg, json!({"action": "mouse_down"})),
        Err(e) => output_err(mode, &e.to_string()),
    }
}

async fn mouse_up(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let button = parts
        .first()
        .map(|s| parse_button(s))
        .unwrap_or(MouseButton::Left);
    match session.mouse_up(button).await {
        Ok(msg) => output_ok(mode, &msg, json!({"action": "mouse_up"})),
        Err(e) => output_err(mode, &e.to_string()),
    }
}

#[cfg(test)]
mod tests {
    use super::{parse_button, resolve_mouse_sub, MouseSub};
    use night_fury_core::MouseButton;

    #[test]
    fn resolves_known_subcommands() {
        assert_eq!(resolve_mouse_sub("move"), MouseSub::Move);
        assert_eq!(resolve_mouse_sub("wheel"), MouseSub::Wheel);
        assert_eq!(resolve_mouse_sub("down"), MouseSub::Down);
        assert_eq!(resolve_mouse_sub("up"), MouseSub::Up);
    }

    #[test]
    fn unknown_subcommand_returns_unknown() {
        assert_eq!(resolve_mouse_sub(""), MouseSub::Unknown);
        assert_eq!(resolve_mouse_sub("click"), MouseSub::Unknown);
    }

    #[test]
    fn parse_button_known_names() {
        assert!(matches!(parse_button("right"), MouseButton::Right));
        assert!(matches!(parse_button("middle"), MouseButton::Middle));
        assert!(matches!(parse_button("left"), MouseButton::Left));
    }

    #[test]
    fn parse_button_is_case_insensitive() {
        assert!(matches!(parse_button("RIGHT"), MouseButton::Right));
        assert!(matches!(parse_button("Middle"), MouseButton::Middle));
    }

    #[test]
    fn parse_button_unknown_defaults_to_left() {
        // Fallback policy: any unrecognised token → Left
        assert!(matches!(parse_button(""), MouseButton::Left));
        assert!(matches!(parse_button("garbage"), MouseButton::Left));
    }
}
