use crate::dap_client;

use base64::Engine as _;
use dap_client::{DapSession, example_bin, example_source, spawn_attach_target, wait_for_exit};
use serde_json::{Value, json};
use serial_test::serial;
use std::path::Path;
use std::time::{Duration, Instant};

const HELLO_LINE: i64 = 5;
const SET_VAR_LINE: i64 = 35;
const OPTIONAL_EVENT_TIMEOUT: Duration = Duration::from_secs(10);

fn assert_response(response: &Value, command: &str, request_seq: i64, success: bool) -> bool {
    assert_eq!(
        response.get("type").and_then(Value::as_str),
        Some("response")
    );
    assert_eq!(
        response.get("command").and_then(Value::as_str),
        Some(command)
    );
    assert_eq!(
        response.get("request_seq").and_then(Value::as_i64),
        Some(request_seq)
    );
    let got_success = response.get("success").and_then(Value::as_bool);
    if got_success == Some(success) {
        assert!(response.get("seq").and_then(Value::as_i64).is_some());
        return true;
    }
    if success
        && let Some(message) = response.get("message").and_then(Value::as_str)
        && (message.contains("ENOSYS")
            || message.contains("Function not implemented")
            || message.contains("EPERM")
            || message.contains("Operation not permitted"))
    {
        return false;
    }
    assert_eq!(got_success, Some(success), "response: {response}");
    assert!(response.get("seq").and_then(Value::as_i64).is_some());
    true
}

macro_rules! ensure_response {
    ($session:expr, $response:expr, $command:expr, $seq:expr, $success:expr) => {{
        if !assert_response($response, $command, $seq, $success) {
            $session.shutdown();
            return Ok(());
        }
    }};
}

macro_rules! require_launch {
    ($session:expr, $program:expr, $source:expr, $line:expr) => {{
        match launch_with_breakpoint($session, $program, $source, $line)? {
            Some(thread_id) => thread_id,
            None => {
                $session.shutdown();
                return Ok(());
            }
        }
    }};
}

macro_rules! require_frame {
    ($session:expr, $thread_id:expr) => {{
        match first_frame_id($session, $thread_id)? {
            Some(frame_id) => frame_id,
            None => {
                $session.shutdown();
                return Ok(());
            }
        }
    }};
}

fn initialize(session: &mut DapSession) -> anyhow::Result<()> {
    let seq = session
        .client
        .send_request("initialize", json!({ "adapterID": "bugstalker" }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "initialize", seq, true);
    let event = session.client.wait_for_event("initialized")?;
    assert_eq!(event.get("type").and_then(Value::as_str), Some("event"));
    Ok(())
}

fn launch_with_breakpoint(
    session: &mut DapSession,
    program: &Path,
    source: &Path,
    line: i64,
) -> anyhow::Result<Option<i64>> {
    initialize(session)?;
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    if !assert_response(&launch_response, "launch", launch_seq, true) {
        return Ok(None);
    }

    let bp_seq = session.client.send_request(
        "setBreakpoints",
        json!({
            "source": { "path": source },
            "breakpoints": [{ "line": line }],
        }),
    )?;
    let bp_response = session.client.read_response(bp_seq)?;
    if !assert_response(&bp_response, "setBreakpoints", bp_seq, true) {
        return Ok(None);
    }

    let config_seq = session
        .client
        .send_request("configurationDone", json!({}))?;
    let config_response = session.client.read_response(config_seq)?;
    if !assert_response(&config_response, "configurationDone", config_seq, true) {
        return Ok(None);
    }

    let stopped = session.client.wait_for_event("stopped")?;
    let thread_id = stopped
        .get("body")
        .and_then(|body| body.get("threadId"))
        .and_then(Value::as_i64)
        .unwrap_or_default();
    Ok(Some(thread_id))
}

fn first_frame_id(session: &mut DapSession, thread_id: i64) -> anyhow::Result<Option<i64>> {
    let stack_seq = session
        .client
        .send_request("stackTrace", json!({ "threadId": thread_id }))?;
    let stack_response = session.client.read_response(stack_seq)?;
    if !assert_response(&stack_response, "stackTrace", stack_seq, true) {
        return Ok(None);
    }
    let frame_id = stack_response["body"]["stackFrames"][0]["id"]
        .as_i64()
        .unwrap_or_default();
    Ok(Some(frame_id))
}

fn wait_for_event_or_terminated(
    session: &mut DapSession,
    event_name: &str,
    timeout: Duration,
) -> anyhow::Result<Option<Value>> {
    let deadline = Instant::now() + timeout;
    loop {
        let remaining = deadline.saturating_duration_since(Instant::now());
        if remaining.is_zero() {
            return Ok(None);
        }
        let event = match session.client.read_event_with_timeout(remaining)? {
            Some(event) => event,
            None => return Ok(None),
        };
        match event.get("event").and_then(Value::as_str) {
            Some(name) if name == event_name => return Ok(Some(event)),
            Some("exited") | Some("terminated") => return Ok(None),
            _ => continue,
        }
    }
}

#[test]
#[serial]
fn test_initialize_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_launch_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "launch", seq, true);
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_attach_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let mut target = spawn_attach_target(&example_bin("dap_attach"))?;
    initialize(&mut session)?;
    let seq = session
        .client
        .send_request("attach", json!({ "pid": target.id() }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "attach", seq, true);
    let config_seq = session
        .client
        .send_request("configurationDone", json!({}))?;
    let config_response = session.client.read_response(config_seq)?;
    ensure_response!(
        session,
        &config_response,
        "configurationDone",
        config_seq,
        true
    );
    let _ = session.client.wait_for_event("stopped")?;
    session.shutdown();
    let _ = wait_for_exit(&mut target, Duration::from_secs(1))
        .or_else(|_| target.kill().map_err(anyhow::Error::from));
    Ok(())
}

