use expect_test::{Expect, expect};
use hir::HirDisplay;

use crate::{
    context::CompletionContext,
    tests::{TEST_CONFIG, position},
};

fn check_expected_type_and_name(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
    let (db, pos) = position(ra_fixture);
    let config = TEST_CONFIG;
    let (completion_context, _analysis) =
        hir::attach_db(&db, || CompletionContext::new(&db, pos, &config, None).unwrap());

    let ty = completion_context
        .expected_type
        .map(|t| {
            hir::attach_db(&db, || {
                t.display_test(&db, completion_context.krate.to_display_target(&db)).to_string()
            })
        })
        .unwrap_or("?".to_owned());

    let name =
        completion_context.expected_name.map_or_else(|| "?".to_owned(), |name| name.to_string());

    expect.assert_eq(&format!("ty: {ty}, name: {name}"));
}

#[test]
fn expected_type_let_without_leading_char() {
    cov_mark::check!(expected_type_let_without_leading_char);
    check_expected_type_and_name(
        r#"
fn foo() {
    let x: u32 = $0;
}
"#,
        expect![[r#"ty: u32, name: x"#]],
    );
}

#[test]
fn expected_type_let_with_leading_char() {
    cov_mark::check!(expected_type_let_with_leading_char);
    check_expected_type_and_name(
        r#"
fn foo() {
    let x: u32 = c$0;
}
"#,
        expect![[r#"ty: u32, name: x"#]],
    );
}

#[test]
fn expected_type_let_pat() {
    check_expected_type_and_name(
        r#"
fn foo() {
    let x$0 = 0u32;
}
"#,
        expect![[r#"ty: u32, name: ?"#]],
    );
    check_expected_type_and_name(
        r#"
fn foo() {
    let $0 = 0u32;
}
"#,
        expect![[r#"ty: u32, name: ?"#]],
    );
}

#[test]
fn expected_type_fn_param() {
    cov_mark::check!(expected_type_fn_param);
    check_expected_type_and_name(
        r#"
fn foo() { bar($0); }
fn bar(x: u32) {}
"#,
        expect![[r#"ty: u32, name: x"#]],
    );
    check_expected_type_and_name(
        r#"
fn foo() { bar(c$0); }
fn bar(x: u32) {}
"#,
        expect![[r#"ty: u32, name: x"#]],
    );
    check_expected_type_and_name(
        r#"
fn foo() { bar(, $0); }
fn bar(x: u32, y: i32) {}
"#,
        expect![[r#"ty: i32, name: y"#]],
    );
    check_expected_type_and_name(
        r#"
fn foo() { bar(, c$0); }
fn bar(x: u32, y: i32) {}
"#,
        expect![[r#"ty: i32, name: y"#]],
    );
}

#[test]
fn expected_type_fn_param_ref() {
    cov_mark::check!(expected_type_fn_param_ref);
    check_expected_type_and_name(
        r#"
fn foo() { bar(&$0); }
fn bar(x: &u32) {}
"#,
        expect![[r#"ty: u32, name: x"#]],
    );
    check_expected_type_and_name(
        r#"
fn foo() { bar(&mut $0); }
fn bar(x: &mut u32) {}
"#,
        expect![[r#"ty: u32, name: x"#]],
    );
    check_expected_type_and_name(
        r#"
fn foo() { bar(& c$0); }
fn bar(x: &u32) {}
        "#,
        expect![[r#"ty: u32, name: x"#]],
    );
    check_expected_type_and_name(
        r#"
fn foo() { bar(&mut c$0); }
fn bar(x: &mut u32) {}
"#,
        expect![[r#"ty: u32, name: x"#]],
    );
    check_expected_type_and_name(
        r#"
fn foo() { bar(&c$0); }
fn bar(x: &u32) {}
        "#,
        expect![[r#"ty: u32, name: x"#]],
    );
}

#[test]
fn expected_type_fn_param_deref() {
    cov_mark::check!(expected_type_fn_param_deref);
    check_expected_type_and_name(
        r#"
fn foo() { bar(*$0); }
fn bar(x: &u32) {}
"#,
        expect!["ty: &'_ &'_ u32, name: x"],
    );
}

#[test]
fn expected_type_struct_field_without_leading_char() {
    cov_mark::check!(expected_type_struct_field_without_leading_char);
    check_expected_type_and_name(
        r#"
struct Foo { a: u32 }
fn foo() {
    Foo { a: $0 };
}
"#,
        expect![[r#"ty: u32, name: a"#]],
    )
}

#[test]
fn expected_type_struct_field_followed_by_comma() {
    cov_mark::check!(expected_type_struct_field_followed_by_comma);
    check_expected_type_and_name(
        r#"
struct Foo { a: u32 }
fn foo() {
    Foo { a: $0, };
}
"#,
        expect![[r#"ty: u32, name: a"#]],
    )
}

#[test]
fn expected_type_generic_struct_field() {
    check_expected_type_and_name(
        r#"
struct Foo<T> { a: T }
fn foo() -> Foo<u32> {
    Foo { a: $0 }
}
"#,
        expect![[r#"ty: u32, name: a"#]],
    )
}

#[test]
fn expected_type_struct_field_with_leading_char() {
    cov_mark::check!(expected_type_struct_field_with_leading_char);
    check_expected_type_and_name(
        r#"
struct Foo { a: u32 }
fn foo() {
    Foo { a: c$0 };
}
"#,
        expect![[r#"ty: u32, name: a"#]],
    );
}

#[test]
fn expected_type_match_arm_without_leading_char() {
    cov_mark::check!(expected_type_match_arm_without_leading_char);
    check_expected_type_and_name(
        r#"
enum E { X }
fn foo() {
   match E::X { $0 }
}
"#,
        expect![[r#"ty: E, name: ?"#]],
    );
}

#[test]
fn expected_type_match_arm_with_leading_char() {
    cov_mark::check!(expected_type_match_arm_with_leading_char);
    check_expected_type_and_name(
        r#"
enum E { X }
fn foo() {
   match E::X { c$0 }
}
"#,
        expect![[r#"ty: E, name: ?"#]],
    );
}

#[test]
fn expected_type_match_arm_body_without_leading_char() {
    cov_mark::check!(expected_type_match_arm_body_without_leading_char);
    check_expected_type_and_name(
        r#"
struct Foo;
enum E { X }
fn foo() -> Foo {
   match E::X { E::X => $0 }
}
"#,
        expect![[r#"ty: Foo, name: ?"#]],
    );
}

#[test]
fn expected_type_match_arm_block_body_without_leading_char() {
    cov_mark::check!(expected_type_match_arm_body_without_leading_char);
    cov_mark::check!(expected_type_match_arm_body_with_leading_char);
    check_expected_type_and_name(
        r#"
struct Foo;
enum E { X }
fn foo() -> Foo {
    match E::X { Foo::X => { $0 } }
}
"#,
        expect![[r#"ty: Foo, name: ?"#]],
    );
}

#[test]
fn expected_type_match_body_arm_with_leading_char() {
    cov_mark::check!(expected_type_match_arm_body_with_leading_char);
    check_expected_type_and_name(
        r#"
struct Foo;
enum E { X }
fn foo() -> Foo {
   match E::X { E::X => c$0 }
}
"#,
        expect![[r#"ty: Foo, name: ?"#]],
    );
}

#[test]
fn expected_type_tuple_struct_pat() {
    check_expected_type_and_name(
        r#"
//- minicore: option
struct Foo(Option<i32>);
fn foo(x: Foo) -> Foo {
   match x { Foo($0) => () }
}
"#,
        expect![[r#"ty: Option<i32>, name: ?"#]],
    );

    check_expected_type_and_name(
        r#"
struct Foo(i32, u32, f32);
fn foo(x: Foo) -> Foo {
   match x { Foo($0) => () }
}
"#,
        expect![[r#"ty: i32, name: ?"#]],
    );

    check_expected_type_and_name(
        r#"
struct Foo(i32, u32, f32);
fn foo(x: Foo) -> Foo {
   match x { Foo(num,$0) => () }
}
"#,
        expect![[r#"ty: u32, name: ?"#]],
    );

    check_expected_type_and_name(
        r#"
struct Foo(i32, u32, f32);
fn foo(x: Foo) -> Foo {
   match x { Foo(num,$0,float) => () }
}
"#,
        expect![[r#"ty: u32, name: ?"#]],
    );
}

#[test]
fn expected_type_if_let_without_leading_char() {
    cov_mark::check!(expected_type_if_let_without_leading_char);
    check_expected_type_and_name(
        r#"
enum Foo { Bar, Baz, Quux }

fn foo() {
    let f = Foo::Quux;
    if let $0 = f { }
}
"#,
        expect![[r#"ty: Foo, name: ?"#]],
    )
}

#[test]
fn expected_type_if_let_with_leading_char() {
    cov_mark::check!(expected_type_if_let_with_leading_char);
    check_expected_type_and_name(
        r#"
enum Foo { Bar, Baz, Quux }

fn foo() {
    let f = Foo::Quux;
    if let c$0 = f { }
}
"#,
        expect![[r#"ty: Foo, name: ?"#]],
    )
}

#[test]
fn expected_type_if_let_chain_bool() {
    check_expected_type_and_name(
        r#"
fn foo() {
    let f = Foo::Quux;
    if let c = f && $0 { }
}
"#,
        expect![[r#"ty: bool, name: ?"#]],
    );
}

#[test]
fn expected_type_if_condition() {
    check_expected_type_and_name(
        r#"
fn foo() {
    if a$0 { }
}
"#,
        expect![[r#"ty: bool, name: ?"#]],
    );
}

#[test]
fn expected_type_if_body() {
    check_expected_type_and_name(
        r#"
enum Foo { Bar, Baz, Quux }

fn foo() {
    let _: Foo = if true {
        $0
    };
}
"#,
        expect![[r#"ty: Foo, name: ?"#]],
    );

    check_expected_type_and_name(
        r#"
enum Foo { Bar, Baz, Quux }

fn foo() {
    let _: Foo = if true {
        Foo::Bar
    } else {
        $0
    };
}
"#,
        expect![[r#"ty: Foo, name: ?"#]],
    );
}

#[test]
fn expected_type_fn_ret_without_leading_char() {
    cov_mark::check!(expected_type_fn_ret_without_leading_char);
    check_expected_type_and_name(
        r#"
fn foo() -> u32 {
    $0
}
"#,
        expect![[r#"ty: u32, name: ?"#]],
    )
}

#[test]
fn expected_type_fn_ret_with_leading_char() {
    cov_mark::check!(expected_type_fn_ret_with_leading_char);
    check_expected_type_and_name(
        r#"
fn foo() -> u32 {
    c$0
}
"#,
        expect![[r#"ty: u32, name: ?"#]],
    )
}

#[test]
fn expected_type_fn_ret_fn_ref_fully_typed() {
    check_expected_type_and_name(
        r#"
fn foo() -> u32 {
    foo$0
}
"#,
        expect![[r#"ty: u32, name: ?"#]],
    )
}

#[test]
fn expected_type_closure_param_return() {
    check_expected_type_and_name(
        r#"
//- minicore: fn
fn foo() {
    bar(|| a$0);
}

fn bar(f: impl FnOnce() -> u32) {}
"#,
        expect![[r#"ty: u32, name: ?"#]],
    );

    check_expected_type_and_name(
        r#"
//- minicore: fn
fn foo() {
    bar(|| $0);
}

fn bar(f: impl FnOnce() -> u32) {}
"#,
        expect![[r#"ty: u32, name: ?"#]],
    );
}

#[test]
fn expected_type_generic_function() {
    check_expected_type_and_name(
        r#"
fn foo() {
    bar::<u32>($0);
}

fn bar<T>(t: T) {}
"#,
        expect![[r#"ty: u32, name: t"#]],
    );
}

#[test]
fn expected_type_generic_method() {
    check_expected_type_and_name(
        r#"
fn foo() {
    S(1u32).bar($0);
}

struct S<T>(T);
impl<T> S<T> {
    fn bar(self, t: T) {}
}
"#,
        expect![[r#"ty: u32, name: t"#]],
    );
}

#[test]
fn expected_type_functional_update() {
    cov_mark::check!(expected_type_struct_func_update);
    check_expected_type_and_name(
        r#"
struct Foo { field: u32 }
fn foo() {
    Foo {
        ..$0
    }
}
"#,
        expect![[r#"ty: Foo, name: ?"#]],
    );
    check_expected_type_and_name(
        r#"
struct Foo { field: u32 }
fn foo() {
    Foo {
        ..self::$0
    }
}
"#,
        expect!["ty: ?, name: ?"],
    );
}

#[test]
fn expected_type_param_pat() {
    check_expected_type_and_name(
        r#"
struct Foo { field: u32 }
fn foo(a$0: Foo) {}
"#,
        expect![[r#"ty: Foo, name: ?"#]],
    );
    check_expected_type_and_name(
        r#"
struct Foo { field: u32 }
fn foo($0: Foo) {}
"#,
        // FIXME make this work, currently fails due to pattern recovery eating the `:`
        expect![[r#"ty: ?, name: ?"#]],
    );
}

#[test]
fn expected_type_ref_prefix_on_field() {
    check_expected_type_and_name(
        r#"
fn foo(_: &mut i32) {}
struct S {
    field: i32,
}

fn main() {
    let s = S {
        field: 100,
    };
    foo(&mut s.f$0);
}
"#,
        expect!["ty: i32, name: ?"],
    );
}

#[test]
fn expected_type_ref_return_pos() {
    check_expected_type_and_name(
        r#"
fn f(thing: u32) -> &u32 {
    &thin$0
}
"#,
        expect!["ty: u32, name: ?"],
    );
}

#[test]
fn expected_type_assign() {
    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() {
    let x: &mut State = &mut State::Stop;
    x = $0;
}
"#,
        expect![[r#"ty: &'_ mut State, name: ?"#]],
    );
}

#[test]
fn expected_type_deref_assign() {
    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() {
    let x: &mut State = &mut State::Stop;
    match x {
        State::Stop => {
            *x = $0;
        },
    }
}
"#,
        expect![[r#"ty: State, name: ?"#]],
    );
}

#[test]
fn expected_type_deref_assign_at_block_end() {
    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() {
    let x: &mut State = &mut State::Stop;
    match x {
        State::Stop => {
            *x = $0
        },
    }
}
"#,
        expect![[r#"ty: State, name: ?"#]],
    );
}

#[test]
fn expected_type_return_expr() {
    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() -> State {
    let _: i32 = if true {
        8
    } else {
        return $0;
    };
}
"#,
        expect![[r#"ty: State, name: ?"#]],
    );
}

#[test]
fn expected_type_return_expr_in_closure() {
    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() {
    let _f: fn() -> State = || {
        let _: i32 = if true {
            8
        } else {
            return $0;
        };
    };
}
"#,
        expect![[r#"ty: State, name: ?"#]],
    );
}

#[test]
fn expected_type_break_expr_in_loop() {
    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() {
    let _x: State = loop {
        {
            break State::Stop;
            break $0;
        }
    };
}
"#,
        expect![[r#"ty: State, name: ?"#]],
    );

    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() {
    let _x: State = 'a: loop {
        {
            break State::Stop;
            break $0;
        }
    };
}
"#,
        expect![[r#"ty: State, name: ?"#]],
    );

    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() {
    let _x: State = 'a: loop {
        while true {
            break $0;
        }
    };
}
"#,
        expect![[r#"ty: (), name: ?"#]],
    );
}

#[test]
fn expected_type_break_expr_in_labeled_loop() {
    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() {
    let _x: State = 'a: loop {
        let _y: i32 = loop {
            {
                break 'a State::Stop;
                break 'a $0;
            }
        };
    };
}
"#,
        expect![[r#"ty: State, name: ?"#]],
    );

    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() {
    let _x: State = 'a: loop {
        let _y: i32 = loop {
            while true {
                break 'a State::Stop;
                break 'a $0;
            }
        };
    };
}
"#,
        expect![[r#"ty: State, name: ?"#]],
    );

    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() {
    'a: while true {
        let _x: State = loop {
            break State::Stop;
            break 'a $0;
        };
    }
}
"#,
        expect![[r#"ty: (), name: ?"#]],
    );
}

#[test]
fn expected_type_break_expr_in_labeled_block() {
    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() {
    let _x: State = 'a: {
        let _y: i32 = 'b: {
            {
                break 'a State::Stop;
                break 'a $0;
            };
        };
    };
}
"#,
        expect![[r#"ty: State, name: ?"#]],
    );
}

#[test]
fn expected_type_logic_op() {
    check_expected_type_and_name(
        r#"
enum State { Stop }
fn foo() {
    true && $0;
}
"#,
        expect![[r#"ty: bool, name: ?"#]],
    );
}
