use gqlrs::*;

#[tokio::test]
pub async fn test_input_object_default_value() {
    #[derive(InputObject)]
    struct MyInput {
        #[graphql(default = 999)]
        a: i32,

        #[graphql(default_with = "vec![1, 2, 3]")]
        b: Vec<i32>,

        #[graphql(default = "abc")]
        c: String,

        #[graphql(default = 999)]
        d: i32,

        #[graphql(default = 999)]
        e: i32,
    }

    struct MyOutput {
        a: i32,
        b: Vec<i32>,
        c: String,
        d: Option<i32>,
        e: Option<i32>,
    }

    #[Object]
    impl MyOutput {
        async fn a(&self) -> i32 {
            self.a
        }

        async fn b(&self) -> &Vec<i32> {
            &self.b
        }

        async fn c(&self) -> &String {
            &self.c
        }

        async fn d(&self) -> &Option<i32> {
            &self.d
        }

        async fn e(&self) -> &Option<i32> {
            &self.e
        }
    }

    struct Root;

    #[Object]
    impl Root {
        async fn a(&self, input: MyInput) -> MyOutput {
            MyOutput {
                a: input.a,
                b: input.b,
                c: input.c,
                d: Some(input.d),
                e: Some(input.e),
            }
        }
    }

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
            a(input:{e:777}) {
                a b c d e
            }
        }"#
    .to_owned();
    assert_eq!(
        schema.execute(&query).await.data,
        value!({
            "a": {
                "a": 999,
                "b": [1, 2, 3],
                "c": "abc",
                "d": 999,
                "e": 777,
            }
        })
    );
}

#[tokio::test]
pub async fn test_inputobject_derive_and_item_attributes() {
    use serde::Deserialize;

    #[derive(Deserialize, PartialEq, Debug, InputObject)]
    struct MyInputObject {
        #[serde(alias = "other")]
        real: i32,
    }

    assert_eq!(
        serde_json::from_str::<MyInputObject>(r#"{ "other" : 100 }"#).unwrap(),
        MyInputObject { real: 100 }
    );
}

#[tokio::test]
pub async fn test_inputobject_flatten_recursive() {
    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct A {
        a: i32,
    }

    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct B {
        #[graphql(default = 70)]
        b: i32,
        #[graphql(flatten)]
        a_obj: A,
    }

    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct MyInputObject {
        #[graphql(flatten)]
        b_obj: B,
        c: i32,
    }

    assert_eq!(
        MyInputObject::parse(Some(value!({
           "a": 10,
           "b": 20,
           "c": 30,
        })))
        .unwrap(),
        MyInputObject {
            b_obj: B {
                b: 20,
                a_obj: A { a: 10 }
            },
            c: 30,
        }
    );

    assert_eq!(
        MyInputObject {
            b_obj: B {
                b: 20,
                a_obj: A { a: 10 }
            },
            c: 30,
        }
        .to_value(),
        value!({
           "a": 10,
           "b": 20,
           "c": 30,
        })
    );

    struct Query;

    #[Object]
    impl Query {
        async fn test(&self, input: MyInputObject) -> i32 {
            input.c + input.b_obj.b + input.b_obj.a_obj.a
        }

        async fn test_with_default(
            &self,
            #[graphql(default_with = r#"MyInputObject {
            b_obj: B {
                b: 2,
                a_obj: A { a: 1 }
            },
            c: 3,
        }"#)]
            input: MyInputObject,
        ) -> i32 {
            input.c + input.b_obj.b + input.b_obj.a_obj.a
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute(
                r#"{
            test(input:{a:10, b: 20, c: 30})
        }"#
            )
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "test": 60,
        })
    );

    assert_eq!(
        schema
            .execute(
                r#"{
            test(input:{a:10, c: 30})
        }"#
            )
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "test": 110,
        })
    );

    assert_eq!(
        schema
            .execute(
                r#"{
            testWithDefault
        }"#
            )
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "testWithDefault": 6,
        })
    );
}

