//! Integration tests comparing jarq output with jq
//!
//! These tests require `jq` to be installed on the system.
//! If jq is not available, tests will be skipped.

use std::process::Command;

/// Check if jq is available on the system
fn jq_available() -> bool {
    Command::new("jq")
        .arg("--version")
        .output()
        .map(|o| o.status.success())
        .unwrap_or(false)
}

/// Run jq with the given filter and input, return stdout
fn run_jq(filter: &str, input: &str) -> Result<String, String> {
    let output = Command::new("jq")
        .arg(filter)
        .stdin(std::process::Stdio::piped())
        .stdout(std::process::Stdio::piped())
        .stderr(std::process::Stdio::piped())
        .spawn()
        .and_then(|mut child| {
            use std::io::Write;
            child
                .stdin
                .take()
                .unwrap()
                .write_all(input.as_bytes())
                .unwrap();
            child.wait_with_output()
        })
        .map_err(|e| format!("Failed to run jq: {}", e))?;

    if output.status.success() {
        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    } else {
        Err(String::from_utf8_lossy(&output.stderr).to_string())
    }
}

/// Run jarq with the given filter and input, return stdout
fn run_jarq(filter: &str, input: &str) -> Result<String, String> {
    // Use the library directly instead of spawning a process
    let mut input_bytes = input.as_bytes().to_vec();
    let value = simd_json::to_owned_value(&mut input_bytes)
        .map_err(|e| format!("Invalid JSON input: {}", e))?;

    let results =
        jarq::filter::evaluate_all(filter, &[value]).map_err(|e| format!("Eval error: {}", e))?;

    // Format output like jq does (one JSON value per line, pretty-printed)
    let output: Vec<String> = results
        .iter()
        .map(|v| jarq::json::to_string_pretty(v))
        .collect();

    Ok(output.join("\n") + if output.is_empty() { "" } else { "\n" })
}

/// Compare jarq output with jq output
fn compare_with_jq(filter: &str, input: &str, description: &str) {
    if !jq_available() {
        eprintln!("Skipping test '{}': jq not available", description);
        return;
    }

    let jq_result = run_jq(filter, input);
    let jarq_result = run_jarq(filter, input);

    match (&jq_result, &jarq_result) {
        (Ok(jq_out), Ok(jarq_out)) => {
            assert_eq!(
                jq_out, jarq_out,
                "\n{}\nFilter: {}\nInput: {}\njq output:\n{}\njarq output:\n{}",
                description, filter, input, jq_out, jarq_out
            );
        }
        (Err(_), Err(_)) => {
            // Both errored, that's acceptable
        }
        (Ok(jq_out), Err(jarq_err)) => {
            panic!(
                "\n{}\nFilter: {}\nInput: {}\njq succeeded with:\n{}\njarq failed with: {}",
                description, filter, input, jq_out, jarq_err
            );
        }
        (Err(jq_err), Ok(jarq_out)) => {
            panic!(
                "\n{}\nFilter: {}\nInput: {}\njq failed with: {}\njarq succeeded with:\n{}",
                description, filter, input, jq_err, jarq_out
            );
        }
    }
}

/// Macro to generate jq compatibility tests
macro_rules! jq_test {
    ($name:ident, $filter:expr, $input:expr, $desc:expr) => {
        #[test]
        fn $name() {
            compare_with_jq($filter, $input, $desc);
        }
    };
}

mod jq_compat {
    use super::compare_with_jq;

    // =============================================================================
    // Basic Access
    // =============================================================================

