use serde_json::{json, Value};
use sgclaw::browser::ws_protocol::{decode_callback_frame, encode_v1_action};
use sgclaw::pipe::Action;

#[test]
fn encodes_navigate_frame_exactly_as_browser_array() {
    let request = encode_v1_action(
        &Action::Navigate,
        &json!({ "url": "https://www.baidu.com" }),
        "https://www.zhihu.com/hot",
        Some("req42"),
    )
    .unwrap();

    assert_eq!(
        request.payload,
        r#"["https://www.zhihu.com/hot","sgHideBrowserCallAfterLoaded","https://www.baidu.com","callBackJsToCpp(\"https://www.zhihu.com/hot@_@https://www.baidu.com@_@sgclaw_cb_req42@_@sgHideBrowserCallAfterLoaded@_@\")"]"#
    );
    let callback = request.callback.unwrap();
    assert_eq!(callback.request_id, "req42");
    assert_eq!(callback.callback_name, "sgclaw_cb_req42");
    assert_eq!(callback.source_url, "https://www.zhihu.com/hot");
    assert_eq!(callback.target_url, "https://www.baidu.com");
    assert_eq!(callback.action_url, "sgHideBrowserCallAfterLoaded");
}

#[test]
fn encodes_get_text_frame_with_documented_callback_action_url() {
    let request = encode_v1_action(
        &Action::GetText,
        &json!({
            "target_url": "https://www.zhihu.com/hot",
            "selector": "#content"
        }),
        "https://www.zhihu.com/hot",
        Some("req42"),
    )
    .unwrap();

    let payload: Value = serde_json::from_str(&request.payload).unwrap();
    assert_eq!(
        payload,
        json!([
            "https://www.zhihu.com/hot",
            "sgBrowserExcuteJsCodeByArea",
            "https://www.zhihu.com/hot",
            "(function(){const el=document.querySelector(\"#content\");if(!el){throw new Error(\"selector not found: #content\");}const text=el.innerText ?? el.textContent ?? \"\";callBackJsToCpp(\"https://www.zhihu.com/hot@_@https://www.zhihu.com/hot@_@sgclaw_cb_req42@_@sgBrowserExcuteJsCodeByArea@_@\"+String(text));})();",
            "hide"
        ])
    );
    let callback = request.callback.unwrap();
    assert_eq!(callback.request_id, "req42");
    assert_eq!(callback.callback_name, "sgclaw_cb_req42");
    assert_eq!(callback.source_url, "https://www.zhihu.com/hot");
    assert_eq!(callback.target_url, "https://www.zhihu.com/hot");
    assert_eq!(callback.action_url, "sgBrowserExcuteJsCodeByArea");
}

#[test]
fn decodes_callback_payload_from_browser_frame() {
    let callback = decode_callback_frame(
        r#"["https://www.zhihu.com/hot","callBackJsToCpp","https://www.zhihu.com/hot@_@https://www.zhihu.com/hot@_@sgclaw_cb_req42@_@sgBrowserExcuteJsCodeByArea@_@天气"]"#,
    )
    .unwrap();

    assert_eq!(callback.source_url, "https://www.zhihu.com/hot");
    assert_eq!(callback.target_url, "https://www.zhihu.com/hot");
    assert_eq!(callback.callback_name, "sgclaw_cb_req42");
    assert_eq!(callback.action_url, "sgBrowserExcuteJsCodeByArea");
    assert_eq!(callback.response_text, "天气");
}

#[test]
fn rejects_malformed_callback_frames_and_missing_request_ids() {
    let malformed = decode_callback_frame(
        r#"["https://www.zhihu.com/hot","callBackJsToCpp","https://www.zhihu.com/hot@_@too-short"]"#,
    )
    .unwrap_err();
    assert!(malformed.to_string().contains("malformed callback payload"));

    let wrong_function = decode_callback_frame(
        r#"["https://www.zhihu.com/hot","sgBrowerserOpenPage","0"]"#,
    )
    .unwrap_err();
    assert!(wrong_function
        .to_string()
        .contains("callback frame must target callBackJsToCpp"));

    let missing_request_id = encode_v1_action(
        &Action::Eval,
        &json!({
            "target_url": "https://www.zhihu.com/hot",
            "script": "2 + 2"
        }),
        "https://www.zhihu.com/hot",
        None,
    )
    .unwrap_err();
    assert!(missing_request_id
        .to_string()
        .contains("request_id is required"));
}

#[test]
fn eval_uses_documented_js_opcode_for_callback_action_url() {
    let request = encode_v1_action(
        &Action::Eval,
        &json!({
            "target_url": "https://www.zhihu.com/hot",
            "script": "2 + 2"
        }),
        "https://www.zhihu.com/hot",
        Some("req-eval"),
    )
    .unwrap();

    let callback = request.callback.unwrap();
    assert_eq!(callback.callback_name, "sgclaw_cb_req-eval");
    assert_eq!(callback.action_url, "sgBrowserExcuteJsCodeByArea");

    let payload: Value = serde_json::from_str(&request.payload).unwrap();
    let js = payload[3].as_str().unwrap();
    assert!(js.contains("callBackJsToCpp(\"https://www.zhihu.com/hot@_@https://www.zhihu.com/hot@_@sgclaw_cb_req-eval@_@sgBrowserExcuteJsCodeByArea@_@\"+String(result))"));
}

#[test]
fn covers_supported_v1_action_mapping_and_rejects_unsupported_actions() {
    let cases = vec![
        (
            Action::Navigate,
            json!({ "url": "https://www.baidu.com" }),
            Some("req-nav"),
            "sgHideBrowserCallAfterLoaded",
            true,
        ),
        (
            Action::Click,
            json!({
                "target_url": "https://www.zhihu.com/hot",
                "selector": "#submit"
            }),
            None,
            "sgBrowserExcuteJsCodeByArea",
            false,
        ),
        (
            Action::Type,
            json!({
                "target_url": "https://www.zhihu.com/hot",
                "selector": "#kw",
                "text": "天气"
            }),
            None,
            "sgBrowserExcuteJsCodeByArea",
            false,
        ),
        (
            Action::GetText,
            json!({
                "target_url": "https://www.zhihu.com/hot",
                "selector": "#content"
            }),
            Some("req-get-text"),
            "sgBrowserExcuteJsCodeByArea",
            true,
        ),
        (
            Action::Eval,
            json!({
                "target_url": "https://www.zhihu.com/hot",
                "script": "2 + 2"
            }),
            Some("req-eval"),
            "sgBrowserExcuteJsCodeByArea",
            true,
        ),
    ];

    for (action, params, request_id, browser_function, expects_callback) in cases {
        let request = encode_v1_action(&action, &params, "https://www.zhihu.com/hot", request_id)
            .unwrap();
        let payload: Value = serde_json::from_str(&request.payload).unwrap();
        assert_eq!(payload[1], json!(browser_function), "action={action:?}");
        assert_eq!(request.callback.is_some(), expects_callback, "action={action:?}");
    }

    let unsupported = encode_v1_action(
        &Action::GetHtml,
        &json!({ "selector": "body" }),
        "https://www.zhihu.com/hot",
        None,
    )
    .unwrap_err();
    assert!(unsupported.to_string().contains("unsupported browser ws action"));
}
