use night_fury_core::BrowserSession;
use serde_json::json;

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

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub(super) enum GetSub {
    Text,
    Attr,
    Html,
    Value,
    Count,
    Box,
    Unknown,
}

pub(super) fn resolve_get_sub(s: &str) -> GetSub {
    match s {
        "text" => GetSub::Text,
        "attr" | "attribute" => GetSub::Attr,
        "html" => GetSub::Html,
        "value" => GetSub::Value,
        "count" => GetSub::Count,
        "box" => GetSub::Box,
        _ => GetSub::Unknown,
    }
}

pub async fn dispatch(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let sub = parts.first().copied().unwrap_or("");
    match resolve_get_sub(sub) {
        GetSub::Text => get_text(session, &parts[1..], mode).await,
        GetSub::Attr => get_attr(session, &parts[1..], mode).await,
        GetSub::Html => get_html(session, &parts[1..], mode).await,
        GetSub::Value => get_value(session, &parts[1..], mode).await,
        GetSub::Count => get_count(session, &parts[1..], mode).await,
        GetSub::Box => get_box(session, &parts[1..], mode).await,
        GetSub::Unknown => output_err(
            mode,
            &format!(
                "Unknown get subcommand: {sub}. Use 'text', 'attr', 'html', 'value', 'count', or 'box'"
            ),
        ),
    }
}

async fn get_text(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let sel = parts.join(" ");
    let sel = sel.trim();
    if sel.is_empty() {
        output_err(mode, "Usage: get text <css-selector>");
        return;
    }
    match session.get_text(sel).await {
        Ok(text) => output_ok(mode, &text, json!({"text": text, "selector": sel})),
        Err(e) => output_err(mode, &e.to_string()),
    }
}

async fn get_attr(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    if parts.len() < 2 {
        output_err(mode, "Usage: get attr <css-selector> <attribute-name>");
        return;
    }
    let sel = parts[..parts.len() - 1].join(" ");
    let attr = parts[parts.len() - 1];
    match session.get_attribute(sel.trim(), attr).await {
        Ok(val) => output_ok(
            mode,
            &val,
            json!({"value": val, "selector": sel, "attribute": attr}),
        ),
        Err(e) => output_err(mode, &e.to_string()),
    }
}

async fn get_html(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let sel = parts.join(" ");
    let sel = sel.trim();
    if sel.is_empty() {
        output_err(mode, "Usage: get html <css-selector>");
        return;
    }
    match session.get_html(sel).await {
        Ok(html) => output_ok(mode, &html, json!({"html": html, "selector": sel})),
        Err(e) => output_err(mode, &e.to_string()),
    }
}

async fn get_value(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let sel = parts.join(" ");
    let sel = sel.trim();
    if sel.is_empty() {
        output_err(mode, "Usage: get value <css-selector>");
        return;
    }
    match session.get_value(sel).await {
        Ok(val) => output_ok(mode, &val, json!({"value": val, "selector": sel})),
        Err(e) => output_err(mode, &e.to_string()),
    }
}

async fn get_count(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let sel = parts.join(" ");
    let sel = sel.trim();
    if sel.is_empty() {
        output_err(mode, "Usage: get count <css-selector>");
        return;
    }
    match session.get_count(sel).await {
        Ok(count) => output_ok(
            mode,
            &format!("{count}"),
            json!({"count": count, "selector": sel}),
        ),
        Err(e) => output_err(mode, &e.to_string()),
    }
}

async fn get_box(session: &BrowserSession, parts: &[&str], mode: OutputMode) {
    let sel = parts.join(" ");
    let sel = sel.trim();
    if sel.is_empty() {
        output_err(mode, "Usage: get box <css-selector>");
        return;
    }
    match session.get_box(sel).await {
        Ok(b) => {
            let human = format!(
                "x={} y={} w={} h={}",
                b.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0),
                b.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0),
                b.get("width").and_then(|v| v.as_f64()).unwrap_or(0.0),
                b.get("height").and_then(|v| v.as_f64()).unwrap_or(0.0),
            );
            output_ok(mode, &human, json!({"box": b, "selector": sel}));
        }
        Err(e) => output_err(mode, &e.to_string()),
    }
}

#[cfg(test)]
mod tests {
    use super::{resolve_get_sub, GetSub};

    #[test]
    fn resolves_known_subcommands() {
        assert_eq!(resolve_get_sub("text"), GetSub::Text);
        assert_eq!(resolve_get_sub("attr"), GetSub::Attr);
        assert_eq!(resolve_get_sub("attribute"), GetSub::Attr); // alias
        assert_eq!(resolve_get_sub("html"), GetSub::Html);
        assert_eq!(resolve_get_sub("value"), GetSub::Value);
        assert_eq!(resolve_get_sub("count"), GetSub::Count);
        assert_eq!(resolve_get_sub("box"), GetSub::Box);
    }

    #[test]
    fn unknown_subcommand_returns_unknown() {
        assert_eq!(resolve_get_sub(""), GetSub::Unknown);
        assert_eq!(resolve_get_sub("tex"), GetSub::Unknown);
        assert_eq!(resolve_get_sub("href"), GetSub::Unknown); // not a real subcommand
    }
}