#[tokio::test]
pub async fn test_inputobject_flatten_multiple() {
    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct A {
        a: i32,
    }

    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct B {
        b: i32,
    }

    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct C {
        c: i32,
    }

    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct Abc {
        #[graphql(flatten)]
        a: A,

        #[graphql(flatten)]
        b: B,

        #[graphql(flatten)]
        c: C,
    }

    assert_eq!(
        Abc::parse(Some(value!({
           "a": 10,
           "b": 20,
           "c": 30,
        })))
        .unwrap(),
        Abc {
            a: A { a: 10 },
            b: B { b: 20 },
            c: C { c: 30 }
        }
    );

    assert_eq!(
        Abc {
            a: A { a: 10 },
            b: B { b: 20 },
            c: C { c: 30 }
        }
        .to_value(),
        value!({
           "a": 10,
           "b": 20,
           "c": 30,
        })
    );
}

#[tokio::test]
pub async fn test_inputobject_flatten_generic() {
    #[derive(InputObject, Debug, Eq, PartialEq)]
    struct A {
        a: i32,
    }

    #[derive(InputObject, Debug, Eq, PartialEq)]
    #[graphql(concrete(name = "AB", params(A, i32)))]
    struct B<T: InputObjectType, C: InputType> {
        #[graphql(default = 70)]
        b: i32,
        #[graphql(flatten)]
        a_obj: T,
        c: C,
    }

    assert_eq!(
        B::parse(Some(value!({
           "a": 10,
           "b": 20,
           "c": 30,
        })))
        .unwrap(),
        B {
            b: 20,
            a_obj: A { a: 10 },
            c: 30,
        }
    );

    assert_eq!(
        B {
            b: 20,
            a_obj: A { a: 10 },
            c: 30,
        }
        .to_value(),
        value!({
           "a": 10,
           "b": 20,
           "c": 30,
        })
    );

    struct Query;

    #[Object]
    impl Query {
        async fn test(&self, input: B<A, i32>) -> i32 {
            input.c + input.b + input.a_obj.a
        }

        async fn test_with_default(
            &self,
            #[graphql(default_with = r#"B {
                b: 2,
                a_obj: A { a: 1 },
                c: 3
            }"#)]
            input: B<A, i32>,
        ) -> i32 {
            input.c + input.b + input.a_obj.a
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute(
                r#"{
            test(input:{a:10, b: 20, c: 30})
        }"#
            )
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "test": 60,
        })
    );

    assert_eq!(
        schema
            .execute(
                r#"{
            test(input:{a:10, c: 30})
        }"#
            )
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "test": 110,
        })
    );

    assert_eq!(
        schema
            .execute(
                r#"{
            testWithDefault
        }"#
            )
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "testWithDefault": 6,
        })
    );
}

#[tokio::test]
pub async fn test_input_object_skip_field() {
    #[derive(InputObject)]
    struct MyInput2 {
        a: i32,
        #[graphql(skip)]
        b: i32,
    }

    struct Root;

    #[Object]
    impl Root {
        async fn a(&self, input: MyInput2) -> i32 {
            assert_eq!(input.b, i32::default());
            input.a
        }
    }

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
            a(input:{a: 777})
        }"#;
    assert_eq!(
        schema.execute(query).await.into_result().unwrap().data,
        value!({
            "a": 777
        })
    );
}

#[tokio::test]
pub async fn test_box_input_object() {
    #[derive(InputObject)]
    struct MyInput {
        value: i32,
        input: Option<Box<MyInput>>,
    }

    struct Root;

    #[Object]
    impl Root {
        async fn q(&self, input: MyInput) -> i32 {
            input.value
                + input.input.as_ref().unwrap().value
                + input.input.as_ref().unwrap().input.as_ref().unwrap().value
        }
    }

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
            q(input: {value: 100, input: { value: 200, input: { value: 300 } } })
        }"#;
    assert_eq!(
        schema.execute(query).await.into_result().unwrap().data,
        value!({
            "q": 600
        })
    );
}

