use async_graphql::*;
use futures_util::stream::{self, Stream};

#[derive(Clone, Debug)]
struct Circle {
    radius: f32,
}

/// Circle
#[Object]
impl Circle {
    async fn scale(&self, s: f32) -> TestInterface {
        Circle {
            radius: self.radius * s,
        }
        .into()
    }
}

#[derive(Clone, Debug)]
struct Square {
    width: f32,
}

/// Square
#[Object]
impl Square {
    #[graphql(deprecation = "Field scale is deprecated")]
    async fn scale(&self, s: f32) -> TestInterface {
        Square {
            width: self.width * s,
        }
        .into()
    }
}

#[derive(Clone, Debug, Interface)]
#[graphql(field(name = "scale", type = "TestInterface", arg(name = "s", type = "f32")))]
enum TestInterface {
    Circle(Circle),
    Square(Square),
}

/// Test Union
#[derive(Clone, Debug, Union)]
enum TestUnion {
    Circle(Circle),
    Square(Square),
}

/// Test Enum
#[derive(Enum, Copy, Clone, Eq, PartialEq)]
enum TestEnum {
    /// Kind 1
    Kind1,

    /// Kind 2
    #[graphql(deprecation = "Kind 2 deprecated")]
    Kind2,
}

#[derive(Clone, Debug, SimpleObject)]
struct SimpleList {
    items: Vec<String>,
}

#[derive(Clone, Debug, SimpleObject)]
struct SimpleOption {
    required: i32,
    optional: Option<i32>,
}

/// TestScalar
#[derive(Clone, Debug)]
struct TestScalar(i32);

/// Test scalar
#[Scalar]
impl ScalarType for TestScalar {
    fn parse(_value: Value) -> InputValueResult<Self> {
        Ok(TestScalar(42))
    }

    fn is_valid(_value: &Value) -> bool {
        true
    }

    fn to_value(&self) -> Value {
        Value::Number(self.0.into())
    }
}

/// Is SimpleObject
/// and some more ```lorem ipsum```
#[derive(SimpleObject)]
struct SimpleObject {
    /// Value a with # 'some' `markdown`"."
    /// and some more lorem ipsum
    a: i32,

    /// Value b description
    b: String,

    /// Value c description
    c: ID,

    d: SimpleOption,

    #[graphql(deprecation = "Field e is deprecated")]
    e: bool,

    #[graphql(deprecation)]
    e2: bool,

    #[graphql(deprecation = true)]
    e3: bool,

    #[graphql(deprecation = false)]
    e4: bool,

    f: TestEnum,

    g: TestInterface,

    h: TestUnion,

    i: SimpleList,

    j: TestScalar,
}

struct Query;

/// Global query
#[Object]
#[allow(unreachable_code)]
impl Query {
    /// Get a simple object
    async fn simple_object(&self) -> SimpleObject {
        unimplemented!()
    }
}

/// Simple Input
#[derive(InputObject)]
pub struct SimpleInput {
    pub a: String,
}

struct Mutation;

/// Global mutation
#[Object]
#[allow(unreachable_code)]
impl Mutation {
    /// simple_mutation description
    /// line2
    /// line3
    async fn simple_mutation(&self, _input: SimpleInput) -> SimpleObject {
        unimplemented!()
    }
}

struct Subscription;

/// Global subscription
#[Subscription]
impl Subscription {
    /// simple_subscription description
    async fn simple_subscription(
        &self,
        #[graphql(default = 1)] step: i32,
    ) -> impl Stream<Item = i32> {
        stream::once(async move { step })
    }
}

// #[tokio::test]
// pub async fn test_introspection_schema() {
//     let schema = Schema::new(Query, Mutation, Subscription);

//     let query = r#"
//     {
//         __schema {
//             directives {
//               name
//               locations
//             }
//             subscriptionType {
//               name
//               fields { name }
//             }
//             types {
//               name
//             }
//             queryType {
//               name
//             }
//             mutationType {
//               name
//             }
//             queryType {
//               name
//             }
//         }
//    }
//    "#;

//     let res_json = value!({
//         "__schema": {
//         }
//     });

//     let res = schema.execute(query).await.unwrap().data;

//     // pretty print result
//     // println!("{}", serde_json::to_string_pretty(&res).unwrap());

//     assert_eq!(res, res_json)
// }

