use std::fs;

use tempfile::tempdir;

#[path = "common/compat.rs"]
mod compat;

use compat::{
    SCENARIOS, build_ryl_command, build_yamllint_command, capture_with_env,
    ensure_yamllint_installed,
};

#[test]
fn braces_rule_matches_yamllint() {
    ensure_yamllint_installed();

    let dir = tempdir().unwrap();

    let default_cfg = dir.path().join("braces-default.yml");
    fs::write(
        &default_cfg,
        "rules:\n  document-start: disable\n  braces: enable\n",
    )
    .unwrap();

    let warning_cfg = dir.path().join("braces-warning.yml");
    fs::write(
        &warning_cfg,
        "rules:\n  document-start: disable\n  braces:\n    level: warning\n",
    )
    .unwrap();

    let min_cfg = dir.path().join("braces-min.yml");
    fs::write(
        &min_cfg,
        "rules:\n  document-start: disable\n  braces:\n    min-spaces-inside: 1\n    max-spaces-inside: -1\n",
    )
    .unwrap();

    let max_cfg = dir.path().join("braces-max.yml");
    fs::write(
        &max_cfg,
        "rules:\n  document-start: disable\n  braces:\n    max-spaces-inside: 1\n",
    )
    .unwrap();

    let empty_cfg = dir.path().join("braces-empty.yml");
    fs::write(
        &empty_cfg,
        "rules:\n  document-start: disable\n  braces:\n    min-spaces-inside-empty: 1\n    max-spaces-inside-empty: 2\n",
    )
    .unwrap();

    let forbid_cfg = dir.path().join("braces-forbid.yml");
    fs::write(
        &forbid_cfg,
        "rules:\n  document-start: disable\n  braces:\n    forbid: true\n",
    )
    .unwrap();

    let forbid_non_empty_cfg = dir.path().join("braces-forbid-non-empty.yml");
    fs::write(
        &forbid_non_empty_cfg,
        "rules:\n  document-start: disable\n  braces:\n    forbid: non-empty\n",
    )
    .unwrap();

    let default_violation = dir.path().join("default.yaml");
    fs::write(&default_violation, "---\nobject: { key: 1 }\n").unwrap();

    let min_violation = dir.path().join("min.yaml");
    fs::write(&min_violation, "---\nobject: {key: 1}\n").unwrap();

    let max_violation = dir.path().join("max.yaml");
    fs::write(&max_violation, "---\nobject: {  key: 1   }\n").unwrap();

    let empty_min_violation = dir.path().join("empty-min.yaml");
    fs::write(&empty_min_violation, "---\nobject: {}\n").unwrap();

    let empty_max_violation = dir.path().join("empty-max.yaml");
    fs::write(&empty_max_violation, "---\nobject: {    }\n").unwrap();

    let forbid_violation = dir.path().join("forbid.yaml");
    fs::write(&forbid_violation, "---\nobject: {key: 1}\n").unwrap();

    let forbid_spaces_violation = dir.path().join("forbid-spaces.yaml");
    fs::write(&forbid_spaces_violation, "---\nobject: { key: 1 }\n").unwrap();

    let exe = env!("CARGO_BIN_EXE_ryl");

    for scenario in SCENARIOS {
        let mut ryl_default = build_ryl_command(exe, scenario.ryl_format);
        ryl_default
            .arg("-c")
            .arg(&default_cfg)
            .arg(&default_violation);
        let (ryl_default_code, ryl_default_output) =
            capture_with_env(ryl_default, scenario.envs);

        let mut yam_default = build_yamllint_command(scenario.yam_format);
        yam_default
            .arg("-c")
            .arg(&default_cfg)
            .arg(&default_violation);
        let (yam_default_code, yam_default_output) =
            capture_with_env(yam_default, scenario.envs);

        assert_eq!(ryl_default_code, 1, "ryl default exit ({})", scenario.label);
        assert_eq!(
            yam_default_code, 1,
            "yamllint default exit ({})",
            scenario.label
        );
        assert_eq!(
            ryl_default_output, yam_default_output,
            "default diagnostics mismatch ({})",
            scenario.label
        );

        let mut ryl_warning = build_ryl_command(exe, scenario.ryl_format);
        ryl_warning
            .arg("-c")
            .arg(&warning_cfg)
            .arg(&default_violation);
        let (ryl_warning_code, ryl_warning_output) =
            capture_with_env(ryl_warning, scenario.envs);

        let mut yam_warning = build_yamllint_command(scenario.yam_format);
        yam_warning
            .arg("-c")
            .arg(&warning_cfg)
            .arg(&default_violation);
        let (yam_warning_code, yam_warning_output) =
            capture_with_env(yam_warning, scenario.envs);

        assert_eq!(ryl_warning_code, 0, "ryl warning exit ({})", scenario.label);
        assert_eq!(
            yam_warning_code, 0,
            "yamllint warning exit ({})",
            scenario.label
        );
        assert_eq!(
            ryl_warning_output, yam_warning_output,
            "warning diagnostics mismatch ({})",
            scenario.label
        );

        let mut ryl_min = build_ryl_command(exe, scenario.ryl_format);
        ryl_min.arg("-c").arg(&min_cfg).arg(&min_violation);
        let (ryl_min_code, ryl_min_output) = capture_with_env(ryl_min, scenario.envs);

        let mut yam_min = build_yamllint_command(scenario.yam_format);
        yam_min.arg("-c").arg(&min_cfg).arg(&min_violation);
        let (yam_min_code, yam_min_output) = capture_with_env(yam_min, scenario.envs);

        assert_eq!(ryl_min_code, 1, "ryl min exit ({})", scenario.label);
        assert_eq!(yam_min_code, 1, "yamllint min exit ({})", scenario.label);
        assert_eq!(
            ryl_min_output, yam_min_output,
            "min diagnostics mismatch ({})",
            scenario.label
        );

        let mut ryl_max = build_ryl_command(exe, scenario.ryl_format);
        ryl_max.arg("-c").arg(&max_cfg).arg(&max_violation);
        let (ryl_max_code, ryl_max_output) = capture_with_env(ryl_max, scenario.envs);

        let mut yam_max = build_yamllint_command(scenario.yam_format);
        yam_max.arg("-c").arg(&max_cfg).arg(&max_violation);
        let (yam_max_code, yam_max_output) = capture_with_env(yam_max, scenario.envs);

        assert_eq!(ryl_max_code, 1, "ryl max exit ({})", scenario.label);
        assert_eq!(yam_max_code, 1, "yamllint max exit ({})", scenario.label);
        assert_eq!(
            ryl_max_output, yam_max_output,
            "max diagnostics mismatch ({})",
            scenario.label
        );

        let mut ryl_empty_min = build_ryl_command(exe, scenario.ryl_format);
        ryl_empty_min
            .arg("-c")
            .arg(&empty_cfg)
            .arg(&empty_min_violation);
        let (ryl_empty_min_code, ryl_empty_min_output) =
            capture_with_env(ryl_empty_min, scenario.envs);

        let mut yam_empty_min = build_yamllint_command(scenario.yam_format);
        yam_empty_min
            .arg("-c")
            .arg(&empty_cfg)
            .arg(&empty_min_violation);
        let (yam_empty_min_code, yam_empty_min_output) =
            capture_with_env(yam_empty_min, scenario.envs);

        assert_eq!(
            ryl_empty_min_code, 1,
            "ryl empty-min exit ({})",
            scenario.label
        );
        assert_eq!(
            yam_empty_min_code, 1,
            "yamllint empty-min exit ({})",
            scenario.label
        );
        assert_eq!(
            ryl_empty_min_output, yam_empty_min_output,
            "empty-min diagnostics mismatch ({})",
            scenario.label
        );

        let mut ryl_empty_max = build_ryl_command(exe, scenario.ryl_format);
        ryl_empty_max
            .arg("-c")
            .arg(&empty_cfg)
            .arg(&empty_max_violation);
        let (ryl_empty_max_code, ryl_empty_max_output) =
            capture_with_env(ryl_empty_max, scenario.envs);

        let mut yam_empty_max = build_yamllint_command(scenario.yam_format);
        yam_empty_max
            .arg("-c")
            .arg(&empty_cfg)
            .arg(&empty_max_violation);
        let (yam_empty_max_code, yam_empty_max_output) =
            capture_with_env(yam_empty_max, scenario.envs);

        assert_eq!(
            ryl_empty_max_code, 1,
            "ryl empty-max exit ({})",
            scenario.label
        );
        assert_eq!(
            yam_empty_max_code, 1,
            "yamllint empty-max exit ({})",
            scenario.label
        );
        assert_eq!(
            ryl_empty_max_output, yam_empty_max_output,
            "empty-max diagnostics mismatch ({})",
            scenario.label
        );

        let mut ryl_forbid = build_ryl_command(exe, scenario.ryl_format);
        ryl_forbid.arg("-c").arg(&forbid_cfg).arg(&forbid_violation);
        let (ryl_forbid_code, ryl_forbid_output) =
            capture_with_env(ryl_forbid, scenario.envs);

        let mut yam_forbid = build_yamllint_command(scenario.yam_format);
        yam_forbid.arg("-c").arg(&forbid_cfg).arg(&forbid_violation);
        let (yam_forbid_code, yam_forbid_output) =
            capture_with_env(yam_forbid, scenario.envs);

        assert_eq!(ryl_forbid_code, 1, "ryl forbid exit ({})", scenario.label);
        assert_eq!(
            yam_forbid_code, 1,
            "yamllint forbid exit ({})",
            scenario.label
        );
        assert_eq!(
            ryl_forbid_output, yam_forbid_output,
            "forbid diagnostics mismatch ({})",
            scenario.label
        );

        let mut ryl_forbid_spaces = build_ryl_command(exe, scenario.ryl_format);
        ryl_forbid_spaces
            .arg("-c")
            .arg(&forbid_non_empty_cfg)
            .arg(&forbid_spaces_violation);
        let (ryl_forbid_spaces_code, ryl_forbid_spaces_output) =
            capture_with_env(ryl_forbid_spaces, scenario.envs);

        let mut yam_forbid_spaces = build_yamllint_command(scenario.yam_format);
        yam_forbid_spaces
            .arg("-c")
            .arg(&forbid_non_empty_cfg)
            .arg(&forbid_spaces_violation);
        let (yam_forbid_spaces_code, yam_forbid_spaces_output) =
            capture_with_env(yam_forbid_spaces, scenario.envs);

        assert_eq!(
            ryl_forbid_spaces_code, 1,
            "ryl forbid-non-empty exit ({})",
            scenario.label
        );
        assert_eq!(
            yam_forbid_spaces_code, 1,
            "yamllint forbid-non-empty exit ({})",
            scenario.label
        );
        assert_eq!(
            ryl_forbid_spaces_output, yam_forbid_spaces_output,
            "forbid-non-empty diagnostics mismatch ({})",
            scenario.label
        );

        let mut ryl_forbid_empty = build_ryl_command(exe, scenario.ryl_format);
        ryl_forbid_empty
            .arg("-c")
            .arg(&forbid_non_empty_cfg)
            .arg(&empty_min_violation);
        let (ryl_forbid_empty_code, ryl_forbid_empty_output) =
            capture_with_env(ryl_forbid_empty, scenario.envs);

        let mut yam_forbid_empty = build_yamllint_command(scenario.yam_format);
        yam_forbid_empty
            .arg("-c")
            .arg(&forbid_non_empty_cfg)
            .arg(&empty_min_violation);
        let (yam_forbid_empty_code, yam_forbid_empty_output) =
            capture_with_env(yam_forbid_empty, scenario.envs);

        assert_eq!(
            ryl_forbid_empty_code, 0,
            "ryl forbid-non-empty empty exit ({})",
            scenario.label
        );
        assert_eq!(
            yam_forbid_empty_code, 0,
            "yamllint forbid-non-empty empty exit ({})",
            scenario.label
        );
        assert_eq!(
            ryl_forbid_empty_output, yam_forbid_empty_output,
            "forbid-non-empty empty diagnostics mismatch ({})",
            scenario.label
        );
    }
}