#[tokio::test]
pub async fn test_both_input_output() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "MyObjectInput")]
    #[allow(dead_code)]
    struct MyObject {
        #[graphql(default = 10)]
        a: i32,
        b: bool,
        #[graphql(skip)]
        c: String,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject) -> MyObject {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: {a: 1, b: true}) { a b } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
                "b": true,
            }
        })
    );

    assert_eq!(
        schema
            .execute("{ obj(input: {b: true}) { a b } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 10,
                "b": true,
            }
        })
    );

    assert_eq!(<MyObject as InputType>::type_name(), "MyObjectInput");
    assert_eq!(<MyObject as OutputTypeMarker>::type_name(), "MyObject");
}

#[tokio::test]
pub async fn test_both_input_output_generic() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(concrete(name = "MyObjectU16", params(u16)))]
    #[graphql(concrete(name = "MyObjectString", params(String)))]
    #[allow(dead_code)]
    struct MyObject<T: InputType + OutputType> {
        a: T,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject<u16>) -> MyObject<String> {
            MyObject::<String> {
                a: format!("{}", input.a),
            }
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: {a: 123}) { a } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": "123",
            }
        })
    );

    assert_eq!(<MyObject<u16> as InputType>::type_name(), "MyObjectU16");
    assert_eq!(
        <MyObject<u16> as OutputTypeMarker>::type_name(),
        "MyObjectU16"
    );
    assert_eq!(
        <MyObject<String> as InputType>::type_name(),
        "MyObjectString"
    );
    assert_eq!(
        <MyObject<String> as OutputTypeMarker>::type_name(),
        "MyObjectString"
    );
}

#[tokio::test]
pub async fn test_both_input_output_generic_with_input_name_suffix() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name_suffix = "Input")]
    #[graphql(concrete(name = "MyObjectU32", params(u32)))]
    #[graphql(concrete(name = "MyObjectString", params(String)))]
    #[allow(dead_code)]
    struct MyObject<T: InputType + OutputType> {
        a: T,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject<u32>) -> MyObject<String> {
            MyObject::<String> {
                a: format!("{}", input.a),
            }
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: {a: 123}) { a } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": "123",
            }
        })
    );

    assert_eq!(
        <MyObject<u32> as InputType>::type_name(),
        "MyObjectU32Input"
    );
    assert_eq!(
        <MyObject<u32> as OutputTypeMarker>::type_name(),
        "MyObjectU32"
    );
    assert_eq!(
        <MyObject<String> as InputType>::type_name(),
        "MyObjectStringInput"
    );
    assert_eq!(
        <MyObject<String> as OutputTypeMarker>::type_name(),
        "MyObjectString"
    );
}

#[tokio::test]
pub async fn test_both_input_output_generic_with_nesting() {
    #[derive(Clone, Copy, PartialEq, Eq, Enum, serde::Serialize)]
    enum MyEnum {
        Option1,
        Option2,
    }

    #[derive(SimpleObject, InputObject)]
    #[graphql(concrete(name = "MyObjectU16", params(u16)))]
    #[graphql(concrete(
        name = "MyObjectMyEnum",
        input_name = "MyObjectMyEnumInput",
        params(MyEnum)
    ))]
    #[allow(dead_code)]
    struct MyObject<T: InputType + OutputType> {
        a: T,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject<MyEnum>) -> MyObject<MyEnum> {
            MyObject::<MyEnum> { a: input.a }
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: {a: OPTION1}) { a } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": "OPTION1",
            }
        }),
    );

    assert_eq!(<MyObject<u16> as InputType>::type_name(), "MyObjectU16");
    assert_eq!(
        <MyObject<u16> as OutputTypeMarker>::type_name(),
        "MyObjectU16"
    );
    assert_eq!(
        <MyObject<MyEnum> as InputType>::type_name(),
        "MyObjectMyEnumInput"
    );
    assert_eq!(
        <MyObject<MyEnum> as OutputTypeMarker>::type_name(),
        "MyObjectMyEnum"
    );
}