// #[tokio::test]
// pub async fn test_introspection_documentation() {
//     let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
//
//     let query = r#"
//     {
//         __type(name: "SimpleObject") {
//             name
//             description
//             fields {
//                 name
//                 description
//             }
//         }
//    }
//    "#;
//
//     let res_json = value!({
//         "__type": {
//             "name": "SimpleObject",
//             "description": "Is SimpleObject",
//             "fields": [
//               {
//                 "name": "a",
//                 "description": "Value a with # 'some' `markdown`\".\""
//               },
//               {
//                 "name": "b",
//                 "description": "Value b description"
//               },
//               {
//                 "name": "c",
//                 "description": "Value c description"
//               },
//               {
//                 "name": "d",
//                 "description": ""
//               },
//               {
//                 "name": "f",
//                 "description": null
//               },
//               {
//                 "name": "g",
//                 "description": null
//               },
//               {
//                 "name": "h",
//                 "description": null
//               },
//               {
//                 "name": "i",
//                 "description": null
//               },
//               {
//                 "name": "j",
//                 "description": null
//               }
//             ]
//         }
//     });
//
//     let res = schema.execute(query).await.unwrap().data;
//
//     assert_eq!(res, res_json)
// }

#[tokio::test]
pub async fn test_introspection_deprecation() {
    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);

    let get_object_query = |obj, is_deprecated| {
        format!(
            r#"
        {{
            __type(name: "{}") {{
                fields(includeDeprecated: {})  {{
                    name
                    isDeprecated
                    deprecationReason
                }}
            }}
       }}
       "#,
            obj, is_deprecated
        )
    };

    // SimpleObject with deprecated inclusive
    let mut query = get_object_query("SimpleObject", "true");

    let mut res_json = value!({
        "__type": {
            "fields": [
              {
                "name": "a",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "b",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "c",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "d",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "e",
                "isDeprecated": true,
                "deprecationReason": "Field e is deprecated"
              },
              {
                "name": "e2",
                "isDeprecated": true,
                "deprecationReason": null
              },
              {
                "name": "e3",
                "isDeprecated": true,
                "deprecationReason": null
              },
              {
                "name": "e4",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "f",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "g",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "h",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "i",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "j",
                "isDeprecated": false,
                "deprecationReason": null
              }
            ]
        }
    });

    let mut res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);

    // SimpleObject with deprecated fields exclusive
    query = get_object_query("SimpleObject", "false");

    res_json = value!({
        "__type": {
            "fields": [
              {
                "name": "a",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "b",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "c",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "d",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "e4",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "f",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "g",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "h",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "i",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "j",
                "isDeprecated": false,
                "deprecationReason": null
              }
            ]
        }
    });

    res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);

    // Object with only one deprecated field inclusive
    query = get_object_query("Square", "true");

    res_json = value!({
        "__type": {
            "fields": [
                {
                "name": "scale",
                "isDeprecated": true,
                "deprecationReason": "Field scale is deprecated"
                }
            ]
        }
    });

    res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);

    // Object with only one deprecated field exclusive
    query = get_object_query("Square", "false");

    res_json = value!({
        "__type": {
            "fields": []
        }
    });

    res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);

    let get_enum_query = |obj, is_deprecated| {
        format!(
            r#"
        {{
            __type(name: "{}") {{
                enumValues(includeDeprecated: {})  {{
                    name
                    isDeprecated
                    deprecationReason
                }}
            }}
       }}
       "#,
            obj, is_deprecated
        )
    };

    // Enum with deprecated value inclusive
    query = get_enum_query("TestEnum", "true");

    res_json = value!({
        "__type": {
            "enumValues": [
              {
                "name": "KIND_1",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "KIND_2",
                "isDeprecated": true,
                "deprecationReason": "Kind 2 deprecated"
              }
            ]
        }
    });

    res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);

    // Enum with deprecated value exclusive
    query = get_enum_query("TestEnum", "false");

    res_json = value!({
        "__type": {
            "enumValues": [
              {
                "name": "KIND_1",
                "isDeprecated": false,
                "deprecationReason": null
              }
            ]
        }
    });

    res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);
}