#[test]
#[serial]
fn test_configuration_done_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    assert!(thread_id > 0);
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_set_breakpoints_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);

    let source = example_source("examples/hello_world/src/hello_world.rs");
    let bp_seq = session.client.send_request(
        "setBreakpoints",
        json!({
            "source": { "path": source },
            "breakpoints": [{ "line": HELLO_LINE }],
        }),
    )?;
    let bp_response = session.client.read_response(bp_seq)?;
    ensure_response!(session, &bp_response, "setBreakpoints", bp_seq, true);
    assert!(bp_response["body"]["breakpoints"].is_array());
    let event = session.client.wait_for_event("breakpoint")?;
    assert_eq!(event["event"], "breakpoint");
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_set_function_breakpoints_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);

    let seq = session.client.send_request(
        "setFunctionBreakpoints",
        json!({ "breakpoints": [{ "name": "myprint" }] }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "setFunctionBreakpoints", seq, true);
    assert!(response["body"]["breakpoints"].is_array());
    let _ = session.client.wait_for_event("breakpoint")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_set_instruction_breakpoints_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session.client.send_request(
        "gotoTargets",
        json!({
            "source": { "path": example_source("examples/hello_world/src/hello_world.rs") },
            "line": HELLO_LINE,
        }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "gotoTargets", seq, true);
    let target = &response["body"]["targets"][0];
    let instruction = target["instructionPointerReference"]
        .as_str()
        .unwrap_or("0x0");

    let ibp_seq = session.client.send_request(
        "setInstructionBreakpoints",
        json!({
            "breakpoints": [{ "instructionReference": instruction }],
        }),
    )?;
    let ibp_response = session.client.read_response(ibp_seq)?;
    ensure_response!(
        session,
        &ibp_response,
        "setInstructionBreakpoints",
        ibp_seq,
        true
    );
    assert!(ibp_response["body"]["breakpoints"].is_array());
    assert!(thread_id > 0);
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_set_exception_breakpoints_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);

    let seq = session
        .client
        .send_request("setExceptionBreakpoints", json!({ "filters": ["signal"] }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "setExceptionBreakpoints", seq, true);
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_threads_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session.client.send_request("threads", json!({}))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "threads", seq, true);
    assert!(response["body"]["threads"].is_array());
    assert!(thread_id > 0);
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_stack_trace_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session
        .client
        .send_request("stackTrace", json!({ "threadId": thread_id }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "stackTrace", seq, true);
    assert!(response["body"]["stackFrames"].is_array());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_scopes_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let frame_id = require_frame!(&mut session, thread_id);
    let seq = session
        .client
        .send_request("scopes", json!({ "frameId": frame_id }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "scopes", seq, true);
    assert!(response["body"]["scopes"].is_array());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_variables_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let frame_id = require_frame!(&mut session, thread_id);
    let scopes_seq = session
        .client
        .send_request("scopes", json!({ "frameId": frame_id }))?;
    let scopes_response = session.client.read_response(scopes_seq)?;
    ensure_response!(session, &scopes_response, "scopes", scopes_seq, true);
    let locals_ref = scopes_response["body"]["scopes"][0]["variablesReference"]
        .as_i64()
        .unwrap_or(0);

    let seq = session
        .client
        .send_request("variables", json!({ "variablesReference": locals_ref }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "variables", seq, true);
    assert!(response["body"]["variables"].is_array());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_set_variable_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("dap_set_variable"),
        &example_source("examples/dap_set_variable/src/main.rs"),
        SET_VAR_LINE
    );
    let frame_id = require_frame!(&mut session, thread_id);
    let scopes_seq = session
        .client
        .send_request("scopes", json!({ "frameId": frame_id }))?;
    let scopes_response = session.client.read_response(scopes_seq)?;
    ensure_response!(session, &scopes_response, "scopes", scopes_seq, true);
    let locals_ref = scopes_response["body"]["scopes"][0]["variablesReference"]
        .as_i64()
        .unwrap_or(0);

    let vars_seq = session
        .client
        .send_request("variables", json!({ "variablesReference": locals_ref }))?;
    let vars_response = session.client.read_response(vars_seq)?;
    ensure_response!(session, &vars_response, "variables", vars_seq, true);
    let container = vars_response["body"]["variables"]
        .as_array()
        .and_then(|vars| vars.iter().find(|v| v["name"] == "container"))
        .cloned()
        .unwrap();
    let container_ref = container["variablesReference"].as_i64().unwrap_or(0);

    let point_seq = session
        .client
        .send_request("variables", json!({ "variablesReference": container_ref }))?;
    let point_response = session.client.read_response(point_seq)?;
    ensure_response!(session, &point_response, "variables", point_seq, true);
    let point = point_response["body"]["variables"]
        .as_array()
        .and_then(|vars| vars.iter().find(|v| v["name"] == "point"))
        .cloned()
        .unwrap();
    let point_ref = point["variablesReference"].as_i64().unwrap_or(0);

    let seq = session.client.send_request(
        "setVariable",
        json!({
            "variablesReference": point_ref,
            "name": "x",
            "value": "42",
        }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "setVariable", seq, true);
    assert!(response["body"]["value"].as_str().is_some());
    let _ = session.client.wait_for_event("invalidated")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_evaluate_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("dap_set_variable"),
        &example_source("examples/dap_set_variable/src/main.rs"),
        SET_VAR_LINE
    );
    let frame_id = require_frame!(&mut session, thread_id);
    let seq = session.client.send_request(
        "evaluate",
        json!({ "expression": "container.point.x", "frameId": frame_id }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "evaluate", seq, true);
    assert!(response["body"]["result"].as_str().is_some());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_set_expression_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("dap_set_variable"),
        &example_source("examples/dap_set_variable/src/main.rs"),
        SET_VAR_LINE
    );
    let frame_id = require_frame!(&mut session, thread_id);
    let seq = session.client.send_request(
        "setExpression",
        json!({
            "expression": "container.point.x",
            "value": "41",
            "frameId": frame_id,
        }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "setExpression", seq, true);
    assert!(response["body"]["value"].as_str().is_some());
    let _ = session.client.wait_for_event("invalidated")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_continue_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session.client.send_request("continue", json!({}))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "continue", seq, true);
    let _ = wait_for_event_or_terminated(&mut session, "continued", OPTIONAL_EVENT_TIMEOUT)?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_next_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session
        .client
        .send_request("next", json!({ "threadId": thread_id }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "next", seq, true);
    let _ = session.client.wait_for_event("stopped")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_step_in_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session
        .client
        .send_request("stepIn", json!({ "threadId": thread_id }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "stepIn", seq, true);
    let _ = session.client.wait_for_event("stopped")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_step_out_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session
        .client
        .send_request("stepOut", json!({ "threadId": thread_id }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "stepOut", seq, true);
    let _ = session.client.wait_for_event("stopped")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_step_back_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session
        .client
        .send_request("stepBack", json!({ "threadId": thread_id }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "stepBack", seq, false);
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_reverse_continue_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session.client.send_request("reverseContinue", json!({}))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "reverseContinue", seq, false);
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_pause_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let pause_seq = session.client.send_request("pause", json!({}))?;
    let pause_response = session.client.read_response(pause_seq)?;
    ensure_response!(session, &pause_response, "pause", pause_seq, true);
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_goto_targets_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session.client.send_request(
        "gotoTargets",
        json!({
            "source": { "path": example_source("examples/hello_world/src/hello_world.rs") },
            "line": HELLO_LINE,
        }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "gotoTargets", seq, true);
    assert!(response["body"]["targets"].is_array());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_goto_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let targets_seq = session.client.send_request(
        "gotoTargets",
        json!({
            "source": { "path": example_source("examples/hello_world/src/hello_world.rs") },
            "line": HELLO_LINE,
        }),
    )?;
    let targets_response = session.client.read_response(targets_seq)?;
    ensure_response!(session, &targets_response, "gotoTargets", targets_seq, true);
    let target_id = targets_response["body"]["targets"][0]["id"]
        .as_i64()
        .unwrap_or(0);

    let seq = session
        .client
        .send_request("goto", json!({ "targetId": target_id }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "goto", seq, true);
    let _ = session.client.wait_for_event("stopped")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_restart_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session.client.send_request("restart", json!({}))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "restart", seq, true);
    let _ = session.client.wait_for_event("stopped")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_restart_frame_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let frame_id = require_frame!(&mut session, thread_id);
    let seq = session
        .client
        .send_request("restartFrame", json!({ "frameId": frame_id }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "restartFrame", seq, true);
    let _ = session.client.wait_for_event("stopped")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_read_memory_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("dap_disassemble"),
        &example_source("examples/dap_disassemble/src/main.rs"),
        22
    );
    let targets_seq = session.client.send_request(
        "gotoTargets",
        json!({
            "source": { "path": example_source("examples/dap_disassemble/src/main.rs") },
            "line": 22,
        }),
    )?;
    let targets_response = session.client.read_response(targets_seq)?;
    ensure_response!(session, &targets_response, "gotoTargets", targets_seq, true);
    let instruction = targets_response["body"]["targets"][0]["instructionPointerReference"]
        .as_str()
        .unwrap_or("0x0");

    let seq = session.client.send_request(
        "readMemory",
        json!({ "memoryReference": instruction, "count": 8 }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "readMemory", seq, true);
    assert!(response["body"]["data"].as_str().is_some());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_write_memory_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("dap_disassemble"),
        &example_source("examples/dap_disassemble/src/main.rs"),
        22
    );
    let targets_seq = session.client.send_request(
        "gotoTargets",
        json!({
            "source": { "path": example_source("examples/dap_disassemble/src/main.rs") },
            "line": 22,
        }),
    )?;
    let targets_response = session.client.read_response(targets_seq)?;
    ensure_response!(session, &targets_response, "gotoTargets", targets_seq, true);
    let instruction = targets_response["body"]["targets"][0]["instructionPointerReference"]
        .as_str()
        .unwrap_or("0x0");

    let read_seq = session.client.send_request(
        "readMemory",
        json!({ "memoryReference": instruction, "count": 4 }),
    )?;
    let read_response = session.client.read_response(read_seq)?;
    ensure_response!(session, &read_response, "readMemory", read_seq, true);
    let data = read_response["body"]["data"].as_str().unwrap_or_default();
    let bytes = base64::engine::general_purpose::STANDARD
        .decode(data)
        .unwrap_or_default();
    assert!(!bytes.is_empty());

    let seq = session.client.send_request(
        "writeMemory",
        json!({ "memoryReference": instruction, "data": data }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "writeMemory", seq, true);
    assert!(response["body"]["bytesWritten"].as_u64().is_some());
    let _ = session.client.wait_for_event("invalidated")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_disassemble_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("dap_disassemble"),
        &example_source("examples/dap_disassemble/src/main.rs"),
        22
    );
    let targets_seq = session.client.send_request(
        "gotoTargets",
        json!({
            "source": { "path": example_source("examples/dap_disassemble/src/main.rs") },
            "line": 22,
        }),
    )?;
    let targets_response = session.client.read_response(targets_seq)?;
    ensure_response!(session, &targets_response, "gotoTargets", targets_seq, true);
    let instruction = targets_response["body"]["targets"][0]["instructionPointerReference"]
        .as_str()
        .unwrap_or("0x0");

    let seq = session.client.send_request(
        "disassemble",
        json!({ "memoryReference": instruction, "instructionCount": 16 }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "disassemble", seq, true);
    assert!(response["body"]["instructions"].is_array());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_data_breakpoint_info_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let seq = session
        .client
        .send_request("dataBreakpointInfo", json!({ "name": "stats.count" }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "dataBreakpointInfo", seq, true);
    assert!(response["body"]["description"].as_str().is_some());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_set_data_breakpoints_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("dap_data_breakpoints");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);

    let info_seq = session
        .client
        .send_request("dataBreakpointInfo", json!({ "name": "stats.count" }))?;
    let info_response = session.client.read_response(info_seq)?;
    ensure_response!(
        session,
        &info_response,
        "dataBreakpointInfo",
        info_seq,
        true
    );
    let data_id = info_response["body"]["dataId"].clone();

    let seq = session.client.send_request(
        "setDataBreakpoints",
        json!({ "breakpoints": [{ "dataId": data_id, "accessType": "write" }] }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "setDataBreakpoints", seq, true);
    assert!(response["body"]["breakpoints"].is_array());
    let _ = session.client.wait_for_event("breakpoint")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_modules_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);

    let seq = session.client.send_request("modules", json!({}))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "modules", seq, true);
    assert!(response["body"]["modules"].is_array());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_loaded_sources_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);

    let seq = session.client.send_request("loadedSources", json!({}))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "loadedSources", seq, true);
    assert!(response["body"]["sources"].is_array());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_source_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);

    let seq = session.client.send_request(
        "source",
        json!({ "source": { "path": example_source("examples/hello_world/src/hello_world.rs") } }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "source", seq, true);
    assert!(response["body"]["content"].as_str().is_some());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_completions_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("dap_set_variable"),
        &example_source("examples/dap_set_variable/src/main.rs"),
        SET_VAR_LINE
    );
    let frame_id = require_frame!(&mut session, thread_id);
    let seq = session.client.send_request(
        "completions",
        json!({
            "text": "con",
            "column": 3,
            "frameId": frame_id,
        }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "completions", seq, true);
    assert!(response["body"]["targets"].is_array());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_step_in_targets_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let frame_id = require_frame!(&mut session, thread_id);
    let seq = session
        .client
        .send_request("stepInTargets", json!({ "frameId": frame_id }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "stepInTargets", seq, true);
    assert!(response["body"]["targets"].is_array());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_breakpoint_locations_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);
    let seq = session.client.send_request(
        "breakpointLocations",
        json!({
            "source": { "path": example_source("examples/hello_world/src/hello_world.rs") },
            "line": HELLO_LINE,
        }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "breakpointLocations", seq, true);
    assert!(response["body"]["breakpoints"].is_array());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_terminate_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session.client.send_request("terminate", json!({}))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "terminate", seq, true);
    let _ = session.client.wait_for_event("terminated")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_terminate_threads_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session
        .client
        .send_request("terminateThreads", json!({ "threadIds": [] }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "terminateThreads", seq, true);
    let _ = session.client.wait_for_event("terminated")?;
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_disconnect_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let response = session.disconnect(true)?;
    assert!(response["success"].as_bool().unwrap_or(false));
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_cancel_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let seq = session.client.send_request("cancel", json!({}))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "cancel", seq, true);
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_run_in_terminal_request() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let seq = session
        .client
        .send_request("runInTerminal", json!({ "args": ["/bin/echo", "dap"] }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "runInTerminal", seq, true);
    assert!(response["body"]["processId"].as_u64().is_some());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_initialized() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let seq = session
        .client
        .send_request("initialize", json!({ "adapterID": "bugstalker" }))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "initialize", seq, true);
    let event = session.client.wait_for_event("initialized")?;
    assert_eq!(event["event"], "initialized");
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_stopped() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_continued() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session.client.send_request("continue", json!({}))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "continue", seq, true);
    let Some(event) =
        wait_for_event_or_terminated(&mut session, "continued", OPTIONAL_EVENT_TIMEOUT)?
    else {
        session.shutdown();
        return Ok(());
    };
    assert_eq!(event["event"], "continued");
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_thread() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let Some(event) = wait_for_event_or_terminated(&mut session, "thread", OPTIONAL_EVENT_TIMEOUT)?
    else {
        session.shutdown();
        return Ok(());
    };
    assert!(event["body"]["threadId"].as_i64().is_some());
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_breakpoint() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);
    let bp_seq = session.client.send_request(
        "setBreakpoints",
        json!({
            "source": { "path": example_source("examples/hello_world/src/hello_world.rs") },
            "breakpoints": [{ "line": HELLO_LINE }],
        }),
    )?;
    let bp_response = session.client.read_response(bp_seq)?;
    ensure_response!(session, &bp_response, "setBreakpoints", bp_seq, true);
    let event = session.client.wait_for_event("breakpoint")?;
    assert_eq!(event["event"], "breakpoint");
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_module() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);
    let event = session.client.wait_for_event("module")?;
    assert_eq!(event["event"], "module");
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_loaded_source() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);
    let event = session.client.wait_for_event("loadedSource")?;
    assert_eq!(event["event"], "loadedSource");
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_process() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);
    let event = session.client.wait_for_event("process")?;
    assert_eq!(event["event"], "process");
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_output() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session.client.send_request("continue", json!({}))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "continue", seq, true);
    let Some(event) = wait_for_event_or_terminated(&mut session, "output", OPTIONAL_EVENT_TIMEOUT)?
    else {
        session.shutdown();
        return Ok(());
    };
    assert_eq!(event["event"], "output");
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_exited() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session.client.send_request("continue", json!({}))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "continue", seq, true);
    let event = session.client.wait_for_event("exited")?;
    assert_eq!(event["event"], "exited");
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_terminated() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let _thread_id = require_launch!(
        &mut session,
        &example_bin("hello_world"),
        &example_source("examples/hello_world/src/hello_world.rs"),
        HELLO_LINE
    );
    let seq = session.client.send_request("terminate", json!({}))?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "terminate", seq, true);
    let event = session.client.wait_for_event("terminated")?;
    assert_eq!(event["event"], "terminated");
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_progress() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);
    let start = session.client.wait_for_event("progressStart")?;
    assert_eq!(start["event"], "progressStart");
    let update = session.client.wait_for_event("progressUpdate")?;
    assert_eq!(update["event"], "progressUpdate");
    let end = session.client.wait_for_event("progressEnd")?;
    assert_eq!(end["event"], "progressEnd");
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_invalidated() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    let thread_id = require_launch!(
        &mut session,
        &example_bin("dap_set_variable"),
        &example_source("examples/dap_set_variable/src/main.rs"),
        SET_VAR_LINE
    );
    let frame_id = require_frame!(&mut session, thread_id);
    let seq = session.client.send_request(
        "setExpression",
        json!({
            "expression": "container.point.x",
            "value": "40",
            "frameId": frame_id,
        }),
    )?;
    let response = session.client.read_response(seq)?;
    ensure_response!(session, &response, "setExpression", seq, true);
    let event = session.client.wait_for_event("invalidated")?;
    assert_eq!(event["event"], "invalidated");
    session.shutdown();
    Ok(())
}

#[test]
#[serial]
fn test_event_capabilities() -> anyhow::Result<()> {
    let mut session = DapSession::start()?;
    initialize(&mut session)?;
    let program = example_bin("hello_world");
    let launch_seq = session
        .client
        .send_request("launch", json!({ "program": program }))?;
    let launch_response = session.client.read_response(launch_seq)?;
    ensure_response!(session, &launch_response, "launch", launch_seq, true);
    let event = session.client.wait_for_event("capabilities")?;
    assert_eq!(event["event"], "capabilities");
    assert!(event["body"]["capabilities"].is_object());
    session.shutdown();
    Ok(())
}