#[tokio::test]
pub async fn test_both_input_output_2() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(name = "MyObj", input_name = "MyObjectInput")]
    #[allow(dead_code)]
    struct MyObject {
        #[graphql(default = 10)]
        a: i32,
        b: bool,
        #[graphql(skip)]
        c: String,
    }

    assert_eq!(<MyObject as InputType>::type_name(), "MyObjectInput");
    assert_eq!(<MyObject as OutputTypeMarker>::type_name(), "MyObj");
}

#[tokio::test]
pub async fn test_both_input_output_with_same_name() {
    #[derive(SimpleObject, InputObject)]
    #[allow(dead_code)]
    struct MyObject {
        #[graphql(default = 10)]
        a: i32,
        b: bool,
        #[graphql(skip)]
        c: String,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject) -> MyObject {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: {a: 1, b: true}) { a b } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
                "b": true,
            }
        })
    );

    let sdl = schema.sdl();
    assert!(sdl.contains("input MyObjectInput"));
    assert!(sdl.contains("obj(input: MyObjectInput!): MyObject!"));

    let resp = schema
        .execute("{ obj(input: {a: 1, b: true, extra: 2}) { a b } }")
        .await;
    assert!(resp.errors[0].message.contains("unknown field \"extra\""));

    assert_eq!(
        schema
            .execute(
                Request::new("query($input: MyObjectInput!) { obj(input: $input) { a b } }")
                    .variables(Variables::from_value(value!({
                        "input": {
                            "a": 2,
                            "b": false,
                        }
                    }))),
            )
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 2,
                "b": false,
            }
        })
    );
}

#[tokio::test]
pub async fn test_both_input_output_flatten() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "ABCInput")]
    #[graphql(name = "ABC")]
    #[allow(clippy::upper_case_acronyms)]
    struct ABC {
        a: i32,
        #[graphql(flatten)]
        bc: BC,
    }

    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "BCInput")]
    struct BC {
        b: i32,
        c: i32,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: ABC) -> ABC {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: { a: 1, b: 2, c: 3 }) { a b c } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
                "b": 2,
                "c": 3
            }
        })
    );
}

#[tokio::test]
pub async fn test_skip_input() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "MyObjectInput")]
    #[allow(dead_code)]
    struct MyObject {
        a: i32,
        #[graphql(skip_input)]
        b: i32,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject) -> MyObject {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: { a: 1 }) { a b } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
                "b": 0,
            }
        })
    );
}

#[tokio::test]
pub async fn test_skip_output() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "MyObjectInput")]
    #[allow(dead_code)]
    struct MyObject {
        a: i32,
        #[graphql(skip_output)]
        b: i32,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject) -> MyObject {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: { a: 1, b: 2 }) { a } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
            }
        })
    );
}

#[tokio::test]
pub async fn test_complex_output() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "MyObjectInput")]
    #[graphql(complex)]
    #[allow(dead_code)]
    struct MyObject {
        a: i32,
    }

    #[ComplexObject]
    impl MyObject {
        async fn double(&self) -> i32 {
            self.a * 2
        }
    }

    struct Query;
    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject) -> MyObject {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: { a: 1 }) { a, double } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
                "double": 2,
            }
        })
    );
}

