use std::collections::HashMap;

use indoc::indoc;
use liwe::model::config::{ActionDefinition, Command, Configuration, Transform};
use tempfile::TempDir;

use crate::fixture::*;

#[test]
fn transform_action_appears_in_code_action_menu() {
    let config = Configuration {
        actions: vec![(
            "uppercase".to_string(),
            ActionDefinition::Transform(Transform {
                title: "Uppercase".to_string(),
                command: "uppercase".to_string(),
                input_template: "{{context}}".to_string(),
            }),
        )]
        .into_iter()
        .collect(),
        commands: vec![(
            "uppercase".to_string(),
            Command {
                run: "tr '[:lower:]' '[:upper:]'".to_string(),
                timeout_seconds: Some(5),
                ..Default::default()
            },
        )]
        .into_iter()
        .collect(),
        ..Default::default()
    };

    Fixture::with_config(
        indoc! {"
            hello world
        "},
        config,
    )
    .code_action_menu(
        uri(1).to_code_action_params(0, "custom.uppercase"),
        lsp_types::CodeAction {
            title: "Uppercase".to_string(),
            kind: action_kind("custom.uppercase"),
            ..Default::default()
        },
    );
}

#[test]
#[cfg_attr(
    windows,
    ignore = "executes a Unix shell command; unreliable on Windows CI runners"
)]
fn transform_action_executes_command_with_cat() {
    let config = Configuration {
        actions: vec![(
            "echo".to_string(),
            ActionDefinition::Transform(Transform {
                title: "Echo".to_string(),
                command: "echo".to_string(),
                input_template: "{{context}}".to_string(),
            }),
        )]
        .into_iter()
        .collect(),
        commands: vec![(
            "echo".to_string(),
            Command {
                run: "cat".to_string(),
                timeout_seconds: Some(5),
                ..Default::default()
            },
        )]
        .into_iter()
        .collect(),
        ..Default::default()
    };

    Fixture::with_config(
        indoc! {"
            hello world
        "},
        config,
    )
    .code_action(
        uri(1).to_code_action_params(0, "custom.echo"),
        vec![uri(1).to_edit("<update_here>\n\nhello world\n\n</update_here>\n")]
            .to_workspace_edit()
            .to_code_action("Echo", "custom.echo"),
    );
}

#[test]
fn transform_action_with_empty_command_shows_in_menu_but_no_resolution() {
    let config = Configuration {
        actions: vec![(
            "empty".to_string(),
            ActionDefinition::Transform(Transform {
                title: "Empty".to_string(),
                command: "empty".to_string(),
                input_template: "{{context}}".to_string(),
            }),
        )]
        .into_iter()
        .collect(),
        commands: vec![(
            "empty".to_string(),
            Command {
                run: "".to_string(),
                timeout_seconds: Some(5),
                ..Default::default()
            },
        )]
        .into_iter()
        .collect(),
        ..Default::default()
    };

    Fixture::with_config(
        indoc! {"
            hello world
        "},
        config,
    )
    .code_action_menu(
        uri(1).to_code_action_params(0, "custom.empty"),
        lsp_types::CodeAction {
            title: "Empty".to_string(),
            kind: action_kind("custom.empty"),
            ..Default::default()
        },
    );
}

#[test]
fn transform_action_with_missing_command_shows_in_menu() {
    let config = Configuration {
        actions: vec![(
            "missing".to_string(),
            ActionDefinition::Transform(Transform {
                title: "Missing".to_string(),
                command: "nonexistent".to_string(),
                input_template: "{{context}}".to_string(),
            }),
        )]
        .into_iter()
        .collect(),
        commands: Default::default(),
        ..Default::default()
    };

    Fixture::with_config(
        indoc! {"
            hello world
        "},
        config,
    )
    .code_action_menu(
        uri(1).to_code_action_params(0, "custom.missing"),
        lsp_types::CodeAction {
            title: "Missing".to_string(),
            kind: action_kind("custom.missing"),
            ..Default::default()
        },
    );
}

#[test]
#[cfg_attr(
    windows,
    ignore = "executes a Unix shell command; unreliable on Windows CI runners"
)]
fn transform_action_with_static_output() {
    let config = Configuration {
        actions: vec![(
            "static".to_string(),
            ActionDefinition::Transform(Transform {
                title: "Static Output".to_string(),
                command: "static".to_string(),
                input_template: "ignored".to_string(),
            }),
        )]
        .into_iter()
        .collect(),
        commands: vec![(
            "static".to_string(),
            Command {
                run: "echo 'replaced content'".to_string(),
                timeout_seconds: Some(5),
                ..Default::default()
            },
        )]
        .into_iter()
        .collect(),
        ..Default::default()
    };

    Fixture::with_config(
        indoc! {"
            original content
        "},
        config,
    )
    .code_action(
        uri(1).to_code_action_params(0, "custom.static"),
        vec![uri(1).to_edit("replaced content\n")]
            .to_workspace_edit()
            .to_code_action("Static Output", "custom.static"),
    );
}

#[test]
fn transform_action_with_multiline_content() {
    let config = Configuration {
        actions: vec![(
            "reverse".to_string(),
            ActionDefinition::Transform(Transform {
                title: "Reverse Lines".to_string(),
                command: "reverse".to_string(),
                input_template: "{{context}}".to_string(),
            }),
        )]
        .into_iter()
        .collect(),
        commands: vec![(
            "reverse".to_string(),
            Command {
                run: "tac".to_string(),
                timeout_seconds: Some(5),
                ..Default::default()
            },
        )]
        .into_iter()
        .collect(),
        ..Default::default()
    };

    Fixture::with_config(
        indoc! {"
            # Header

            line1

            line2

            line3
        "},
        config,
    )
    .code_action_menu(
        uri(1).to_code_action_params(0, "custom.reverse"),
        lsp_types::CodeAction {
            title: "Reverse Lines".to_string(),
            kind: action_kind("custom.reverse"),
            ..Default::default()
        },
    );
}

#[test]
#[cfg_attr(
    windows,
    ignore = "executes a Unix shell command; unreliable on Windows CI runners"
)]
fn transform_action_processes_input_template() {
    let config = Configuration {
        actions: vec![(
            "prefix".to_string(),
            ActionDefinition::Transform(Transform {
                title: "Add Prefix".to_string(),
                command: "prefix".to_string(),
                input_template: "PREFIX: {{context}}".to_string(),
            }),
        )]
        .into_iter()
        .collect(),
        commands: vec![(
            "prefix".to_string(),
            Command {
                run: "cat".to_string(),
                timeout_seconds: Some(5),
                ..Default::default()
            },
        )]
        .into_iter()
        .collect(),
        ..Default::default()
    };

    Fixture::with_config(
        indoc! {"
            hello
        "},
        config,
    )
    .code_action(
        uri(1).to_code_action_params(0, "custom.prefix"),
        vec![uri(1).to_edit("PREFIX: <update_here>\n\nhello\n\n</update_here>\n")]
            .to_workspace_edit()
            .to_code_action("Add Prefix", "custom.prefix"),
    );
}

#[test]
fn transform_action_with_timeout() {
    let config = Configuration {
        actions: vec![(
            "slow".to_string(),
            ActionDefinition::Transform(Transform {
                title: "Slow Command".to_string(),
                command: "slow".to_string(),
                input_template: "{{context}}".to_string(),
            }),
        )]
        .into_iter()
        .collect(),
        commands: vec![(
            "slow".to_string(),
            Command {
                run: "cat".to_string(),
                timeout_seconds: Some(60),
                ..Default::default()
            },
        )]
        .into_iter()
        .collect(),
        ..Default::default()
    };

    Fixture::with_config(
        indoc! {"
            test content
        "},
        config,
    )
    .code_action_menu(
        uri(1).to_code_action_params(0, "custom.slow"),
        lsp_types::CodeAction {
            title: "Slow Command".to_string(),
            kind: action_kind("custom.slow"),
            ..Default::default()
        },
    );
}

#[test]
#[cfg_attr(
    windows,
    ignore = "executes a command via piped stdio; unreliable on Windows CI runners"
)]
fn transform_action_with_shell_false_and_args() {
    let config = Configuration {
        actions: vec![(
            "direct".to_string(),
            ActionDefinition::Transform(Transform {
                title: "Direct Exec".to_string(),
                command: "direct".to_string(),
                input_template: "{{context}}".to_string(),
            }),
        )]
        .into_iter()
        .collect(),
        commands: vec![(
            "direct".to_string(),
            Command {
                run: "cat".to_string(),
                args: Some(vec!["-".to_string()]),
                shell: Some(false),
                timeout_seconds: Some(5),
                ..Default::default()
            },
        )]
        .into_iter()
        .collect(),
        ..Default::default()
    };

    Fixture::with_config(
        indoc! {"
            direct execution test
        "},
        config,
    )
    .code_action(
        uri(1).to_code_action_params(0, "custom.direct"),
        vec![uri(1).to_edit("<update_here>\n\ndirect execution test\n\n</update_here>\n")]
            .to_workspace_edit()
            .to_code_action("Direct Exec", "custom.direct"),
    );
}

#[test]
#[cfg_attr(
    windows,
    ignore = "executes a Unix shell command; unreliable on Windows CI runners"
)]
fn transform_action_with_env_vars() {
    std::env::set_var("IWE_TEST_VAR", "expanded_value");

    let mut env = HashMap::new();
    env.insert("OUTPUT_VAR".to_string(), "$IWE_TEST_VAR".to_string());

    let config = Configuration {
        actions: vec![(
            "envtest".to_string(),
            ActionDefinition::Transform(Transform {
                title: "Env Test".to_string(),
                command: "envtest".to_string(),
                input_template: "{{context}}".to_string(),
            }),
        )]
        .into_iter()
        .collect(),
        commands: vec![(
            "envtest".to_string(),
            Command {
                run: "echo $OUTPUT_VAR".to_string(),
                env: Some(env),
                timeout_seconds: Some(5),
                ..Default::default()
            },
        )]
        .into_iter()
        .collect(),
        ..Default::default()
    };

    Fixture::with_config(
        indoc! {"
            env test content
        "},
        config,
    )
    .code_action(
        uri(1).to_code_action_params(0, "custom.envtest"),
        vec![uri(1).to_edit("expanded_value\n")]
            .to_workspace_edit()
            .to_code_action("Env Test", "custom.envtest"),
    );

    std::env::remove_var("IWE_TEST_VAR");
}

#[test]
#[cfg_attr(
    windows,
    ignore = "executes a Unix shell command; unreliable on Windows CI runners"
)]
fn transform_action_with_cwd() {
    let temp_dir = TempDir::new().expect("Failed to create temp directory");
    let cwd = temp_dir.path().to_string_lossy().to_string();

    std::fs::write(temp_dir.path().join("testfile.txt"), "cwd_test_marker\n")
        .expect("Failed to write test file");

    let config = Configuration {
        actions: vec![(
            "cwdtest".to_string(),
            ActionDefinition::Transform(Transform {
                title: "Cwd Test".to_string(),
                command: "cwdtest".to_string(),
                input_template: "{{context}}".to_string(),
            }),
        )]
        .into_iter()
        .collect(),
        commands: vec![(
            "cwdtest".to_string(),
            Command {
                run: "cat testfile.txt".to_string(),
                cwd: Some(cwd),
                timeout_seconds: Some(5),
                ..Default::default()
            },
        )]
        .into_iter()
        .collect(),
        ..Default::default()
    };

    Fixture::with_config(
        indoc! {"
            cwd test content
        "},
        config,
    )
    .code_action(
        uri(1).to_code_action_params(0, "custom.cwdtest"),
        vec![uri(1).to_edit("cwd_test_marker\n")]
            .to_workspace_edit()
            .to_code_action("Cwd Test", "custom.cwdtest"),
    );
}
