use std::fs;
use std::path::Path;
use std::process::Output;

use assert_cmd::Command;
use predicates::prelude::*;
use serde_json::Value;
use tempfile::tempdir;

fn compat_cmd() -> Command {
    let mut cmd = Command::cargo_bin("shuck").unwrap();
    cmd.env("SHUCK_SHELLCHECK_COMPAT", "1");
    cmd
}

fn run_compat(args: &[&str], cwd: &Path) -> Output {
    compat_cmd().current_dir(cwd).args(args).output().unwrap()
}

fn json1_comments(output: &Output) -> Vec<Value> {
    serde_json::from_slice::<Value>(&output.stdout).unwrap()["comments"]
        .as_array()
        .unwrap()
        .clone()
}

fn comment_by_code(comments: &[Value], code: u64) -> &Value {
    comments
        .iter()
        .find(|comment| comment["code"].as_u64() == Some(code))
        .unwrap()
}

fn has_comment(comments: &[Value], file: &str, code: u64) -> bool {
    comments.iter().any(|comment| {
        comment["file"].as_str() == Some(file) && comment["code"].as_u64() == Some(code)
    })
}

#[test]
fn env_activation_uses_shellcheck_surface() {
    let mut cmd = compat_cmd();
    cmd.arg("--version");
    cmd.assert()
        .success()
        .stdout(predicate::str::contains("ShellCheck compatibility mode"))
        .stdout(predicate::str::contains("engine: shuck"));
}

#[cfg(unix)]
#[test]
fn argv0_basename_shellcheck_activates_compat_mode() {
    use std::os::unix::fs::symlink;

    let tempdir = tempdir().unwrap();
    let link = tempdir.path().join("shellcheck");
    symlink(assert_cmd::cargo::cargo_bin("shuck"), &link).unwrap();

    let mut cmd = Command::new(&link);
    cmd.arg("--version");
    cmd.assert()
        .success()
        .stdout(predicate::str::contains("ShellCheck compatibility mode"));
}

#[test]
fn plain_shuck_help_stays_on_existing_cli() {
    let mut cmd = Command::cargo_bin("shuck").unwrap();
    cmd.arg("--help");
    cmd.assert()
        .success()
        .stdout(predicate::str::contains("Shell checker CLI for shuck"))
        .stdout(predicate::str::contains("ShellCheck compatibility mode").not());
}

#[test]
fn compat_reads_shellcheckrc_from_cwd() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join(".shellcheckrc"), "disable=SC2086\n").unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $foo\n").unwrap();

    let mut cmd = compat_cmd();
    cmd.current_dir(tempdir.path())
        .args(["-f", "json1", "x.sh"]);
    cmd.assert()
        .code(1)
        .stdout(predicate::str::contains("\"code\":2154"))
        .stdout(predicate::str::contains("\"code\":2086").not());
}

#[test]
fn compat_norc_disables_shellcheckrc_search() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join(".shellcheckrc"), "disable=SC2086\n").unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $foo\n").unwrap();

    let mut cmd = compat_cmd();
    cmd.current_dir(tempdir.path())
        .args(["--norc", "-f", "json1", "x.sh"]);
    cmd.assert()
        .code(1)
        .stdout(predicate::str::contains("\"code\":2154"))
        .stdout(predicate::str::contains("\"code\":2086"));
}

#[test]
fn compat_rcfile_overrides_searched_config() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join(".shellcheckrc"), "disable=SC2086\n").unwrap();
    fs::write(tempdir.path().join("alt.rc"), "disable=SC2154\n").unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $foo\n").unwrap();

    let mut cmd = compat_cmd();
    cmd.current_dir(tempdir.path())
        .args(["--rcfile", "alt.rc", "-f", "json1", "x.sh"]);
    cmd.assert()
        .code(1)
        .stdout(predicate::str::contains("\"code\":2086"))
        .stdout(predicate::str::contains("\"code\":2154").not());
}

#[test]
fn compat_include_and_severity_filter_work_on_sc_codes() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $foo\n").unwrap();

    let mut include = compat_cmd();
    include
        .current_dir(tempdir.path())
        .args(["-f", "json1", "--include=SC2086", "x.sh"]);
    include
        .assert()
        .code(1)
        .stdout(predicate::str::contains("\"code\":2086"))
        .stdout(predicate::str::contains("\"code\":2154").not());

    let mut severity = compat_cmd();
    severity
        .current_dir(tempdir.path())
        .args(["-f", "json1", "--severity=warning", "x.sh"]);
    severity
        .assert()
        .code(1)
        .stdout(predicate::str::contains("\"code\":2154"))
        .stdout(predicate::str::contains("\"code\":2086").not());
}