#[tokio::test]
pub async fn test_input_object_process_with() {
    mod processor {
        pub fn string(input: &mut String) {
            while let Some(ch) = input.pop() {
                if !ch.is_whitespace() {
                    input.push(ch);
                    break;
                }
            }
        }
    }
    #[derive(InputObject)]
    struct MyInput {
        // processor does nothing on default value
        #[graphql(default = "  ", process_with = "processor::string")]
        a: String,

        #[graphql(process_with = "processor::string")]
        b: String,
    }

    struct MyOutput {
        a: String,
        b: String,
    }

    #[Object]
    impl MyOutput {
        async fn a(&self) -> &String {
            &self.a
        }

        async fn b(&self) -> &String {
            &self.b
        }
    }

    struct Root;

    #[Object]
    impl Root {
        async fn a(&self, input: MyInput) -> MyOutput {
            MyOutput {
                a: input.a,
                b: input.b,
            }
        }
    }

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
            a(input:{b: "test b   "}) {
                a b
            }
        }"#
    .to_owned();
    assert_eq!(
        schema.execute(&query).await.data,
        value!({
            "a": {
                "a": "  ",
                "b": "test b",
            }
        })
    );

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
            a(input:{a: "test a ", b: "test"}) {
                a b
            }
        }"#
    .to_owned();
    assert_eq!(
        schema.execute(&query).await.data,
        value!({
            "a": {
                "a": "test a",
                "b": "test",
            }
        })
    );
}

#[tokio::test]
pub async fn test_input_object_validator() {
    fn check_my_object(obj: &MyInput) -> Result<(), &'static str> {
        if obj.a < 100 || obj.b < 100 {
            Err("invalid MyInput")
        } else {
            Ok(())
        }
    }

    #[derive(InputObject)]
    #[graphql(validator = "check_my_object")]
    struct MyInput {
        a: i32,
        b: i32,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn a(&self, input: MyInput) -> i32 {
            input.a + input.b
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);

    assert_eq!(
        schema
            .execute("{ a(input: { a: 200, b: 300 }) }")
            .await
            .data,
        value!({ "a": 500 })
    );

    assert_eq!(
        schema
            .execute("{ a(input: { a: 100, b: 25 }) }")
            .await
            .into_result()
            .unwrap_err(),
        vec![ServerError {
            message: r#"Failed to parse "MyInput": invalid MyInput"#.to_string(),
            source: None,
            locations: vec![Pos {
                line: 1,
                column: 12
            }],
            path: vec![PathSegment::Field("a".to_string())],
            extensions: None
        }]
    );
}

#[tokio::test]
pub async fn test_custom_validator_with_extensions_input() {
    struct MyValidator {
        expect: i32,
    }

    impl MyValidator {
        pub fn new(n: i32) -> Self {
            MyValidator { expect: n }
        }
    }

    impl CustomValidator<i32> for MyValidator {
        fn check(&self, value: &i32) -> Result<(), InputValueError<i32>> {
            if *value == self.expect {
                Ok(())
            } else {
                Err(
                    InputValueError::custom(format!("expect 100, actual {}", value))
                        .with_extension("code", 99),
                )
            }
        }
    }

    #[derive(InputObject, Debug)]
    struct ValueInput {
        #[graphql(validator(custom = "MyValidator::new(100)"))]
        v: i32,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn value(&self, n: ValueInput) -> i32 {
            n.v
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ value(n: {v: 100}) }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({ "value": 100 })
    );

    let mut error_extensions = ErrorExtensionValues::default();
    error_extensions.set("code", 99);
    assert_eq!(
        schema
            .execute("{ value(n: {v: 11}) }")
            .await
            .into_result()
            .unwrap_err(),
        vec![ServerError {
            message: r#"Failed to parse "Int": expect 100, actual 11 (occurred while parsing "ValueInput")"#.to_string(),
            source: None,
            locations: vec![Pos {
                line: 1,
                column: 12
            }],
            path: vec![PathSegment::Field("value".to_string())],
            extensions: Some(error_extensions)
        }]
    );
}