#[tokio::test]
pub async fn test_introspection_type_kind() {
    let schema = Schema::new(Query, Mutation, EmptySubscription);

    let get_type_kind_query = |obj| {
        format!(
            r#"{{
              __type(name: "{}") {{
                  name
                  kind
              }}
          }}"#,
            obj
        )
    };

    // Test simple object
    let mut query = get_type_kind_query("SimpleObject");

    let mut res_json = value!({
        "__type": {
            "name": "SimpleObject",
            "kind": "OBJECT"
        }
    });

    let mut res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);

    // Test object
    query = get_type_kind_query("Square");

    res_json = value!({
        "__type": {
            "name": "Square",
            "kind": "OBJECT"
        }
    });

    res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);

    // Test enum
    query = get_type_kind_query("TestEnum");

    res_json = value!({
        "__type": {
            "name": "TestEnum",
            "kind": "ENUM"
        }
    });

    res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);

    // Test union
    query = get_type_kind_query("TestUnion");

    res_json = value!({
        "__type": {
            "name": "TestUnion",
            "kind": "UNION"
        }
    });

    res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);

    // Test scalar
    query = get_type_kind_query("ID");

    res_json = value!({
        "__type": {
            "name": "ID",
            "kind": "SCALAR"
        }
    });

    res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);

    let get_field_kind_query = |obj| {
        format!(
            r#"{{
              __type(name: "{}") {{
                  fields {{
                      name
                      type {{ name kind ofType {{ name kind }} }}
                  }}
              }}
          }}"#,
            obj
        )
    };

    // Test list
    query = get_field_kind_query("SimpleList");

    res_json = value!({
        "__type": {
            "fields": [
                {
                "name": "items",
                "type": {
                    "name": null,
                    "kind": "NON_NULL",
                    "ofType": {
                        "name": null,
                        "kind": "LIST"
                    }
                }
                }
            ]
          }
    });

    res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);

    // Test NON_NULL
    query = get_field_kind_query("SimpleOption");

    res_json = value!({
        "__type": {
            "fields": [
              {
                "name": "required",
                "type": {
                  "name": null,
                  "kind": "NON_NULL",
                  "ofType": {
                    "name": "Int",
                    "kind": "SCALAR"
                  }
                }
              },
              {
                "name": "optional",
                "type": {
                  "name": "Int",
                  "kind": "SCALAR",
                  "ofType": null
                }
              }
            ]
        }
    });

    res = schema.execute(&query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);
}

#[tokio::test]
pub async fn test_introspection_scalar() {
    let schema = Schema::new(Query, Mutation, EmptySubscription);

    let query = r#"
    {
        __type(name: "TestScalar") {
            kind
            name
            description
        }
   }
   "#;

    let res_json = value!({
        "__type": {
            "kind": "SCALAR",
            "name": "TestScalar",
            "description": "Test scalar",
        }
    });

    let res = schema.execute(query).await.into_result().unwrap().data;

    assert_eq!(res, res_json)
}

#[tokio::test]
pub async fn test_introspection_union() {
    let schema = Schema::new(Query, Mutation, EmptySubscription);

    let query = r#"
    {
        __type(name: "TestUnion") {
            kind
            name
            description
            possibleTypes { name }
        }
   }
   "#;

    let res_json = value!({
        "__type": {
            "kind": "UNION",
            "name": "TestUnion",
            "description": "Test Union",
            "possibleTypes": [
              {
                "name": "Circle"
              },
              {
                "name": "Square"
              }
            ]
        }
    });

    let res = schema.execute(query).await.into_result().unwrap().data;

    assert_eq!(res, res_json)
}

#[tokio::test]
pub async fn test_introspection_interface() {
    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);

    // Test if possibleTypes and other fields are set
    let mut query = r#"
    {
        __type(name: "TestInterface") {
            kind
            name
            description
            possibleTypes { name }
            fields { name }
        }
   }
   "#;

    let mut res_json = value!({
        "__type": {
            "kind": "INTERFACE",
            "name": "TestInterface",
            "description": null,
            "possibleTypes": [
              {
                "name": "Circle"
              },
              {
                "name": "Square"
              }
            ],
            "fields": [
              {
                "name": "scale"
              }
            ]
        }
    });

    let mut res = schema.execute(query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);

    // Test if interfaces and other fields are set
    query = r#"
    {
        __type(name: "Circle") {
            kind
            name
            description
            interfaces { name }
            fields { name }
        }
   }
   "#;

    res_json = value!({
        "__type": {
            "kind": "OBJECT",
            "name": "Circle",
            "description": "Circle",
            "interfaces": [
              {
                "name": "TestInterface"
              }
            ],
            "fields": [
              {
                "name": "scale"
              }
            ]
        }
    });

    res = schema.execute(query).await.into_result().unwrap().data;

    assert_eq!(res, res_json);
}