#[test]
fn compat_json1_uses_metadata_info_level_for_sc1091() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\n. ./lib.sh\n").unwrap();

    let output = run_compat(["--norc", "-f", "json1", "x.sh"].as_slice(), tempdir.path());
    assert_eq!(output.status.code(), Some(1));

    let comments = json1_comments(&output);
    let sc1091 = comment_by_code(&comments, 1091);
    assert_eq!(sc1091["level"].as_str(), Some("info"));
}

#[test]
fn compat_no_external_sources_keeps_explicit_sourced_files_available_for_sc1091() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("main.sh"), "#!/bin/sh\n. ./lib.sh\n").unwrap();
    fs::write(tempdir.path().join("lib.sh"), "#!/bin/sh\necho ok\n").unwrap();

    let output = run_compat(
        [
            "--norc",
            "--include=SC1091",
            "-f",
            "json1",
            "main.sh",
            "lib.sh",
        ]
        .as_slice(),
        tempdir.path(),
    );

    assert_eq!(output.status.code(), Some(0));
    assert_eq!(json1_comments(&output), Vec::<Value>::new());
}

#[test]
fn compat_severity_filter_respects_metadata_info_level_for_sc1091() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\n. ./lib.sh\n").unwrap();

    let output = run_compat(
        ["--norc", "-f", "json1", "--severity=warning", "x.sh"].as_slice(),
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(0));
    assert_eq!(json1_comments(&output), Vec::<Value>::new());
}

#[test]
fn compat_json1_uses_metadata_style_level_for_sc2003() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("x.sh"),
        "#!/bin/sh\nprintf '%s\\n' \"$(expr 1 + 2)\"\n",
    )
    .unwrap();

    let output = run_compat(["--norc", "-f", "json1", "x.sh"].as_slice(), tempdir.path());
    assert_eq!(output.status.code(), Some(1));

    let comments = json1_comments(&output);
    let sc2003 = comment_by_code(&comments, 2003);
    assert_eq!(sc2003["level"].as_str(), Some("style"));
}

#[test]
fn compat_severity_filter_respects_metadata_style_level_for_sc2003() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("x.sh"),
        "#!/bin/sh\nprintf '%s\\n' \"$(expr 1 + 2)\"\n",
    )
    .unwrap();

    let output = run_compat(
        ["--norc", "-f", "json1", "--severity=info", "x.sh"].as_slice(),
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(0));
    assert_eq!(json1_comments(&output), Vec::<Value>::new());
}

#[test]
fn compat_include_unknown_sc_code_is_accepted_as_empty_selection() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $foo\n").unwrap();

    let output = run_compat(
        ["--norc", "-f", "json1", "--include=SC9999", "x.sh"].as_slice(),
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(0));
    assert_eq!(json1_comments(&output), Vec::<Value>::new());
}

#[test]
fn compat_exclude_unknown_sc_code_is_ignored() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $foo\n").unwrap();

    let output = run_compat(
        ["--norc", "-f", "json1", "--exclude=SC9999", "x.sh"].as_slice(),
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(1));

    let ordered_codes = json1_comments(&output)
        .into_iter()
        .map(|comment| comment["code"].as_u64().unwrap())
        .collect::<Vec<_>>();
    assert_eq!(ordered_codes, vec![2154, 2086]);
}

#[test]
fn compat_optional_uppercase_check_is_disabled_by_default() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $VAR\n").unwrap();

    let output = run_compat(["--norc", "-f", "json1", "x.sh"].as_slice(), tempdir.path());
    assert_eq!(output.status.code(), Some(1));

    let ordered_codes = json1_comments(&output)
        .into_iter()
        .map(|comment| comment["code"].as_u64().unwrap())
        .collect::<Vec<_>>();
    assert_eq!(ordered_codes, vec![2086]);
}

#[test]
fn compat_shellcheckrc_enable_turns_on_check_unassigned_uppercase() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join(".shellcheckrc"),
        "enable=check-unassigned-uppercase\n",
    )
    .unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $VAR\n").unwrap();

    let output = run_compat(["-f", "json1", "x.sh"].as_slice(), tempdir.path());
    assert_eq!(output.status.code(), Some(1));

    let ordered_codes = json1_comments(&output)
        .into_iter()
        .map(|comment| comment["code"].as_u64().unwrap())
        .collect::<Vec<_>>();
    assert_eq!(ordered_codes, vec![2154, 2086]);
}

