use crate::repl::tests::{fail_test, run_test, run_test_contains, run_test_with_env, TestResult};
use nu_test_support::{nu, nu_repl_code};
use std::collections::HashMap;

#[test]
fn env_shorthand() -> TestResult {
    run_test("FOO=BAR if false { 3 } else { 4 }", "4")
}

#[test]
fn subcommand() -> TestResult {
    run_test("def foo [] {}; def \"foo bar\" [] {3}; foo bar", "3")
}

#[test]
fn alias_1() -> TestResult {
    run_test("def foo [$x] { $x + 10 }; alias f = foo; f 100", "110")
}

#[test]
fn ints_with_underscores() -> TestResult {
    run_test("1_0000_0000_0000 + 10", "1000000000010")
}

#[test]
fn floats_with_underscores() -> TestResult {
    run_test("3.1415_9265_3589_793 * 2", "6.283185307179586")
}

#[test]
fn bin_ints_with_underscores() -> TestResult {
    run_test("0b_10100_11101_10010", "21426")
}

#[test]
fn oct_ints_with_underscores() -> TestResult {
    run_test("0o2443_6442_7652_0044", "90422533333028")
}

#[test]
fn hex_ints_with_underscores() -> TestResult {
    run_test("0x68__9d__6a", "6856042")
}

#[test]
fn alias_2() -> TestResult {
    run_test(
        "def foo [$x $y] { $x + $y + 10 }; alias f = foo 33; f 100",
        "143",
    )
}

#[test]
fn alias_2_multi_word() -> TestResult {
    run_test(
        r#"def "foo bar" [$x $y] { $x + $y + 10 }; alias f = foo bar 33; f 100"#,
        "143",
    )
}

#[ignore = "TODO: Allow alias to alias existing command with the same name"]
#[test]
fn alias_recursion() -> TestResult {
    run_test_contains(r#"alias ls = ls -a; ls"#, " ")
}

#[test]
fn block_param1() -> TestResult {
    run_test("[3] | each { |it| $it + 10 } | get 0", "13")
}

#[test]
fn block_param2() -> TestResult {
    run_test("[3] | each { |y| $y + 10 } | get 0", "13")
}

#[test]
fn block_param3_list_iteration() -> TestResult {
    run_test("[1,2,3] | each { |it| $it + 10 } | get 1", "12")
}

#[test]
fn block_param4_list_iteration() -> TestResult {
    run_test("[1,2,3] | each { |y| $y + 10 } | get 2", "13")
}

#[test]
fn range_iteration1() -> TestResult {
    run_test("1..4 | each { |y| $y + 10 } | get 0", "11")
}

#[test]
fn range_iteration2() -> TestResult {
    run_test("4..1 | each { |y| $y + 100 } | get 3", "101")
}

#[test]
fn simple_value_iteration() -> TestResult {
    run_test("4 | each { |it| $it + 10 }", "14")
}

#[test]
fn comment_multiline() -> TestResult {
    run_test(
        r#"def foo [] {
        let x = 1 + 2 # comment
        let y = 3 + 4 # another comment
        $x + $y
    }; foo"#,
        "10",
    )
}

#[test]
fn comment_skipping_1() -> TestResult {
    run_test(
        r#"let x = {
        y: 20
        # foo
    }; $x.y"#,
        "20",
    )
}

#[test]
fn comment_skipping_2() -> TestResult {
    run_test(
        r#"let x = {
        y: 20
        # foo
        z: 40
    }; $x.z"#,
        "40",
    )
}

#[test]
fn comment_skipping_in_pipeline_1() -> TestResult {
    run_test(
        r#"[1,2,3] | #comment
        each { |$it| $it + 2 } | # foo
        math sum #bar"#,
        "12",
    )
}

#[test]
fn comment_skipping_in_pipeline_2() -> TestResult {
    run_test(
        r#"[1,2,3] #comment
        | #comment2
        each { |$it| $it + 2 } #foo
        | # bar
        math sum #baz"#,
        "12",
    )
}

#[test]
fn comment_skipping_in_pipeline_3() -> TestResult {
    run_test(
        r#"[1,2,3] | #comment
        #comment2
        each { |$it| $it + 2 } #foo
        | # bar
        #baz
        math sum #foobar"#,
        "12",
    )
}