#[tokio::test]
pub async fn test_introspection_enum() {
    let schema = Schema::new(Query, Mutation, EmptySubscription);

    let query = r#"
    {
        __type(name: "TestEnum") {
            kind
            name
            description
            enumValues(includeDeprecated: true) {
                name
                description
                isDeprecated
                deprecationReason
            }
        }
   }
   "#;

    let res_json = value!({
        "__type": {
            "kind": "ENUM",
            "name": "TestEnum",
            "description": "Test Enum",
            "enumValues": [
              {
                "name": "KIND_1",
                "description": "Kind 1",
                "isDeprecated": false,
                "deprecationReason": null
              },
              {
                "name": "KIND_2",
                "description": "Kind 2",
                "isDeprecated": true,
                "deprecationReason": "Kind 2 deprecated"
              }
            ]
        }
    });

    let res = schema.execute(query).await.into_result().unwrap().data;

    println!("{}", serde_json::to_string_pretty(&res).unwrap());

    assert_eq!(res, res_json)
}

#[tokio::test]
pub async fn test_introspection_input_object() {
    let schema = Schema::new(Query, Mutation, EmptySubscription);

    let query = r#"
    {
        __type(name: "SimpleInput") {
            kind
            name
            description
            inputFields { name }
        }
   }
   "#;

    let res_json = value!({
        "__type": {
            "kind": "INPUT_OBJECT",
            "name": "SimpleInput",
            "description": "Simple Input",
            "inputFields": [
                {
                    "name": "a"
                }
            ],
        }
    });

    let res = schema.execute(query).await.into_result().unwrap().data;

    assert_eq!(res, res_json)
}

#[tokio::test]
pub async fn test_introspection_mutation() {
    let schema = Schema::new(Query, Mutation, EmptySubscription);

    let query = r#"
    {
        __type(name: "Mutation") {
            name
    		kind
    		description
            fields {
                description
                name
                type { kind name }
                args { name }
            }
        }
   }
   "#;

    let res_json = value!({
        "__type": {
            "name": "Mutation",
            "kind": "OBJECT",
            "description": "Global mutation",
            "fields": [
              {
                "description": "simple_mutation description\nline2\nline3",
                "name": "simpleMutation",
                "type": {
                  "kind": "NON_NULL",
                  "name": null
                },
                "args": [
                  {
                    "name": "input"
                  }
                ]
              }
            ]
        }
    });

    let res = schema.execute(query).await.into_result().unwrap().data;

    assert_eq!(res, res_json)
}

#[tokio::test]
pub async fn test_introspection_subscription() {
    let schema = Schema::new(Query, EmptyMutation, Subscription);

    let query = r#"
    {
        __type(name: "Subscription") {
            name
    		kind
    		description
            fields {
                description
                name
                type { kind name }
                args { name }
            }
        }
   }
   "#;

    let res_json = value!({
        "__type": {
            "name": "Subscription",
            "kind": "OBJECT",
            "description": "Global subscription",
            "fields": [
              {
                "description": "simple_subscription description",
                "name": "simpleSubscription",
                "type": {
                  "kind": "NON_NULL",
                  "name": null
                },
                "args": [
                  {
                    "name": "step"
                  }
                ]
              }
            ]
        }
    });

    let res = schema.execute(query).await.into_result().unwrap().data;

    assert_eq!(res, res_json)
}