#[test]
fn compat_include_sc2248_does_not_select_bare_slash_marker() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\n*/\n").unwrap();

    let output = run_compat(
        ["--norc", "-f", "json1", "--include=SC2248", "x.sh"].as_slice(),
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(0));
    assert_eq!(json1_comments(&output), Vec::<Value>::new());
}

#[test]
fn compat_include_sc2335_does_not_select_unquoted_path_in_mkdir() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("x.sh"),
        "#!/bin/bash\nmkdir -p -m 750 $PKG/var/lib/app\n",
    )
    .unwrap();

    let output = run_compat(
        ["--norc", "-f", "json1", "--include=SC2335", "x.sh"].as_slice(),
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(0));
    assert_eq!(json1_comments(&output), Vec::<Value>::new());
}

#[test]
fn compat_flags_indirect_only_sc2034_targets() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("x.sh"),
        "#!/bin/bash\ntarget=ok\nname=target\nprintf '%s\\n' \"${!name}\"\n",
    )
    .unwrap();

    let output = run_compat(
        ["--norc", "-s", "bash", "-f", "json1", "x.sh"].as_slice(),
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(1));

    let comments = json1_comments(&output);
    let comment = comment_by_code(&comments, 2034);
    assert_eq!(comment["line"].as_u64(), Some(2));
    assert_eq!(comment["endLine"].as_u64(), Some(2));
}

#[test]
fn compat_reports_unreached_nested_functions_as_sc2329() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("x.sh"),
        "#!/bin/bash\nouter() {\n  inner() { :; }\n}\n",
    )
    .unwrap();

    let output = run_compat(
        [
            "--norc",
            "-s",
            "bash",
            "-f",
            "json1",
            "--include=SC2329",
            "x.sh",
        ]
        .as_slice(),
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(1));

    let comments = json1_comments(&output);
    let comment = comment_by_code(&comments, 2329);
    assert_eq!(comment["line"].as_u64(), Some(3));
    assert_eq!(comment["endLine"].as_u64(), Some(3));
}

#[test]
fn compat_accepts_busybox_shell_alias() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "echo hi\n").unwrap();

    let mut cmd = compat_cmd();
    cmd.current_dir(tempdir.path())
        .args(["-s", "busybox", "-f", "json1", "x.sh"]);
    cmd.assert().success();
}

#[test]
fn compat_list_optional_prints_catalog() {
    let mut cmd = compat_cmd();
    cmd.arg("--list-optional");
    cmd.assert()
        .success()
        .stdout(predicate::str::contains("name:    add-default-case"))
        .stdout(predicate::str::contains("name:    useless-use-of-cat"));
}

#[test]
fn compat_check_sourced_reports_resolved_source_diagnostics() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("main.sh"), "#!/bin/sh\n. ./lib.sh\n").unwrap();
    fs::write(tempdir.path().join("lib.sh"), "echo $foo\n").unwrap();

    let mut cmd = compat_cmd();
    cmd.current_dir(tempdir.path())
        .args(["-a", "-f", "json1", "main.sh"]);
    cmd.assert()
        .code(1)
        .stdout(predicate::str::contains("\"file\":\"lib.sh\""))
        .stdout(predicate::str::contains("\"code\":2086"));
}

#[test]
fn compat_external_sources_controls_c001_source_closure() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("main.sh"),
        "#!/bin/sh\nfoo=1\n. ./lib.sh\n",
    )
    .unwrap();
    fs::write(tempdir.path().join("lib.sh"), "printf '%s\\n' \"$foo\"\n").unwrap();

    let output = run_compat(
        &["--norc", "--include=SC2034", "-f", "json1", "main.sh"],
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(1));
    let comments = json1_comments(&output);
    assert!(has_comment(&comments, "main.sh", 2034));

    let output = run_compat(
        &["--norc", "-x", "--include=SC2034", "-f", "json1", "main.sh"],
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(0));
    assert!(json1_comments(&output).is_empty());
}