#[test]
fn nested_double_curly_flow_mapping_matches_yamllint() {
    ensure_yamllint_installed();

    let dir = tempdir().unwrap();
    let cfg = dir.path().join("braces-forbid.yml");
    fs::write(
        &cfg,
        "rules:\n  document-start: disable\n  braces:\n    forbid: true\n",
    )
    .unwrap();

    let input = dir.path().join("double-curly.yml");
    fs::write(&input, "outer: {{inner: 1}: 2}\n").unwrap();

    let exe = env!("CARGO_BIN_EXE_ryl");

    for scenario in SCENARIOS {
        let mut ryl_cmd = build_ryl_command(exe, scenario.ryl_format);
        ryl_cmd.arg("-c").arg(&cfg).arg(&input);
        let (ryl_code, ryl_msg) = capture_with_env(ryl_cmd, scenario.envs);

        let mut yam_cmd = build_yamllint_command(scenario.yam_format);
        yam_cmd.arg("-c").arg(&cfg).arg(&input);
        let (yam_code, yam_msg) = capture_with_env(yam_cmd, scenario.envs);

        assert_eq!(
            ryl_code, yam_code,
            "exit mismatch for nested double-curly flow mapping ({})",
            scenario.label
        );
        assert_eq!(
            ryl_msg, yam_msg,
            "diagnostics mismatch for nested double-curly flow mapping ({})",
            scenario.label
        );
    }
}