#[tokio::test]
pub async fn test_inputobject_generic_with_name_type() {
    use std::borrow::Cow;

    #[derive(InputObject)]
    #[graphql(name_type)]
    struct GenericInput<T: InputType> {
        content: Vec<T>,
        count: u16,
    }

    impl<T: InputType> TypeName for GenericInput<T> {
        fn type_name() -> Cow<'static, str> {
            format!("{}GenericInput", <T as InputType>::type_name()).into()
        }
    }

    struct Query;

    #[Object]
    impl Query {
        async fn test_generic_input(&self, input: GenericInput<i32>) -> i32 {
            input.count as i32
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    let query = r#"{ testGenericInput(input: { content: [1, 2, 3], count: 3 }) }"#;
    let result = schema.execute(query).await;

    assert_eq!(
        result.data,
        value!({
            "testGenericInput": 3
        })
    );

    assert_eq!(
        <GenericInput<i32> as InputType>::type_name(),
        "IntGenericInput"
    );
}

#[tokio::test]
pub async fn test_input_name_suffix() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name_suffix = "Input")]
    #[allow(dead_code)]
    struct MyObject {
        #[graphql(default = 10)]
        a: i32,
        b: bool,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject) -> MyObject {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: {a: 1, b: true}) { a b } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
                "b": true,
            }
        })
    );

    // The input type name should be MyObject + "Input" suffix
    assert_eq!(<MyObject as InputType>::type_name(), "MyObjectInput");
    // The output type name should remain the original name
    assert_eq!(<MyObject as OutputTypeMarker>::type_name(), "MyObject");
}

#[tokio::test]
pub async fn test_input_name_suffix_with_oneof() {
    #[derive(OneofObject)]
    #[graphql(input_name_suffix = "Input")]
    enum MyOneof {
        A(i32),
        B(String),
    }

    struct Query;

    #[Object]
    impl Query {
        async fn test(&self, input: MyOneof) -> i32 {
            match input {
                MyOneof::A(a) => a,
                MyOneof::B(b) => b.len() as i32,
            }
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ test(input: { a: 42 }) }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "test": 42
        })
    );

    // The input type name should be MyOneof + "Input" suffix
    assert_eq!(<MyOneof as InputType>::type_name(), "MyOneofInput");
}

#[tokio::test]
pub async fn test_input_name_suffix_uses_graphql_name() {
    // input_name_suffix appends to the GraphQL type name used by the output object.
    #[derive(SimpleObject, InputObject)]
    #[graphql(name = "CustomObj", input_name_suffix = "Request")]
    #[allow(dead_code)]
    struct MyObject {
        a: i32,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject) -> MyObject {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: {a: 1}) { a } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 1,
            }
        })
    );

    assert_eq!(<MyObject as InputType>::type_name(), "CustomObjRequest");
    assert_eq!(<MyObject as OutputTypeMarker>::type_name(), "CustomObj");
}

#[tokio::test]
pub async fn test_input_object_input_using() {
    mod converters {
        pub fn to_uppercase(s: String) -> String {
            s.to_uppercase()
        }
    }

    #[derive(InputObject)]
    struct MyInput {
        #[graphql(input_using = "converters::to_uppercase")]
        name: String,
    }

    struct Root;

    #[Object]
    impl Root {
        async fn test(&self, input: MyInput) -> String {
            input.name
        }
    }

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
        test(input: { name: "hello" })
    }"#;
    assert_eq!(
        schema.execute(query).await.data,
        value!({
            "test": "HELLO"
        })
    );
}

#[tokio::test]
pub async fn test_input_object_input_using_runs_before_validators() {
    mod converter {
        pub fn append_suffix(mut value: String) -> String {
            value.push('!');
            value
        }
    }

    #[derive(InputObject)]
    struct MyInput {
        #[graphql(input_using = "converter::append_suffix", validator(max_length = 5))]
        name: String,
    }

    struct Root;

    #[Object]
    impl Root {
        async fn test(&self, input: MyInput) -> String {
            input.name
        }
    }

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
        test(input: { name: "hello" })
    }"#;
    assert!(schema.execute(query).await.into_result().is_err());
}

#[tokio::test]
pub async fn test_input_object_input_using_with_process_with() {
    mod processor {
        pub fn trim(input: &mut String) {
            *input = input.trim().to_string();
        }
    }

    mod converter {
        pub fn to_uppercase(s: String) -> String {
            s.to_uppercase()
        }
    }

    #[derive(InputObject)]
    struct MyInput {
        #[graphql(
            process_with = "processor::trim",
            input_using = "converter::to_uppercase"
        )]
        name: String,
    }

    struct Root;

    #[Object]
    impl Root {
        async fn test(&self, input: MyInput) -> String {
            input.name.clone()
        }
    }

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
        test(input: { name: "  hello world  " })
    }"#;
    assert_eq!(
        schema.execute(query).await.data,
        value!({
            "test": "HELLO WORLD"
        })
    );
}

