use dap::DapRegistry;
use gpui::{BackgroundExecutor, TestAppContext, VisualTestContext};
use project::{FakeFs, Project};
use serde_json::json;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use task::{DebugRequest, DebugScenario, LaunchRequest, TaskContext, VariableName, ZedDebugConfig};
use util::path;

// use crate::new_process_modal::NewProcessMode;
use crate::tests::{init_test, init_test_workspace};

#[gpui::test]
async fn test_debug_session_substitutes_variables_and_relativizes_paths(
    executor: BackgroundExecutor,
    cx: &mut TestAppContext,
) {
    init_test(cx);

    let fs = FakeFs::new(executor.clone());
    fs.insert_tree(
        path!("/project"),
        json!({
            "main.rs": "fn main() {}"
        }),
    )
    .await;

    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
    let workspace = init_test_workspace(&project, cx).await;
    let cx = &mut VisualTestContext::from_window(*workspace, cx);

    let test_variables = vec![(
        VariableName::WorktreeRoot,
        path!("/test/worktree/path").to_string(),
    )]
    .into_iter()
    .collect();

    let task_context = TaskContext {
        cwd: None,
        task_variables: test_variables,
        project_env: Default::default(),
    };

    let home_dir = paths::home_dir();

    let test_cases: Vec<(&'static str, &'static str)> = vec![
        // Absolute path - should not be relativized
        (
            path!("/absolute/path/to/program"),
            path!("/absolute/path/to/program"),
        ),
        // Relative path - should be prefixed with worktree root
        (
            format!(".{0}src{0}program", std::path::MAIN_SEPARATOR).leak(),
            path!("/test/worktree/path/src/program"),
        ),
        // Home directory path - should be expanded to full home directory path
        (
            format!("~{0}src{0}program", std::path::MAIN_SEPARATOR).leak(),
            home_dir
                .join("src")
                .join("program")
                .to_string_lossy()
                .to_string()
                .leak(),
        ),
        // Path with $ZED_WORKTREE_ROOT - should be substituted without double appending
        (
            format!(
                "$ZED_WORKTREE_ROOT{0}src{0}program",
                std::path::MAIN_SEPARATOR
            )
            .leak(),
            path!("/test/worktree/path/src/program"),
        ),
    ];

    let called_launch = Arc::new(AtomicBool::new(false));

    for (input_path, expected_path) in test_cases {
        let _subscription = project::debugger::test::intercept_debug_sessions(cx, {
            let called_launch = called_launch.clone();
            move |client| {
                client.on_request::<dap::requests::Launch, _>({
                    let called_launch = called_launch.clone();

                    move |_, args| {
                        let config = args.raw.as_object().unwrap();

                        assert_eq!(
                            config["program"].as_str().unwrap(),
                            expected_path,
                            "Program path was not correctly substituted for input: {}",
                            input_path
                        );

                        assert_eq!(
                            config["cwd"].as_str().unwrap(),
                            expected_path,
                            "CWD path was not correctly substituted for input: {}",
                            input_path
                        );

                        let expected_other_field = if input_path.contains("$ZED_WORKTREE_ROOT") {
                            input_path
                                .replace("$ZED_WORKTREE_ROOT", &path!("/test/worktree/path"))
                                .to_owned()
                        } else {
                            input_path.to_string()
                        };

                        assert_eq!(
                            config["otherField"].as_str().unwrap(),
                            &expected_other_field,
                            "Other field was incorrectly modified for input: {}",
                            input_path
                        );

                        called_launch.store(true, Ordering::SeqCst);

                        Ok(())
                    }
                });
            }
        });

        let scenario = DebugScenario {
            adapter: "fake-adapter".into(),
            label: "test-debug-session".into(),
            build: None,
            config: json!({
                "request": "launch",
                "program": input_path,
                "cwd": input_path,
                "otherField": input_path
            }),
            tcp_connection: None,
        };

        workspace
            .update(cx, |workspace, window, cx| {
                workspace.start_debug_session(scenario, task_context.clone(), None, window, cx)
            })
            .unwrap();

        cx.run_until_parked();

        assert!(called_launch.load(Ordering::SeqCst));
        called_launch.store(false, Ordering::SeqCst);
    }
}

// #[gpui::test]
// async fn test_save_debug_scenario_to_file(executor: BackgroundExecutor, cx: &mut TestAppContext) {
//     init_test(cx);

//     let fs = FakeFs::new(executor.clone());
//     fs.insert_tree(
//         path!("/project"),
//         json!({
//             "main.rs": "fn main() {}"
//         }),
//     )
//     .await;

//     let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
//     let workspace = init_test_workspace(&project, cx).await;
//     let cx = &mut VisualTestContext::from_window(*workspace, cx);

//     workspace
//         .update(cx, |workspace, window, cx| {
//             crate::new_process_modal::NewProcessModal::show(
//                 workspace,
//                 window,
//                 NewProcessMode::Debug,
//                 None,
//                 cx,
//             );
//         })
//         .unwrap();

