use crate::workdir::Workdir;

macro_rules! select_test {
    ($name:ident, $select:expr_2021, $select_no_headers:expr_2021,
     $expected_headers:expr_2021, $expected_rows:expr_2021) => {
        mod $name {
            use super::data;
            use crate::workdir::Workdir;

            #[test]
            fn headers() {
                let wrk = Workdir::new(stringify!($name));
                wrk.create("data.csv", data(true));
                let mut cmd = wrk.command("select");
                cmd.arg("--").arg($select).arg("data.csv");
                let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);

                let expected = vec![
                    $expected_headers
                        .iter()
                        .map(|s| s.to_string())
                        .collect::<Vec<String>>(),
                    $expected_rows
                        .iter()
                        .map(|s| s.to_string())
                        .collect::<Vec<String>>(),
                ];
                assert_eq!(got, expected);
            }

            #[test]
            fn no_headers() {
                let wrk = Workdir::new(stringify!($name));
                wrk.create("data.csv", data(false));
                let mut cmd = wrk.command("select");
                cmd.arg("--no-headers")
                    .arg("--")
                    .arg($select_no_headers)
                    .arg("data.csv");
                let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);

                let expected = vec![
                    $expected_rows
                        .iter()
                        .map(|s| s.to_string())
                        .collect::<Vec<String>>(),
                ];
                assert_eq!(got, expected);
            }
        }
    };
}

macro_rules! select_test_err {
    ($name:ident, $select:expr_2021) => {
        #[test]
        fn $name() {
            let wrk = Workdir::new(stringify!($name));
            wrk.create("data.csv", data(true));
            let mut cmd = wrk.command("select");
            cmd.arg($select).arg("data.csv");
            wrk.assert_err(&mut cmd);
        }
    };
}

fn header_row() -> Vec<String> {
    svec!["h1", "h2", "h[]3", "h4", "h1"]
}

fn data(headers: bool) -> Vec<Vec<String>> {
    let mut rows = vec![svec!["a", "b", "c", "d", "e"]];
    if headers {
        rows.insert(0, header_row())
    }
    rows
}

select_test!(select_simple, "h1", "1", ["h1"], ["a"]);
select_test!(select_simple_idx, "h1[0]", "1", ["h1"], ["a"]);
select_test!(select_simple_idx_2, "h1[1]", "5", ["h1"], ["e"]);