#[test]
fn bad_var_name() -> TestResult {
    fail_test(r#"let $"foo bar" = 4"#, "can't contain")
}

#[test]
fn bad_var_name2() -> TestResult {
    fail_test(r#"let $foo-bar = 4"#, "valid variable")
}

#[test]
fn assignment_with_no_var() -> TestResult {
    let cases = [
        "let = if $",
        "mut = if $",
        "const = if $",
        "let = 'foo' | $in; $x | describe",
        "mut = 'foo' | $in; $x | describe",
    ];

    let expected = "valid variable";

    for case in cases {
        fail_test(case, expected)?;
    }

    Ok(())
}

#[test]
fn long_flag() -> TestResult {
    run_test(
        r#"([a, b, c] | enumerate | each --keep-empty { |e| if $e.index != 1 { 100 }}).1 | to nuon"#,
        "null",
    )
}

#[test]
fn for_in_missing_var_name() -> TestResult {
    fail_test("for in", "missing")
}

#[test]
fn multiline_pipe_in_block() -> TestResult {
    run_test(
        r#"do {
            echo hello |
            str length
        }"#,
        "5",
    )
}

#[test]
fn bad_short_flag() -> TestResult {
    fail_test(r#"def foo3 [-l?:int] { $l }"#, "short flag")
}

#[test]
fn quotes_with_equals() -> TestResult {
    run_test(
        r#"let query_prefix = "https://api.github.com/search/issues?q=repo:nushell/"; $query_prefix"#,
        "https://api.github.com/search/issues?q=repo:nushell/",
    )
}

#[test]
fn string_interp_with_equals() -> TestResult {
    run_test(
        r#"let query_prefix = $"https://api.github.com/search/issues?q=repo:nushell/"; $query_prefix"#,
        "https://api.github.com/search/issues?q=repo:nushell/",
    )
}

#[test]
fn recursive_parse() -> TestResult {
    run_test(r#"def c [] { c }; echo done"#, "done")
}

#[test]
fn commands_have_usage() -> TestResult {
    run_test_contains(
        r#"
    # This is a test
    #
    # To see if I have cool usage
    def foo [] {}
    help foo"#,
        "cool usage",
    )
}

#[test]
fn commands_from_crlf_source_have_short_usage() -> TestResult {
    run_test_contains(
        "# This is a test\r\n#\r\n# To see if I have cool usage\r\ndef foo [] {}\r\nscope commands | where name == foo | get usage.0",
        "This is a test",
    )
}

#[test]
fn commands_from_crlf_source_have_extra_usage() -> TestResult {
    run_test_contains(
        "# This is a test\r\n#\r\n# To see if I have cool usage\r\ndef foo [] {}\r\nscope commands | where name == foo | get extra_usage.0",
        "To see if I have cool usage",
    )
}

#[test]
fn equals_separates_long_flag() -> TestResult {
    run_test(
        r#"'nushell' | fill --alignment right --width=10 --character='-'"#,
        "---nushell",
    )
}

#[test]
fn assign_expressions() -> TestResult {
    let env = HashMap::from([("VENV_OLD_PATH", "Foobar"), ("Path", "Quux")]);
    run_test_with_env(
        r#"$env.Path = (if ($env | columns | "VENV_OLD_PATH" in $in) { $env.VENV_OLD_PATH } else { $env.Path }); echo $env.Path"#,
        "Foobar",
        &env,
    )
}

#[test]
fn string_interpolation_paren_test() -> TestResult {
    run_test(r#"$"('(')(')')""#, "()")
}

#[test]
fn string_interpolation_paren_test2() -> TestResult {
    run_test(r#"$"('(')test(')')""#, "(test)")
}

#[test]
fn string_interpolation_paren_test3() -> TestResult {
    run_test(r#"$"('(')("test")test(')')""#, "(testtest)")
}

#[test]
fn string_interpolation_escaping() -> TestResult {
    run_test(r#"$"hello\nworld" | lines | length"#, "2")
}

#[test]
fn capture_multiple_commands() -> TestResult {
    run_test(
        r#"
let CONST_A = 'Hello'

def 'say-hi' [] {
    echo (call-me)
}

def 'call-me' [] {
    echo $CONST_A
}

[(say-hi) (call-me)] | str join

    "#,
        "HelloHello",
    )
}

#[test]
fn capture_multiple_commands2() -> TestResult {
    run_test(
        r#"
let CONST_A = 'Hello'

def 'call-me' [] {
    echo $CONST_A
}

def 'say-hi' [] {
    echo (call-me)
}

[(say-hi) (call-me)] | str join

    "#,
        "HelloHello",
    )
}

#[test]
fn capture_multiple_commands3() -> TestResult {
    run_test(
        r#"
let CONST_A = 'Hello'

def 'say-hi' [] {
    echo (call-me)
}

def 'call-me' [] {
    echo $CONST_A
}

[(call-me) (say-hi)] | str join

    "#,
        "HelloHello",
    )
}

#[test]
fn capture_multiple_commands4() -> TestResult {
    run_test(
        r#"
let CONST_A = 'Hello'

def 'call-me' [] {
    echo $CONST_A
}

def 'say-hi' [] {
    echo (call-me)
}

[(call-me) (say-hi)] | str join

    "#,
        "HelloHello",
    )
}

#[test]
fn capture_row_condition() -> TestResult {
    run_test(
        r#"let name = "foo"; [foo] | where $'($name)' =~ $it | str join"#,
        "foo",
    )
}

#[test]
fn starts_with_operator_succeeds() -> TestResult {
    run_test(
        r#"[Moe Larry Curly] | where $it starts-with L | str join"#,
        "Larry",
    )
}

#[test]
fn ends_with_operator_succeeds() -> TestResult {
    run_test(
        r#"[Moe Larry Curly] | where $it ends-with ly | str join"#,
        "Curly",
    )
}

#[test]
fn proper_missing_param() -> TestResult {
    fail_test(r#"def foo [x y z w] { }; foo a b c"#, "missing w")
}

#[test]
fn block_arity_check1() -> TestResult {
    fail_test(r#"ls | each { |x, y| 1}"#, "expected 1 closure parameter")
}

// deprecating former support for escapes like `/uNNNN`, dropping test.
#[test]
fn string_escape_unicode_extended() -> TestResult {
    run_test(r#""\u{015B}\u{1f10b}""#, "ś🄋")
}

#[test]
fn string_escape_interpolation() -> TestResult {
    run_test(r#"$"\u{015B}(char hamburger)abc""#, "ś≡abc")
}

#[test]
fn string_escape_interpolation2() -> TestResult {
    run_test(r#"$"2 + 2 is \(2 + 2)""#, "2 + 2 is (2 + 2)")
}

#[test]
fn proper_rest_types() -> TestResult {
    run_test(
        r#"def foo [--verbose(-v), # my test flag
                   ...rest: int # my rest comment
                ] { if $verbose { print "verbose!" } else { print "not verbose!" } }; foo"#,
        "not verbose!",
    )
}

#[test]
fn single_value_row_condition() -> TestResult {
    run_test(
        r#"[[a, b]; [true, false], [true, true]] | where a | length"#,
        "2",
    )
}

#[test]
fn performance_nested_lists() -> TestResult {
    // Parser used to be exponential on deeply nested lists
    // TODO: Add a timeout
    fail_test(r#"[[[[[[[[[[[[[[[[[[[[[[[[[[[["#, "Unexpected end of code")
}

#[test]
fn unary_not_1() -> TestResult {
    run_test(r#"not false"#, "true")
}

#[test]
fn unary_not_2() -> TestResult {
    run_test(r#"not (false)"#, "true")
}

#[test]
fn unary_not_3() -> TestResult {
    run_test(r#"(not false)"#, "true")
}

#[test]
fn unary_not_4() -> TestResult {
    run_test(r#"if not false { "hello" } else { "world" }"#, "hello")
}

#[test]
fn unary_not_5() -> TestResult {
    run_test(
        r#"if not not not not false { "hello" } else { "world" }"#,
        "world",
    )
}

#[test]
fn unary_not_6() -> TestResult {
    run_test(
        r#"[[name, present]; [abc, true], [def, false]] | where not present | get name.0"#,
        "def",
    )
}

#[test]
fn comment_in_multiple_pipelines() -> TestResult {
    run_test(
        r#"[[name, present]; [abc, true], [def, false]]
        # | where not present
        | get name.0"#,
        "abc",
    )
}

#[test]
fn date_literal() -> TestResult {
    run_test(r#"2022-09-10 | date to-record | get day"#, "10")
}

#[test]
fn and_and_or() -> TestResult {
    run_test(r#"true and false or true"#, "true")
}

#[test]
fn and_and_xor() -> TestResult {
    // Assumes the precedence NOT > AND > XOR > OR
    run_test(r#"true and true xor true and false"#, "true")
}

#[test]
fn or_and_xor() -> TestResult {
    // Assumes the precedence NOT > AND > XOR > OR
    run_test(r#"true or false xor true or false"#, "true")
}

#[test]
fn unbalanced_delimiter() -> TestResult {
    fail_test(r#"{a:{b:5}}}"#, "unbalanced { and }")
}

#[test]
fn unbalanced_delimiter2() -> TestResult {
    fail_test(r#"{}#.}"#, "unbalanced { and }")
}

#[test]
fn unbalanced_delimiter3() -> TestResult {
    fail_test(r#"{"#, "Unexpected end of code")
}

#[test]
fn unbalanced_delimiter4() -> TestResult {
    fail_test(r#"}"#, "unbalanced { and }")
}

#[test]
fn unbalanced_parens1() -> TestResult {
    fail_test(r#")"#, "unbalanced ( and )")
}

#[test]
fn unbalanced_parens2() -> TestResult {
    fail_test(r#"("("))"#, "unbalanced ( and )")
}

#[test]
fn plugin_use_with_string_literal() -> TestResult {
    fail_test(
        r#"plugin use 'nu-plugin-math'"#,
        "Plugin registry file not set",
    )
}

#[test]
fn plugin_use_with_string_constant() -> TestResult {
    let input = "\
const file = 'nu-plugin-math'
plugin use $file
";
    // should not fail with `not a constant`
    fail_test(input, "Plugin registry file not set")
}

#[test]
fn plugin_use_with_string_variable() -> TestResult {
    let input = "\
let file = 'nu-plugin-math'
plugin use $file
";
    fail_test(input, "Value is not a parse-time constant")
}

#[test]
fn plugin_use_with_non_string_constant() -> TestResult {
    let input = "\
const file = 6
plugin use $file
";
    fail_test(input, "expected string, found int")
}

#[test]
fn extern_errors_with_no_space_between_params_and_name_1() -> TestResult {
    fail_test("extern cmd[]", "expected space")
}

#[test]
fn extern_errors_with_no_space_between_params_and_name_2() -> TestResult {
    fail_test("extern cmd(--flag)", "expected space")
}

#[test]
fn duration_with_underscores_1() -> TestResult {
    run_test("420_min", "7hr")
}

#[test]
fn duration_with_underscores_2() -> TestResult {
    run_test("1_000_000sec", "1wk 4day 13hr 46min 40sec")
}

#[test]
fn duration_with_underscores_3() -> TestResult {
    fail_test("1_000_d_ay", "Command `1_000_d_ay` not found")
}

#[test]
fn duration_with_faulty_number() -> TestResult {
    fail_test("sleep 4-ms", "duration value must be a number")
}

#[test]
fn filesize_with_underscores_1() -> TestResult {
    run_test("420_mb", "400.5 MiB")
}

#[test]
fn filesize_with_underscores_2() -> TestResult {
    run_test("1_000_000B", "976.6 KiB")
}

#[test]
fn filesize_with_underscores_3() -> TestResult {
    fail_test("42m_b", "Command `42m_b` not found")
}

#[test]
fn filesize_is_not_hex() -> TestResult {
    run_test("0x42b", "1067")
}

#[test]
fn let_variable_type_mismatch() -> TestResult {
    fail_test(r#"let x: int = "foo""#, "expected int, found string")
}

#[test]
fn let_variable_disallows_completer() -> TestResult {
    fail_test(
        r#"let x: int@completer = 42"#,
        "Unexpected custom completer",
    )
}

#[test]
fn def_with_input_output() -> TestResult {
    run_test(r#"def foo []: nothing -> int { 3 }; foo"#, "3")
}

#[test]
fn def_with_input_output_with_line_breaks() -> TestResult {
    run_test(
        r#"def foo []: [
          nothing -> int
        ] { 3 }; foo"#,
        "3",
    )
}

#[test]
fn def_with_multi_input_output_with_line_breaks() -> TestResult {
    run_test(
        r#"def foo []: [
          nothing -> int
          string -> int
        ] { 3 }; foo"#,
        "3",
    )
}

#[test]
fn def_with_multi_input_output_without_commas() -> TestResult {
    run_test(
        r#"def foo []: [nothing -> int string -> int] { 3 }; foo"#,
        "3",
    )
}

#[test]
fn def_with_multi_input_output_called_with_first_sig() -> TestResult {
    run_test(
        r#"def foo []: [int -> int, string -> int] { 3 }; 10 | foo"#,
        "3",
    )
}

#[test]
fn def_with_multi_input_output_called_with_second_sig() -> TestResult {
    run_test(
        r#"def foo []: [int -> int, string -> int] { 3 }; "bob" | foo"#,
        "3",
    )
}

#[test]
fn def_with_input_output_mismatch_1() -> TestResult {
    fail_test(
        r#"def foo []: [int -> int, string -> int] { 3 }; foo"#,
        "command doesn't support",
    )
}

#[test]
fn def_with_input_output_mismatch_2() -> TestResult {
    fail_test(
        r#"def foo []: [int -> int, string -> int] { 3 }; {x: 2} | foo"#,
        "command doesn't support",
    )
}

#[test]
fn def_with_input_output_broken_1() -> TestResult {
    fail_test(r#"def foo []: int { 3 }"#, "expected arrow")
}

#[test]
fn def_with_input_output_broken_2() -> TestResult {
    fail_test(r#"def foo []: int -> { 3 }"#, "expected type")
}

#[test]
fn def_with_input_output_broken_3() -> TestResult {
    fail_test(
        r#"def foo []: int -> int@completer {}"#,
        "Unexpected custom completer",
    )
}

#[test]
fn def_with_input_output_broken_4() -> TestResult {
    fail_test(
        r#"def foo []: int -> list<int@completer> {}"#,
        "Unexpected custom completer",
    )
}

#[test]
fn def_with_in_var_let_1() -> TestResult {
    run_test(
        r#"def foo []: [int -> int, string -> int] { let x = $in; if ($x | describe) == "int" { 3 } else { 4 } }; "100" | foo"#,
        "4",
    )
}

#[test]
fn def_with_in_var_let_2() -> TestResult {
    run_test(
        r#"def foo []: [int -> int, string -> int] { let x = $in; if ($x | describe) == "int" { 3 } else { 4 } }; 100 | foo"#,
        "3",
    )
}

#[test]
fn def_with_in_var_mut_1() -> TestResult {
    run_test(
        r#"def foo []: [int -> int, string -> int] { mut x = $in; if ($x | describe) == "int" { 3 } else { 4 } }; "100" | foo"#,
        "4",
    )
}

#[test]
fn def_with_in_var_mut_2() -> TestResult {
    run_test(
        r#"def foo []: [int -> int, string -> int] { mut x = $in; if ($x | describe) == "int" { 3 } else { 4 } }; 100 | foo"#,
        "3",
    )
}

#[test]
fn properly_nest_captures() -> TestResult {
    run_test(r#"do { let b = 3; def c [] { $b }; c }"#, "3")
}

#[test]
fn properly_nest_captures_call_first() -> TestResult {
    run_test(r#"do { let b = 3; c; def c [] { $b }; c }"#, "3")
}

#[test]
fn properly_typecheck_rest_param() -> TestResult {
    run_test(
        r#"def foo [...rest: string] { $rest | length }; foo "a" "b" "c""#,
        "3",
    )
}

#[test]
fn implied_collect_has_compatible_type() -> TestResult {
    run_test(r#"let idx = 3 | $in; $idx < 1"#, "false")
}

#[test]
fn record_expected_colon() -> TestResult {
    fail_test(r#"{ a: 2 b }"#, "expected ':'")?;
    fail_test(r#"{ a: 2 b 3 }"#, "expected ':'")
}

#[test]
fn record_missing_value() -> TestResult {
    fail_test(r#"{ a: 2 b: }"#, "expected value for record field")
}

#[test]
fn def_requires_body_closure() -> TestResult {
    fail_test("def a [] (echo 4)", "expected definition body closure")
}

#[test]
fn not_panic_with_recursive_call() {
    let result = nu!(nu_repl_code(&[
        "def px [] { if true { 3 } else { px } }",
        "let x = 1",
        "$x | px",
    ]));
    assert_eq!(result.out, "3");

    let result = nu!(nu_repl_code(&[
        "def px [n=0] { let l = $in; if $n == 0 { return false } else { $l | px ($n - 1) } }",
        "let x = 1",
        "$x | px"
    ]));
    assert_eq!(result.out, "false");

    let result = nu!(nu_repl_code(&[
        "def px [n=0] { let l = $in; if $n == 0 { return false } else { $l | px ($n - 1) } }",
        "let x = 1",
        "def foo [] { $x }",
        "foo | px"
    ]));
    assert_eq!(result.out, "false");

    let result = nu!(nu_repl_code(&[
        "def px [n=0] { let l = $in; if $n == 0 { return false } else { $l | px ($n - 1) } }",
        "let x = 1",
        "do {|| $x } | px"
    ]));
    assert_eq!(result.out, "false");

    let result = nu!(
        cwd: "tests/parsing/samples",
        "nu recursive_func_with_alias.nu"
    );
    assert!(result.status.success());
}