#[tokio::test]
pub async fn test_input_object_input_using_infers_graphql_type() {
    struct Name(String);

    fn from_graphql(value: String) -> Name {
        Name(value.to_uppercase())
    }

    fn to_graphql(value: &Name) -> String {
        value.0.clone()
    }

    #[derive(InputObject)]
    struct MyInput {
        #[graphql(input_using = "from_graphql", output_using = "to_graphql")]
        name: Name,
    }

    struct Root;

    #[Object]
    impl Root {
        async fn test(&self, input: MyInput) -> String {
            input.name.0
        }
    }

    let schema = Schema::new(Root, EmptyMutation, EmptySubscription);
    let query = r#"{
        test(input: { name: "hello" })
    }"#;
    assert_eq!(
        schema.execute(query).await.data,
        value!({
            "test": "HELLO"
        })
    );
}

#[tokio::test]
pub async fn test_both_input_output_generic_same_concrete() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(concrete(name = "MyObjectI32", params(i32)))]
    #[allow(dead_code)]
    struct MyObject<T: InputType + OutputType> {
        a: T,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject<i32>) -> MyObject<i32> {
            input
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: {a: 123}) { a } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 123,
            }
        })
    );
}

#[tokio::test]
pub async fn test_both_input_output_generic_same_concrete_wrapped_input() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(concrete(name = "MyObjectI32", params(i32)))]
    #[allow(dead_code)]
    struct MyObject<T: InputType + OutputType> {
        a: T,
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(
            &self,
            inputs: Vec<MyObject<i32>>,
            fallback: Option<MyObject<i32>>,
        ) -> MyObject<i32> {
            inputs.into_iter().next().or(fallback).unwrap()
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    let sdl = schema.sdl();
    assert!(sdl.contains("inputs: [MyObjectI32Input!]!"));
    assert!(sdl.contains("fallback: MyObjectI32Input"));

    assert_eq!(
        schema
            .execute(
                Request::new(
                    "query($inputs: [MyObjectI32Input!]!, $fallback: MyObjectI32Input) { obj(inputs: $inputs, fallback: $fallback) { a } }",
                )
                .variables(Variables::from_value(value!({
                    "inputs": [{
                        "a": 321,
                    }],
                    "fallback": {
                        "a": 654,
                    }
                }))),
            )
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "a": 321,
            }
        })
    );
}

#[tokio::test]
pub async fn test_both_input_output_generic_same_concrete_nested_output() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(concrete(name = "MyObjectI32", params(i32)))]
    #[allow(dead_code)]
    struct MyObject<T: InputType + OutputType> {
        a: T,
    }

    struct Wrapper {
        inner: MyObject<i32>,
    }

    #[Object]
    impl Wrapper {
        async fn inner(&self) -> &MyObject<i32> {
            &self.inner
        }
    }

    struct Query;

    #[Object]
    impl Query {
        async fn obj(&self, input: MyObject<i32>) -> Wrapper {
            Wrapper { inner: input }
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ obj(input: {a: 456}) { inner { a } } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "inner": {
                    "a": 456,
                }
            }
        })
    );

    let sdl = schema.sdl();
    assert!(sdl.contains("input MyObjectI32Input"));
    assert!(sdl.contains("obj(input: MyObjectI32Input!): Wrapper!"));

    assert_eq!(
        schema
            .execute(
                Request::new(
                    "query($input: MyObjectI32Input!) { obj(input: $input) { inner { a } } }",
                )
                .variables(Variables::from_value(value!({
                    "input": {
                        "a": 789,
                    }
                }))),
            )
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "obj": {
                "inner": {
                    "a": 789,
                }
            }
        })
    );
}