    jq_test!(identity, ".", r#"{"name":"Alice","age":30}"#, "identity");
    jq_test!(
        field_access,
        ".name",
        r#"{"name":"Alice","age":30}"#,
        "field access"
    );
    jq_test!(
        missing_field,
        ".missing",
        r#"{"name":"Alice","age":30}"#,
        "missing field"
    );
    jq_test!(
        nested_field,
        ".a.b.c",
        r#"{"a":{"b":{"c":1}}}"#,
        "nested field"
    );
    jq_test!(array_index, ".[0]", "[1,2,3]", "array index");
    jq_test!(negative_index, ".[-1]", "[1,2,3]", "negative index");
    jq_test!(out_of_bounds, ".[10]", "[1,2,3]", "out of bounds");

    // =============================================================================
    // Iteration
    // =============================================================================

    jq_test!(array_iterate, ".[]", "[1,2,3]", "array iterate");
    jq_test!(object_iterate, ".[]", r#"{"a":1,"b":2}"#, "object iterate");
    jq_test!(
        nested_iterate,
        ".items[].id",
        r#"{"items":[{"id":1},{"id":2}]}"#,
        "nested iterate"
    );

    // =============================================================================
    // Pipes
    // =============================================================================

    jq_test!(
        pipe_to_index,
        ".items | .[0]",
        r#"{"items":[1,2,3]}"#,
        "pipe to index"
    );
    jq_test!(
        chained_builtins,
        "sort | reverse",
        "[3,1,2]",
        "chained builtins"
    );

    // =============================================================================
    // Builtins
    // =============================================================================

    jq_test!(length_array, "length", "[1,2,3]", "length array");
    jq_test!(length_string, "length", r#""hello""#, "length string");
    jq_test!(keys, "keys", r#"{"a":1,"b":2}"#, "keys");
    jq_test!(type_builtin, "type", "42", "type");
    jq_test!(first, "first", "[1,2,3]", "first");
    jq_test!(last, "last", "[1,2,3]", "last");
    jq_test!(sort, "sort", "[3,1,2]", "sort");
    jq_test!(reverse, "reverse", "[1,2,3]", "reverse");
    jq_test!(min, "min", "[3,1,4,1,5]", "min");
    jq_test!(max, "max", "[3,1,4,1,5]", "max");
    jq_test!(unique, "unique", "[1,2,1,3,2]", "unique");
    jq_test!(flatten, "flatten", "[[1,2],[3,4]]", "flatten");
    jq_test!(add_arrays, "add", "[[1,2],[3,4]]", "add arrays");
    jq_test!(not_null, "not", "null", "not null");
    jq_test!(not_true, "not", "true", "not true");

    // =============================================================================
    // Array Construction
    // =============================================================================

    jq_test!(simple_array, "[.a, .b]", r#"{"a":1,"b":2}"#, "simple array");
    jq_test!(empty_array, "[]", r#"{"x":1}"#, "empty array");
    jq_test!(collect_iterate, "[.[]]", "[1,2,3]", "collect iterate");

    // =============================================================================
    // Object Construction
    // =============================================================================

    jq_test!(
        simple_object,
        "{name: .first}",
        r#"{"first":"Alice","last":"Smith"}"#,
        "simple object"
    );
    jq_test!(empty_object, "{}", r#"{"x":1}"#, "empty object");
    jq_test!(
        dynamic_key,
        "{(.key): .value}",
        r#"{"key":"name","value":"Alice"}"#,
        "dynamic key"
    );

    // =============================================================================
    // map() and select()
    // =============================================================================

    jq_test!(
        map_field_access,
        "map(.a)",
        r#"[{"a":1},{"a":2}]"#,
        "map field access"
    );
    jq_test!(map_identity, "map(.)", "[1,2,3]", "map identity");
    jq_test!(map_index, "map(.[0])", "[[1,2],[3,4]]", "map index");
    jq_test!(map_empty_array, "map(.x)", "[]", "map empty array");
    jq_test!(
        select_truthy,
        ".[] | select(.x)",
        r#"[{"x":true},{"x":false},{"x":true}]"#,
        "select truthy"
    );
    jq_test!(
        select_with_gt,
        ".[] | select(. > 2)",
        "[1,2,3,4,5]",
        "select with gt"
    );
    jq_test!(
        select_filters_null_false,
        ".[] | select(.)",
        r#"[null,false,true,0,"",1]"#,
        "select filters null/false"
    );
    jq_test!(
        select_on_object,
        "select(.active)",
        r#"{"active":true}"#,
        "select on object"
    );
    jq_test!(
        select_false_outputs_nothing,
        "select(.active)",
        r#"{"active":false}"#,
        "select false outputs nothing"
    );

    // =============================================================================
    // Comparison Operators
    // =============================================================================

    jq_test!(eq_number_true, ". == 5", "5", "eq number true");
    jq_test!(eq_number_false, ". == 6", "5", "eq number false");
    jq_test!(
        eq_string_true,
        r#". == "foo""#,
        r#""foo""#,
        "eq string true"
    );
    jq_test!(
        eq_string_false,
        r#". == "bar""#,
        r#""foo""#,
        "eq string false"
    );
    jq_test!(eq_null, ". == null", "null", "eq null");
    jq_test!(ne_true, ". != 6", "5", "ne true");
    jq_test!(ne_false, ". != 5", "5", "ne false");
    jq_test!(lt_true, ". < 10", "5", "lt true");
    jq_test!(lt_false, ". < 5", "5", "lt false");
    jq_test!(le_equal, ". <= 5", "5", "le equal");
    jq_test!(le_less, ". <= 10", "5", "le less");
    jq_test!(gt_true, ". > 3", "5", "gt true");
    jq_test!(gt_false, ". > 5", "5", "gt false");
    jq_test!(ge_equal, ". >= 5", "5", "ge equal");
    jq_test!(ge_greater, ". >= 3", "5", "ge greater");
    jq_test!(lt_string, r#". < "banana""#, r#""apple""#, "lt string");
    jq_test!(eq_array, ". == [1,2]", "[1,2]", "eq array");
    jq_test!(eq_object, r#". == {"a":1}"#, r#"{"a":1}"#, "eq object");
    jq_test!(
        select_with_ge,
        ".[] | select(.age >= 25)",
        r#"[{"age":25},{"age":30},{"age":20}]"#,
        "select with ge"
    );

    // =============================================================================
    // Boolean Operators
    // =============================================================================

    jq_test!(and_both_true, "true and true", "true", "and both true");
    jq_test!(and_one_false, "true and false", "true", "and one false");
    jq_test!(and_first_false, "false and true", "true", "and first false");
    jq_test!(and_both_false, "false and false", "true", "and both false");
    jq_test!(or_both_true, "true or true", "true", "or both true");
    jq_test!(or_one_true, "true or false", "true", "or one true");
    jq_test!(or_second_true, "false or true", "true", "or second true");
    jq_test!(or_both_false, "false or false", "true", "or both false");
    jq_test!(
        and_with_comparisons,
        ". > 3 and . < 10",
        "5",
        "and with comparisons"
    );
    jq_test!(
        or_with_comparisons,
        ". < 3 or . > 4",
        "5",
        "or with comparisons"
    );
    jq_test!(
        select_with_and,
        ".[] | select(.a > 1 and .b)",
        r#"[{"a":1,"b":true},{"a":2,"b":false},{"a":3,"b":true}]"#,
        "select with and"
    );
    jq_test!(
        select_with_or,
        ".[] | select(.x < 3 or .x > 7)",
        r#"[{"x":1},{"x":5},{"x":10}]"#,
        "select with or"
    );
    jq_test!(
        compound_and,
        ".x > 0 and .x < 10",
        r#"{"x":5}"#,
        "compound and"
    );
    jq_test!(
        field_and_field,
        ".active and .admin",
        r#"{"active":true,"admin":false}"#,
        "field and field"
    );
    jq_test!(
        field_or_field,
        ".active or .admin",
        r#"{"active":true,"admin":false}"#,
        "field or field"
    );

    // =============================================================================
    // Higher-Order Functions
    // =============================================================================

    jq_test!(
        sort_by_field,
        "sort_by(.age)",
        r#"[{"age":30},{"age":20},{"age":25}]"#,
        "sort_by field"
    );
    jq_test!(
        sort_by_string,
        "sort_by(.name)",
        r#"[{"name":"bob"},{"name":"alice"}]"#,
        "sort_by string"
    );
    jq_test!(sort_by_empty, "sort_by(.x)", "[]", "sort_by empty");
    jq_test!(
        group_by_field,
        "group_by(.t)",
        r#"[{"t":"a","v":1},{"t":"b","v":2},{"t":"a","v":3}]"#,
        "group_by field"
    );
    jq_test!(group_by_empty, "group_by(.x)", "[]", "group_by empty");
    jq_test!(
        unique_by_field,
        "unique_by(.id)",
        r#"[{"id":1,"v":"a"},{"id":2,"v":"b"},{"id":1,"v":"c"}]"#,
        "unique_by field"
    );
    jq_test!(unique_by_empty, "unique_by(.x)", "[]", "unique_by empty");
    jq_test!(
        min_by_field,
        "min_by(.age)",
        r#"[{"age":30},{"age":20},{"age":25}]"#,
        "min_by field"
    );
    jq_test!(min_by_empty, "min_by(.x)", "[]", "min_by empty");
    jq_test!(
        max_by_field,
        "max_by(.score)",
        r#"[{"score":10},{"score":50},{"score":30}]"#,
        "max_by field"
    );
    jq_test!(max_by_empty, "max_by(.x)", "[]", "max_by empty");
    jq_test!(
        group_then_max_by,
        "group_by(.d) | map(max_by(.s))",
        r#"[{"d":"eng","s":100},{"d":"sales","s":80},{"d":"eng","s":120}]"#,
        "group then max_by"
    );

    // =============================================================================
    // Conditionals
    // =============================================================================

    jq_test!(
        if_then_else_true,
        r#"if .x > 0 then "positive" else "non-positive" end"#,
        r#"{"x":5}"#,
        "if-then-else true"
    );
    jq_test!(
        if_then_else_false,
        r#"if .x > 0 then "positive" else "non-positive" end"#,
        r#"{"x":-1}"#,
        "if-then-else false"
    );
    jq_test!(
        if_then_else_eq,
        r#"if .x == 0 then "zero" else "non-zero" end"#,
        r#"{"x":0}"#,
        "if-then-else eq"
    );
    jq_test!(
        if_then_else_with_expr,
        r#"if . > 3 then "big" else "small" end"#,
        "5",
        "if-then-else with expr"
    );
    jq_test!(
        if_inside_map,
        r#"map(if . > 3 then "big" else "small" end)"#,
        "[1,2,3,4,5]",
        "if inside map"
    );

    // =============================================================================
    // Alternative Operator
    // =============================================================================

    jq_test!(alt_null, r#". // "default""#, "null", "alt null");
    jq_test!(alt_false, r#". // "default""#, "false", "alt false");
    jq_test!(
        alt_field_null,
        r#".name // "unknown""#,
        r#"{"name":null}"#,
        "alt field null"
    );
    jq_test!(
        alt_field_missing,
        r#".name // "unknown""#,
        "{}",
        "alt field missing"
    );
    jq_test!(
        alt_field_present,
        r#".name // "unknown""#,
        r#"{"name":"alice"}"#,
        "alt field present"
    );
    jq_test!(
        alt_zero_truthy,
        r#". // "default""#,
        "0",
        "alt zero (truthy)"
    );
    jq_test!(
        alt_empty_string_truthy,
        r#". // "default""#,
        r#""""#,
        "alt empty string (truthy)"
    );
    jq_test!(
        alt_chain,
        r#"null // false // "fallback""#,
        "null",
        "alt chain"
    );

    // =============================================================================
    // has()
    // =============================================================================

    jq_test!(
        has_present,
        r#"has("name")"#,
        r#"{"name":"alice","age":30}"#,
        "has present"
    );
    jq_test!(
        has_missing,
        r#"has("name")"#,
        r#"{"age":30}"#,
        "has missing"
    );
    jq_test!(
        has_null_value,
        r#"has("name")"#,
        r#"{"name":null}"#,
        "has null value"
    );
    jq_test!(
        has_in_select,
        r#"map(select(has("a")))"#,
        r#"[{"a":1},{"b":2}]"#,
        "has in select"
    );

    // =============================================================================
    // String Operations
    // =============================================================================

    jq_test!(split_basic, r#"split("/")"#, r#""a/b/c""#, "split basic");
    jq_test!(
        split_empty_delim,
        r#"split("")"#,
        r#""hello""#,
        "split empty delim"
    );
    jq_test!(
        split_empty_string,
        r#"split("/")"#,
        r#""""#,
        "split empty string"
    );
    jq_test!(join_basic, r#"join("-")"#, r#"["a","b","c"]"#, "join basic");
    jq_test!(join_empty, r#"join("-")"#, "[]", "join empty");
    jq_test!(
        join_with_null,
        r#"join("-")"#,
        r#"["a",null,"b"]"#,
        "join with null"
    );
    jq_test!(
        startswith_true,
        r#"startswith("hello")"#,
        r#""hello world""#,
        "startswith true"
    );
    jq_test!(
        startswith_false,
        r#"startswith("world")"#,
        r#""hello world""#,
        "startswith false"
    );
    jq_test!(
        endswith_true,
        r#"endswith(".json")"#,
        r#""hello.json""#,
        "endswith true"
    );
    jq_test!(
        endswith_false,
        r#"endswith(".json")"#,
        r#""hello.txt""#,
        "endswith false"
    );
    jq_test!(
        contains_string,
        r#"contains("wor")"#,
        r#""hello world""#,
        "contains string"
    );
    jq_test!(
        contains_string_false,
        r#"contains("xyz")"#,
        r#""hello""#,
        "contains string false"
    );
    jq_test!(contains_array, "contains([2])", "[1,2,3]", "contains array");
    jq_test!(
        contains_array_multi,
        "contains([2,3])",
        "[1,2,3]",
        "contains array multi"
    );
    jq_test!(
        contains_array_false,
        "contains([4])",
        "[1,2,3]",
        "contains array false"
    );
    jq_test!(
        contains_object,
        r#"contains({"a":1})"#,
        r#"{"a":1,"b":2}"#,
        "contains object"
    );
    jq_test!(
        contains_object_false,
        r#"contains({"c":3})"#,
        r#"{"a":1,"b":2}"#,
        "contains object false"
    );
    jq_test!(
        contains_in_select,
        r#"map(select(contains({"a":1})))"#,
        r#"[{"a":1},{"b":2}]"#,
        "contains in select"
    );
    jq_test!(
        endswith_in_select,
        r#"map(select(endswith(".json")))"#,
        r#"["foo.json","bar.txt","baz.json"]"#,
        "endswith in select"
    );

    // =============================================================================
    // Object Manipulation (to_entries, from_entries, with_entries, add objects)
    // =============================================================================

    jq_test!(
        to_entries_basic,
        "to_entries",
        r#"{"a":1,"b":2}"#,
        "to_entries basic"
    );
    jq_test!(to_entries_empty, "to_entries", "{}", "to_entries empty");
    jq_test!(
        from_entries_basic,
        "from_entries",
        r#"[{"key":"a","value":1},{"key":"b","value":2}]"#,
        "from_entries basic"
    );
    jq_test!(
        from_entries_name_key,
        "from_entries",
        r#"[{"name":"a","value":1}]"#,
        "from_entries with name key"
    );
    jq_test!(
        from_entries_empty,
        "from_entries",
        "[]",
        "from_entries empty"
    );
    jq_test!(
        to_from_entries_roundtrip,
        "to_entries | from_entries",
        r#"{"a":1,"b":2}"#,
        "to_entries | from_entries roundtrip"
    );
    jq_test!(
        with_entries_identity,
        "with_entries(.)",
        r#"{"a":1,"b":2}"#,
        "with_entries identity"
    );
    jq_test!(
        with_entries_select,
        "with_entries(select(.value > 1))",
        r#"{"a":1,"b":2,"c":3}"#,
        "with_entries select"
    );
    jq_test!(add_objects, "add", r#"[{"a":1},{"b":2}]"#, "add objects");
    jq_test!(
        add_objects_override,
        "add",
        r#"[{"a":1},{"a":2}]"#,
        "add objects override"
    );
    jq_test!(
        add_objects_empty,
        "add",
        r#"[{}]"#,
        "add single empty object"
    );
}