#[test]
fn github_actions_payload_expression_braces_match_yamllint() {
    ensure_yamllint_installed();

    let dir = tempdir().unwrap();
    let cfg = dir.path().join("cfg.yml");
    fs::write(
        &cfg,
        "extends: default\nrules:\n  line-length:\n    max: 110\n",
    )
    .unwrap();

    let input = dir.path().join("input.yml");
    fs::write(
        &input,
        "---\nsteps:\n  - uses: slackapi/slack-github-action@v2.1.1\n    with:\n      token: ${{ env.SLACK_BOT_TOKEN }}\n      # yamllint disable rule:line-length\n      payload: |\n        channel: \"internal-airflow-ci-cd\"\n        text: \"🚨🕒 Failure Alert: ${{ env.workflow_id }} on branch *${{ env.branch }}* 🕒🚨\\n\\n*Details:* <${{ env.run_url }}|View the failure log>\"\n        blocks:\n          - type: \"section\"\n            text:\n              type: \"mrkdwn\"\n              text: \"🚨🕒 Failure Alert: ${{ env.workflow_id }} ${{ env.branch }} 🕒🚨\\n\\n*Details:* <${{ env.run_url }}|View the failure log>\"\n    env:\n      run_url: ${{ steps.find-workflow-run-status.outputs.run-url }}\n      branch: ${{ matrix.branch }}\n      workflow_id: ${{ matrix.workflow-id }}\n",
    )
    .unwrap();

    let exe = env!("CARGO_BIN_EXE_ryl");
    for scenario in SCENARIOS {
        let mut ryl_cmd = build_ryl_command(exe, scenario.ryl_format);
        ryl_cmd.arg("-c").arg(&cfg).arg("--strict").arg(&input);
        let (ryl_code, ryl_msg) = capture_with_env(ryl_cmd, scenario.envs);

        let mut yam_cmd = build_yamllint_command(scenario.yam_format);
        yam_cmd.arg("-c").arg(&cfg).arg("--strict").arg(&input);
        let (yam_code, yam_msg) = capture_with_env(yam_cmd, scenario.envs);

        assert_eq!(
            ryl_code, yam_code,
            "exit mismatch for github actions payload expressions ({})",
            scenario.label
        );
        assert_eq!(
            ryl_msg, yam_msg,
            "diagnostics mismatch for github actions payload expressions ({})",
            scenario.label
        );
    }
}