#[test]
fn compat_external_sources_controls_c006_source_closure() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("main.sh"),
        "#!/bin/sh\n. ./lib.sh\nprintf '%s\\n' \"$foo\"\n",
    )
    .unwrap();
    fs::write(tempdir.path().join("lib.sh"), "foo=1\n").unwrap();

    let output = run_compat(
        &["--norc", "--include=SC2154", "-f", "json1", "main.sh"],
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(1));
    let comments = json1_comments(&output);
    assert!(has_comment(&comments, "main.sh", 2154));

    let output = run_compat(
        &["--norc", "-x", "--include=SC2154", "-f", "json1", "main.sh"],
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(0));
    assert!(json1_comments(&output).is_empty());
}

#[test]
fn compat_check_sourced_does_not_imply_source_closure() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("main.sh"),
        "#!/bin/sh\nfoo=1\n. ./lib.sh\n",
    )
    .unwrap();
    fs::write(
        tempdir.path().join("lib.sh"),
        "#!/bin/sh\nprintf '%s\\n' \"$foo\"\nbar=1\n",
    )
    .unwrap();

    let output = run_compat(
        &["--norc", "-a", "--include=SC2034", "-f", "json1", "main.sh"],
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(1));

    let comments = json1_comments(&output);
    assert!(has_comment(&comments, "main.sh", 2034));
    assert!(has_comment(&comments, "lib.sh", 2034));
}

#[test]
fn compat_color_flags_do_not_consume_filename() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $foo\n").unwrap();

    let mut long = compat_cmd();
    long.current_dir(tempdir.path())
        .args(["--color", "x.sh", "-f", "json1"]);
    long.assert()
        .code(1)
        .stdout(predicate::str::contains("\"code\":2086"));

    let mut short = compat_cmd();
    short
        .current_dir(tempdir.path())
        .args(["-C", "x.sh", "-f", "json1"]);
    short
        .assert()
        .code(1)
        .stdout(predicate::str::contains("\"code\":2086"));
}

#[test]
fn compat_dash_path_reads_from_stdin() {
    let mut cmd = compat_cmd();
    cmd.args(["-f", "json1", "-"])
        .write_stdin("#!/bin/sh\necho $foo\n");
    cmd.assert()
        .code(1)
        .stdout(predicate::str::contains("\"file\":\"-\""))
        .stdout(predicate::str::contains("\"code\":2086"))
        .stderr(predicate::str::is_empty());
}

#[test]
fn compat_enable_all_is_accepted() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $foo\n").unwrap();

    let output = run_compat(
        ["--norc", "--enable=all", "-f", "json1", "x.sh"].as_slice(),
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(1));

    let comments = json1_comments(&output);
    assert!(
        comments
            .iter()
            .any(|comment| comment["code"].as_u64() == Some(2086))
    );
    assert!(
        comments
            .iter()
            .any(|comment| comment["code"].as_u64() == Some(2154))
    );
}

#[test]
fn compat_enable_all_includes_check_unassigned_uppercase() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $VAR\n").unwrap();

    let output = run_compat(
        ["--norc", "--enable=all", "-f", "json1", "x.sh"].as_slice(),
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(1));

    let ordered_codes = json1_comments(&output)
        .into_iter()
        .map(|comment| comment["code"].as_u64().unwrap())
        .collect::<Vec<_>>();
    assert_eq!(ordered_codes, vec![2154, 2086]);
}

#[test]
fn compat_accepts_named_unimplemented_optional_checks_as_noops() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $foo\n").unwrap();

    for check in ["add-default-case", "useless-use-of-cat"] {
        let output = run_compat(
            ["--norc", "--enable", check, "-f", "json1", "x.sh"].as_slice(),
            tempdir.path(),
        );
        assert_eq!(output.status.code(), Some(1), "optional check {check}");

        let comments = json1_comments(&output);
        assert!(
            comments
                .iter()
                .any(|comment| comment["code"].as_u64() == Some(2086))
        );
        assert!(
            comments
                .iter()
                .any(|comment| comment["code"].as_u64() == Some(2154))
        );
    }
}

#[test]
fn compat_check_unassigned_uppercase_enables_sc2154_on_uppercase_names() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $VAR\n").unwrap();

    let output = run_compat(
        [
            "--norc",
            "--enable=check-unassigned-uppercase",
            "-f",
            "json1",
            "x.sh",
        ]
        .as_slice(),
        tempdir.path(),
    );
    assert_eq!(output.status.code(), Some(1));

    let ordered_codes = json1_comments(&output)
        .into_iter()
        .map(|comment| comment["code"].as_u64().unwrap())
        .collect::<Vec<_>>();
    assert_eq!(ordered_codes, vec![2154, 2086]);
}