// #[tokio::test]
// pub async fn test_introspection_full() {
//     let schema = Schema::new(Query, EmptyMutation, Subscription);
//
//     let query = r#"
//     {
//         __type(name: "SimpleObject") {
//             kind
//             name
//             description
//             fields(includeDeprecated: true) {
//                 name
//                 description
//                 args { name }
//                 type { name kind ofType { name kind } }
//                 isDeprecated
//                 deprecationReason
//             }
//             interfaces { name }
//             possibleTypes { name }
//             enumValues { name }
//             inputFields { name }
//             ofType { name }
//         }
//    }
//    "#;
//
//     let res_json = value!({
//         "__type": {
//             "kind": "OBJECT",
//             "name": "SimpleObject",
//             "description": "Is SimpleObject",
//             "fields": [
//               {
//                 "name": "a",
//                 "description": "Value a with # 'some' `markdown`\".\"",
//                 "args": [],
//                 "type": {
//                   "name": null,
//                   "kind": "NON_NULL",
//                   "ofType": {
//                     "name": "Int",
//                     "kind": "SCALAR"
//                   }
//                 },
//                 "isDeprecated": false,
//                 "deprecationReason": null
//               },
//               {
//                 "name": "b",
//                 "description": "Value b description",
//                 "args": [],
//                 "type": {
//                   "name": null,
//                   "kind": "NON_NULL",
//                   "ofType": {
//                     "name": "String",
//                     "kind": "SCALAR"
//                   }
//                 },
//                 "isDeprecated": false,
//                 "deprecationReason": null
//               },
//               {
//                 "name": "c",
//                 "description": "Value c description",
//                 "args": [],
//                 "type": {
//                   "name": null,
//                   "kind": "NON_NULL",
//                   "ofType": {
//                     "name": "ID",
//                     "kind": "SCALAR"
//                   }
//                 },
//                 "isDeprecated": false,
//                 "deprecationReason": null
//               },
//               {
//                 "name": "d",
//                 "description": "",
//                 "args": [],
//                 "type": {
//                   "name": null,
//                   "kind": "NON_NULL",
//                   "ofType": {
//                     "name": "SimpleOption",
//                     "kind": "OBJECT"
//                   }
//                 },
//                 "isDeprecated": false,
//                 "deprecationReason": null
//               },
//               {
//                 "name": "e",
//                 "description": null,
//                 "args": [],
//                 "type": {
//                   "name": null,
//                   "kind": "NON_NULL",
//                   "ofType": {
//                     "name": "Boolean",
//                     "kind": "SCALAR"
//                   }
//                 },
//                 "isDeprecated": true,
//                 "deprecationReason": "Field e is deprecated"
//               },
//               {
//                 "name": "f",
//                 "description": null,
//                 "args": [],
//                 "type": {
//                   "name": null,
//                   "kind": "NON_NULL",
//                   "ofType": {
//                     "name": "TestEnum",
//                     "kind": "ENUM"
//                   }
//                 },
//                 "isDeprecated": false,
//                 "deprecationReason": null
//               },
//               {
//                 "name": "g",
//                 "description": null,
//                 "args": [],
//                 "type": {
//                   "name": null,
//                   "kind": "NON_NULL",
//                   "ofType": {
//                     "name": "TestInterface",
//                     "kind": "INTERFACE"
//                   }
//                 },
//                 "isDeprecated": false,
//                 "deprecationReason": null
//               },
//               {
//                 "name": "h",
//                 "description": null,
//                 "args": [],
//                 "type": {
//                   "name": null,
//                   "kind": "NON_NULL",
//                   "ofType": {
//                     "name": "TestUnion",
//                     "kind": "UNION"
//                   }
//                 },
//                 "isDeprecated": false,
//                 "deprecationReason": null
//               },
//               {
//                 "name": "i",
//                 "description": null,
//                 "args": [],
//                 "type": {
//                   "name": null,
//                   "kind": "NON_NULL",
//                   "ofType": {
//                     "name": "SimpleList",
//                     "kind": "OBJECT"
//                   }
//                 },
//                 "isDeprecated": false,
//                 "deprecationReason": null
//               },
//               {
//                 "name": "j",
//                 "description": null,
//                 "args": [],
//                 "type": {
//                   "name": null,
//                   "kind": "NON_NULL",
//                   "ofType": {
//                     "name": "TestScalar",
//                     "kind": "SCALAR"
//                   }
//                 },
//                 "isDeprecated": false,
//                 "deprecationReason": null
//               }
//             ],
//             "interfaces": [],
//             "possibleTypes": null,
//             "enumValues": null,
//             "inputFields": null,
//             "ofType": null
//         }
//     });
//
//     let res = schema.execute(query).await.unwrap().data;
//
//     // pretty print result
//     // println!("{}", serde_json::to_string_pretty(&res).unwrap());
//
//     assert_eq!(res, res_json)
// }