//     cx.run_until_parked();

//     let modal = workspace
//         .update(cx, |workspace, _, cx| {
//             workspace.active_modal::<crate::new_process_modal::NewProcessModal>(cx)
//         })
//         .unwrap()
//         .expect("Modal should be active");

//     modal.update_in(cx, |modal, window, cx| {
//         modal.set_configure("/project/main", "/project", false, window, cx);
//         modal.save_scenario(window, cx);
//     });

//     cx.executor().run_until_parked();

//     let debug_json_content = fs
//         .load(path!("/project/.zed/debug.json").as_ref())
//         .await
//         .expect("debug.json should exist");

//     let expected_content = vec![
//         "[",
//         "  {",
//         r#"    "adapter": "fake-adapter","#,
//         r#"    "label": "main (fake-adapter)","#,
//         r#"    "request": "launch","#,
//         r#"    "program": "/project/main","#,
//         r#"    "cwd": "/project","#,
//         r#"    "args": [],"#,
//         r#"    "env": {}"#,
//         "  }",
//         "]",
//     ];

//     let actual_lines: Vec<&str> = debug_json_content.lines().collect();
//     pretty_assertions::assert_eq!(expected_content, actual_lines);

//     modal.update_in(cx, |modal, window, cx| {
//         modal.set_configure("/project/other", "/project", true, window, cx);
//         modal.save_scenario(window, cx);
//     });

//     cx.executor().run_until_parked();

//     let debug_json_content = fs
//         .load(path!("/project/.zed/debug.json").as_ref())
//         .await
//         .expect("debug.json should exist after second save");

//     let expected_content = vec![
//         "[",
//         "  {",
//         r#"    "adapter": "fake-adapter","#,
//         r#"    "label": "main (fake-adapter)","#,
//         r#"    "request": "launch","#,
//         r#"    "program": "/project/main","#,
//         r#"    "cwd": "/project","#,
//         r#"    "args": [],"#,
//         r#"    "env": {}"#,
//         "  },",
//         "  {",
//         r#"    "adapter": "fake-adapter","#,
//         r#"    "label": "other (fake-adapter)","#,
//         r#"    "request": "launch","#,
//         r#"    "program": "/project/other","#,
//         r#"    "cwd": "/project","#,
//         r#"    "args": [],"#,
//         r#"    "env": {}"#,
//         "  }",
//         "]",
//     ];

//     let actual_lines: Vec<&str> = debug_json_content.lines().collect();
//     pretty_assertions::assert_eq!(expected_content, actual_lines);
// }

#[gpui::test]
async fn test_dap_adapter_config_conversion_and_validation(cx: &mut TestAppContext) {
    init_test(cx);

    let mut expected_adapters = vec![
        "CodeLLDB",
        "Debugpy",
        "PHP",
        "JavaScript",
        "Ruby",
        "Delve",
        "GDB",
        "fake-adapter",
    ];

    let adapter_names = cx.update(|cx| {
        let registry = DapRegistry::global(cx);
        registry.enumerate_adapters()
    });

    let zed_config = ZedDebugConfig {
        label: "test_debug_session".into(),
        adapter: "test_adapter".into(),
        request: DebugRequest::Launch(LaunchRequest {
            program: "test_program".into(),
            cwd: None,
            args: vec![],
            env: Default::default(),
        }),
        stop_on_entry: Some(true),
    };

    for adapter_name in adapter_names {
        let adapter_str = adapter_name.to_string();
        if let Some(pos) = expected_adapters.iter().position(|&x| x == adapter_str) {
            expected_adapters.remove(pos);
        }

        let adapter = cx
            .update(|cx| {
                let registry = DapRegistry::global(cx);
                registry.adapter(adapter_name.as_ref())
            })
            .unwrap_or_else(|| panic!("Adapter {} should exist", adapter_name));

        let mut adapter_specific_config = zed_config.clone();
        adapter_specific_config.adapter = adapter_name.to_string().into();

        let debug_scenario = adapter
            .config_from_zed_format(adapter_specific_config)
            .unwrap_or_else(|_| {
                panic!(
                    "Adapter {} should successfully convert from Zed format",
                    adapter_name
                )
            });

        assert!(
            debug_scenario.config.is_object(),
            "Adapter {} should produce a JSON object for config",
            adapter_name
        );

        let request_type = adapter
            .request_kind(&debug_scenario.config)
            .unwrap_or_else(|_| {
                panic!(
                    "Adapter {} should validate the config successfully",
                    adapter_name
                )
            });

        match request_type {
            dap::StartDebuggingRequestArgumentsRequest::Launch => {}
            dap::StartDebuggingRequestArgumentsRequest::Attach => {
                panic!(
                    "Expected Launch request but got Attach for adapter {}",
                    adapter_name
                );
            }
        }
    }

    assert!(
        expected_adapters.is_empty(),
        "The following expected adapters were not found in the registry: {:?}",
        expected_adapters
    );
}