#[test]
fn compat_json1_orders_higher_severity_before_lower_severity_at_same_span() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $foo\n").unwrap();

    let output = run_compat(["--norc", "-f", "json1", "x.sh"].as_slice(), tempdir.path());
    assert_eq!(output.status.code(), Some(1));

    let comments = json1_comments(&output);
    let ordered_codes = comments
        .iter()
        .map(|comment| comment["code"].as_u64().unwrap())
        .collect::<Vec<_>>();
    assert_eq!(ordered_codes, vec![2154, 2086]);
}

#[test]
fn compat_json1_emits_sc2086_fix_payload_for_plain_expansions() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $foo\n").unwrap();

    let output = run_compat(["--norc", "-f", "json1", "x.sh"].as_slice(), tempdir.path());
    assert_eq!(output.status.code(), Some(1));

    let comments = json1_comments(&output);
    let sc2086 = comment_by_code(&comments, 2086);
    let replacements = sc2086["fix"]["replacements"].as_array().unwrap();
    assert_eq!(replacements.len(), 2);
    assert_eq!(replacements[0]["column"].as_u64(), Some(6));
    assert_eq!(replacements[0]["endColumn"].as_u64(), Some(6));
    assert_eq!(replacements[0]["insertionPoint"].as_str(), Some("afterEnd"));
    assert_eq!(replacements[0]["replacement"].as_str(), Some("\""));
    assert_eq!(replacements[1]["column"].as_u64(), Some(10));
    assert_eq!(replacements[1]["endColumn"].as_u64(), Some(10));
    assert_eq!(
        replacements[1]["insertionPoint"].as_str(),
        Some("beforeStart")
    );
    assert_eq!(replacements[1]["replacement"].as_str(), Some("\""));
}

#[test]
fn compat_json1_emits_sc2086_fix_payload_for_mixed_words() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("x.sh"),
        "#!/bin/sh\nprintf %s prefix${name}suffix\n",
    )
    .unwrap();

    let output = run_compat(["--norc", "-f", "json1", "x.sh"].as_slice(), tempdir.path());
    assert_eq!(output.status.code(), Some(1));

    let comments = json1_comments(&output);
    let sc2086 = comment_by_code(&comments, 2086);
    let replacements = sc2086["fix"]["replacements"].as_array().unwrap();
    assert_eq!(replacements.len(), 2);
    assert_eq!(replacements[0]["column"].as_u64(), Some(17));
    assert_eq!(replacements[0]["endColumn"].as_u64(), Some(17));
    assert_eq!(replacements[0]["insertionPoint"].as_str(), Some("afterEnd"));
    assert_eq!(replacements[1]["column"].as_u64(), Some(24));
    assert_eq!(replacements[1]["endColumn"].as_u64(), Some(24));
    assert_eq!(
        replacements[1]["insertionPoint"].as_str(),
        Some("beforeStart")
    );
}

#[test]
fn compat_diff_reports_when_diagnostics_are_not_auto_fixable() {
    let tempdir = tempdir().unwrap();
    fs::write(
        tempdir.path().join("x.sh"),
        "#!/bin/bash\nprintf '%s\\n' x &;\n",
    )
    .unwrap();

    let output = run_compat(["--norc", "-f", "diff", "x.sh"].as_slice(), tempdir.path());
    assert_eq!(output.status.code(), Some(1));

    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(stdout.is_empty());
    assert_eq!(
        stderr,
        "Issues were detected, but none were auto-fixable. Use another format to see them.\n"
    );
    assert!(!stderr.contains("@@ compatibility mode @@"));
    assert!(!stderr.contains("--- x.sh"));
}

#[test]
fn compat_diff_emits_unified_patch_for_fixable_sc2086() {
    let tempdir = tempdir().unwrap();
    fs::write(tempdir.path().join("x.sh"), "#!/bin/sh\necho $foo\n").unwrap();

    let output = run_compat(["--norc", "-f", "diff", "x.sh"].as_slice(), tempdir.path());
    assert_eq!(output.status.code(), Some(1));

    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("--- a/x.sh"));
    assert!(stdout.contains("+++ b/x.sh"));
    assert!(stdout.contains("-echo $foo"));
    assert!(stdout.contains("+echo \"$foo\""));
}