select_test!(select_quoted, r#""h[]3""#, "3", ["h[]3"], ["c"]);
select_test!(select_quoted_idx, r#""h[]3"[0]"#, "3", ["h[]3"], ["c"]);

select_test!(
    select_range,
    "h1-h4",
    "1-4",
    ["h1", "h2", "h[]3", "h4"],
    ["a", "b", "c", "d"]
);

select_test!(
    select_range_multi,
    r#"h1-h2,"h[]3"-h4"#,
    "1-2,3-4",
    ["h1", "h2", "h[]3", "h4"],
    ["a", "b", "c", "d"]
);
select_test!(
    select_range_multi_idx,
    r#"h1-h2,"h[]3"[0]-h4"#,
    "1-2,3-4",
    ["h1", "h2", "h[]3", "h4"],
    ["a", "b", "c", "d"]
);

select_test!(
    select_reverse,
    "h1[1]-h1[0]",
    "5-1",
    ["h1", "h4", "h[]3", "h2", "h1"],
    ["e", "d", "c", "b", "a"]
);

select_test!(
    select_not,
    r#"!"h[]3"[0]"#,
    "!3",
    ["h1", "h2", "h4", "h1"],
    ["a", "b", "d", "e"]
);
select_test!(select_not_range, "!h1[1]-h2", "!5-2", ["h1"], ["a"]);

select_test!(select_duplicate, "h1,h1", "1,1", ["h1", "h1"], ["a", "a"]);
select_test!(
    select_duplicate_range,
    "h1-h2,h1-h2",
    "1-2,1-2",
    ["h1", "h2", "h1", "h2"],
    ["a", "b", "a", "b"]
);
select_test!(
    select_duplicate_range_reverse,
    "h1-h2,h2-h1",
    "1-2,2-1",
    ["h1", "h2", "h2", "h1"],
    ["a", "b", "b", "a"]
);

select_test!(select_range_no_end, "h4-", "4-", ["h4", "h1"], ["d", "e"]);
select_test!(select_range_no_start, "-h2", "-2", ["h1", "h2"], ["a", "b"]);
select_test!(
    select_range_no_end_cat,
    "h4-,h1",
    "4-,1",
    ["h4", "h1", "h1"],
    ["d", "e", "a"]
);
select_test!(
    select_range_no_start_cat,
    "-h2,h1[1]",
    "-2,5",
    ["h1", "h2", "h1"],
    ["a", "b", "e"]
);

select_test!(
    select_regex,
    "/h[1-3]/",
    "1,2,5",
    ["h1", "h2", "h1"],
    ["a", "b", "e"]
);

select_test!(
    select_not_regex,
    "!/h1|h2/",
    "3,4",
    ["h[]3", "h4"],
    ["c", "d"]
);

select_test!(
    select_regex_digit,
    r#"/h\d/"#,
    "1,2,4,5",
    ["h1", "h2", "h4", "h1"],
    ["a", "b", "d", "e"]
);

select_test!(
    select_reverse_sentinel,
    r#"_-1"#,
    "5-1",
    ["h1", "h4", "h[]3", "h2", "h1"],
    ["e", "d", "c", "b", "a"]
);

select_test_err!(select_err_unknown_header, "done");
select_test_err!(select_err_oob_low, "0");
select_test_err!(select_err_oob_high, "6");
select_test_err!(select_err_idx_as_name, "1[0]");
select_test_err!(select_err_idx_oob_high, "h1[2]");
select_test_err!(select_err_idx_not_int, "h1[2.0]");
select_test_err!(select_err_idx_not_int_2, "h1[a]");
select_test_err!(select_err_unclosed_quote, r#""h1"#);
select_test_err!(select_err_unclosed_bracket, r#""h1"[1"#);
select_test_err!(select_err_expected_end_of_field, "a-b-");
// Regression: a single `/` previously triggered a slice-OOB panic in the
// regex-delimiter guard (`chars[1..0]`); now it must be a clean parse error.
select_test_err!(select_err_single_slash, "/");
select_test_err!(select_err_regex_nomatch, "/nomatch/");
select_test_err!(select_err_regex_invalid, "/?/");
select_test_err!(select_err_regex_empty, "//");
select_test_err!(select_err_regex_triple_slash, "///");
select_test_err!(select_err_empty_name_bracket, "[5]");
select_test_err!(select_err_empty_name_leading_comma, ",h1");

#[test]
fn test_select_quoted_name_with_csv_escape() {
    // A header value containing a literal `"` is written by the csv crate
    // as `"foo""bar"` (CSV-style doubled-quote escape). The selector should
    // accept the same `""` escape and match the un-escaped header value.
    let wrk = Workdir::new("test_select_quoted_name_with_csv_escape");
    let data = vec![svec![r#"foo"bar"#, "h2"], svec!["a", "b"]];
    wrk.create("data.csv", data);
    let mut cmd = wrk.command("select");
    cmd.arg("--").arg(r#""foo""bar""#).arg("data.csv");
    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let expected = vec![svec![r#"foo"bar"#], svec!["a"]];
    assert_eq!(got, expected);
}

#[test]
fn test_select_quoted_numeric_is_name_not_index() {
    // Quoting a numeric selector means "the header literally named 1",
    // not "column 1". With headers [foo, 1], `"1"` must pick column 2.
    let wrk = Workdir::new("test_select_quoted_numeric_is_name_not_index");
    let data = vec![svec!["foo", "1"], svec!["a", "b"]];
    wrk.create("data.csv", data);
    let mut cmd = wrk.command("select");
    cmd.arg("--").arg(r#""1""#).arg("data.csv");
    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);
    let expected = vec![svec!["1"], svec!["b"]];
    assert_eq!(got, expected);
}

fn unsorted_data(headers: bool) -> Vec<Vec<String>> {
    let mut rows = vec![
        svec![
            "value1", "value2", "value3", "value4", "value5", "value6", "value7", "value8",
            "value9", "value10"
        ],
        svec!["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
        svec![
            "value10", "value9", "value8", "value7", "value6", "value5", "value4", "value3",
            "value2", "value1"
        ],
    ];
    if headers {
        rows.insert(
            0,
            svec![
                "Günther", "Alice", "Çemil", "Đan", "Fátima", "Héctor", "İbrahim", "Bob", "Jürgen",
                "Élise"
            ],
        );
    }
    rows
}

#[test]
fn test_select_sort() {
    let wrk = Workdir::new("test_select_sort");
    wrk.create("data.csv", unsorted_data(true));
    let mut cmd = wrk.command("select");
    cmd.arg("1-").arg("--sort").arg("data.csv");
    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);

    let expected = vec![
        svec![
            "Alice", "Bob", "Fátima", "Günther", "Héctor", "Jürgen", "Çemil", "Élise", "Đan",
            "İbrahim"
        ],
        svec![
            "value2", "value8", "value5", "value1", "value6", "value9", "value3", "value10",
            "value4", "value7"
        ],
        svec!["2", "8", "5", "1", "6", "9", "3", "10", "4", "7"],
        svec![
            "value9", "value3", "value6", "value10", "value5", "value2", "value8", "value1",
            "value7", "value4"
        ],
    ];
    assert_eq!(got, expected);
}

#[test]
fn test_select_sort_subset() {
    let wrk = Workdir::new("test_select_sort_subset");
    wrk.create("data.csv", unsorted_data(true));
    let mut cmd = wrk.command("select");
    cmd.arg("4,7-9").arg("--sort").arg("data.csv");
    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);

    let expected = vec![
        svec!["Bob", "Jürgen", "Đan", "İbrahim"],
        svec!["value8", "value9", "value4", "value7"],
        svec!["8", "9", "4", "7"],
        svec!["value3", "value2", "value7", "value4"],
    ];
    assert_eq!(got, expected);
}

#[test]
fn test_select_sort_embedded_quote_in_header() {
    // Headers contain an embedded `"`; the prior implementation built a quoted
    // selector string without escaping, which mangled the round-trip.
    let wrk = Workdir::new("test_select_sort_embedded_quote_in_header");
    wrk.create(
        "data.csv",
        vec![
            svec![r#"Say "hi""#, "Alpha", r#"Quote " in middle"#],
            svec!["v1", "v2", "v3"],
        ],
    );
    let mut cmd = wrk.command("select");
    cmd.arg("1-").arg("--sort").arg("data.csv");
    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);

    let expected = vec![
        svec!["Alpha", r#"Quote " in middle"#, r#"Say "hi""#],
        svec!["v2", "v3", "v1"],
    ];
    assert_eq!(got, expected);
}

#[test]
fn test_select_sort_duplicate_header_names() {
    // With duplicates, the prior round-trip resolved every quoted name to the
    // first occurrence, silently dropping the second column. Sorting by index
    // preserves both.
    let wrk = Workdir::new("test_select_sort_duplicate_header_names");
    wrk.create(
        "data.csv",
        vec![svec!["b", "a", "a", "b"], svec!["1", "2", "3", "4"]],
    );
    let mut cmd = wrk.command("select");
    cmd.arg("1-").arg("--sort").arg("data.csv");
    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);

    let expected = vec![svec!["a", "a", "b", "b"], svec!["2", "3", "1", "4"]];
    assert_eq!(got, expected);
}

#[test]
fn test_select_sort_non_utf8_headers() {
    // Headers contain bytes that are not valid UTF-8 (Latin-1 encoded names).
    // The prior implementation went through `String::from_utf8_lossy`, replacing
    // those bytes with U+FFFD; the resolved selector then could not match the
    // original header.
    let wrk = Workdir::new("test_select_sort_non_utf8_headers");
    let path = wrk.path("data.csv");
    // Header row: "café" with é as Latin-1 (0xE9), then "apple", then 0xFF byte.
    // CSV body: a,b,c
    let bytes: Vec<u8> = b"caf\xE9,apple,\xFFbad\n1,2,3\n".to_vec();
    std::fs::write(&path, &bytes).unwrap();

    let mut cmd = wrk.command("select");
    cmd.arg("1-").arg("--sort").arg("data.csv");
    let out = wrk.output(&mut cmd);
    assert!(
        out.status.success(),
        "select --sort failed on non-UTF-8 headers: {}",
        String::from_utf8_lossy(&out.stderr)
    );

    // Lexicographic byte order: "apple" (0x61) < "café" (0x63) < "\xFFbad" (0xFF).
    // Body columns are reordered accordingly: apple,café,\xFFbad → 2,1,3.
    let expected: Vec<u8> = b"apple,caf\xE9,\xFFbad\n2,1,3\n".to_vec();
    assert_eq!(out.stdout, expected);
}

#[test]
fn test_select_random_seeded() {
    let wrk = Workdir::new("test_select_random_seeded");
    wrk.create("data.csv", unsorted_data(true));
    let mut cmd = wrk.command("select");
    cmd.arg("1-")
        .arg("--random")
        .args(["--seed", "42"])
        .arg("data.csv");
    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);

    let expected = vec![
        svec![
            "Jürgen", "İbrahim", "Đan", "Çemil", "Alice", "Héctor", "Élise", "Bob", "Fátima",
            "Günther"
        ],
        svec![
            "value9", "value7", "value4", "value3", "value2", "value6", "value10", "value8",
            "value5", "value1"
        ],
        svec!["9", "7", "4", "3", "2", "6", "10", "8", "5", "1"],
        svec![
            "value2", "value4", "value7", "value8", "value9", "value5", "value1", "value3",
            "value6", "value10"
        ],
    ];
    assert_eq!(got, expected);
}

#[test]
fn test_select_random_seeded_subset() {
    let wrk = Workdir::new("test_select_random_seeded_subset");
    wrk.create("data.csv", unsorted_data(true));
    let mut cmd = wrk.command("select");
    cmd.arg("4,7-9")
        .arg("--random")
        .args(["--seed", "42"])
        .arg("data.csv");
    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);

    let expected = vec![
        svec!["İbrahim", "Đan", "Jürgen", "Bob"],
        svec!["value7", "value4", "value9", "value8"],
        svec!["7", "4", "9", "8"],
        svec!["value4", "value7", "value2", "value3"],
    ];
    assert_eq!(got, expected);
}

// Add tests for semicolon-separated CSV files
#[test]
fn test_select_semicolon_separator() {
    let wrk = Workdir::new("test_select_semicolon_separator");
    // Create a CSV file with semicolon separator
    let data = vec![svec!["h1;h2;h3;h4"], svec!["a;b;c;d"]];
    wrk.create("data.csv", data);

    // Test with default separator (should fail to parse correctly)
    let mut cmd = wrk.command("select");
    cmd.arg("h1").arg("data.csv");

    wrk.assert_err(&mut cmd);

    // Test with explicit semicolon separator
    let mut cmd = wrk.command("select");
    cmd.arg("--delimiter")
        .arg(";")
        .arg("--")
        .arg("h1")
        .arg("data.csv");
    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);

    // Now the output should be correct
    let expected = vec![svec!["h1"], svec!["a"]];
    assert_eq!(got, expected);
}

// Test handling of out-of-bounds indices
#[test]
fn test_select_out_of_bounds_indices() {
    let wrk = Workdir::new("test_select_out_of_bounds_indices");
    wrk.create("data.csv", data(true));

    // Test with an index that's out of bounds
    let mut cmd = wrk.command("select");
    cmd.arg("--").arg("10").arg("data.csv");

    // This should not panic with our new robust implementation
    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);

    // The output should be empty because the index is out of bounds
    let expected: Vec<Vec<String>> = vec![];
    assert_eq!(got, expected);
}

// Test the robustness of the select function with different CSV formats
#[test]
fn test_select_robustness() {
    let wrk = Workdir::new("test_select_robustness");

    // Test with a CSV file that has fewer columns than expected
    let data = vec![
        svec!["h1", "h2"], // Only 2 columns instead of 5
        svec!["a", "b"],
    ];
    wrk.create("data.csv", data);

    // Test with an index that's out of bounds
    let mut cmd = wrk.command("select");
    cmd.arg("--").arg("5").arg("data.csv");

    // This should not panic with our new robust implementation
    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);

    // The output should be empty because the index is out of bounds
    let expected: Vec<Vec<String>> = vec![];
    assert_eq!(got, expected);

    // Test with a valid index
    let mut cmd = wrk.command("select");
    cmd.arg("--").arg("2").arg("data.csv");

    // This should work correctly
    let got: Vec<Vec<String>> = wrk.read_stdout(&mut cmd);

    // The output should contain the second column
    let expected = vec![svec!["h2"], svec!["b"]];
    assert_eq!(got, expected);
}
