use gqlrs::*;

#[tokio::test]
pub async fn test_interface_simple_object() {
    #[derive(SimpleObject)]
    struct MyObj {
        id: i32,
        title: String,
    }

    #[derive(Interface)]
    #[graphql(field(name = "id", ty = "&i32"))]
    enum Node {
        MyObj(MyObj),
    }

    struct Query;

    #[Object]
    impl Query {
        async fn node(&self) -> Node {
            MyObj {
                id: 33,
                title: "haha".to_string(),
            }
            .into()
        }
    }

    let query = r#"{
            node {
                ... on Node {
                    id
                }
            }
        }"#;
    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema.execute(query).await.into_result().unwrap().data,
        value!({
            "node": {
                "id": 33,
            }
        })
    );
}

#[tokio::test]
pub async fn test_interface_simple_object2() {
    #[derive(SimpleObject)]
    struct MyObj {
        id: i32,
        title: String,
    }

    #[derive(Interface)]
    #[graphql(field(name = "id", ty = "&i32"))]
    enum Node {
        MyObj(MyObj),
    }

    struct Query;

    #[Object]
    impl Query {
        async fn node(&self) -> Node {
            MyObj {
                id: 33,
                title: "haha".to_string(),
            }
            .into()
        }
    }

    let query = r#"{
            node {
                ... on Node {
                    id
                }
            }
        }"#;
    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema.execute(query).await.into_result().unwrap().data,
        value!({
            "node": {
                "id": 33,
            }
        })
    );
}

#[tokio::test]
pub async fn test_multiple_interfaces() {
    struct MyObj;

    #[Object]
    impl MyObj {
        async fn value_a(&self) -> i32 {
            1
        }

        async fn value_b(&self) -> i32 {
            2
        }

        async fn value_c(&self) -> i32 {
            3
        }
    }

    #[derive(Interface)]
    #[graphql(field(name = "value_a", ty = "i32"))]
    enum InterfaceA {
        MyObj(MyObj),
    }

    #[derive(Interface)]
    #[graphql(field(name = "value_b", ty = "i32"))]
    enum InterfaceB {
        MyObj(MyObj),
    }

    struct Query;

    #[Object]
    impl Query {
        async fn my_obj(&self) -> InterfaceB {
            MyObj.into()
        }
    }

    let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
        .register_output_type::<InterfaceA>() // `InterfaceA` is not directly referenced, so manual registration is required.
        .finish();
    let query = r#"{
            myObj {
               ... on InterfaceA {
                valueA
              }
              ... on InterfaceB {
                valueB
              }
              ... on MyObj {
                valueC
              }
            }
        }"#;
    assert_eq!(
        schema.execute(query).await.into_result().unwrap().data,
        value!({
            "myObj": {
                "valueA": 1,
                "valueB": 2,
                "valueC": 3,
            }
        })
    );
}

#[tokio::test]
pub async fn test_multiple_objects_in_multiple_interfaces() {
    struct MyObjOne;

    #[Object]
    impl MyObjOne {
        async fn value_a(&self) -> i32 {
            1
        }

        async fn value_b(&self) -> i32 {
            2
        }

        async fn value_c(&self) -> i32 {
            3
        }
    }

    struct MyObjTwo;

    #[Object]
    impl MyObjTwo {
        async fn value_a(&self) -> i32 {
            1
        }
    }

    #[derive(Interface)]
    #[graphql(field(name = "value_a", ty = "i32"))]
    enum InterfaceA {
        MyObjOne(MyObjOne),
        MyObjTwo(MyObjTwo),
    }

    #[derive(Interface)]
    #[graphql(field(name = "value_b", ty = "i32"))]
    enum InterfaceB {
        MyObjOne(MyObjOne),
    }

    struct Query;

    #[Object]
    impl Query {
        async fn my_obj(&self) -> Vec<InterfaceA> {
            vec![MyObjOne.into(), MyObjTwo.into()]
        }
    }

    let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
        .register_output_type::<InterfaceB>() // `InterfaceB` is not directly referenced, so manual registration is required.
        .finish();
    let query = r#"{
             myObj {
                ... on InterfaceA {
                 valueA
               }
               ... on InterfaceB {
                 valueB
               }
               ... on MyObjOne {
                 valueC
               }
             }
         }"#;
    assert_eq!(
        schema.execute(query).await.into_result().unwrap().data,
        value!({
            "myObj": [{
                "valueA": 1,
                "valueB": 2,
                "valueC": 3,
            }, {
                "valueA": 1
            }]
        })
    );
}

#[tokio::test]
pub async fn test_interface_field_result() {
    struct MyObj;

    #[Object]
    impl MyObj {
        async fn value(&self) -> FieldResult<i32> {
            Ok(10)
        }
    }

    #[derive(Interface)]
    #[graphql(field(name = "value", ty = "i32"))]
    enum Node {
        MyObj(MyObj),
    }

    struct Query;

    #[Object]
    impl Query {
        async fn node(&self) -> Node {
            MyObj.into()
        }
    }

    let query = r#"{
            node {
                ... on Node {
                    value
                }
            }
        }"#;
    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema.execute(query).await.into_result().unwrap().data,
        value!({
            "node": {
                "value": 10,
            }
        })
    );
}

#[tokio::test]
pub async fn test_interface_field_method() {
    struct A;

    #[Object]
    impl A {
        #[graphql(name = "created_at")]
        pub async fn created_at(&self) -> i32 {
            1
        }
    }

    struct B;

    #[Object]
    impl B {
        #[graphql(name = "created_at")]
        pub async fn created_at(&self) -> i32 {
            2
        }
    }

    #[derive(Interface)]
    #[graphql(field(name = "created_at", method = "created_at", ty = "i32"))]
    enum MyInterface {
        A(A),
        B(B),
    }

    struct Query;

    #[Object]
    impl Query {
        async fn test(&self) -> MyInterface {
            A.into()
        }
    }

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

#[tokio::test]
pub async fn test_interface_implement_other_interface() {
    #[derive(Interface)]
    #[graphql(field(name = "id", ty = "ID"))]
    pub enum Entity {
        Company(Company),
        Organization(Organization),
    }

    #[derive(Interface)]
    #[graphql(field(name = "id", ty = "ID"))]
    pub enum Node {
        Entity(Entity),
    }

    pub struct Company {}

    #[Object]
    impl Company {
        pub async fn id(&self) -> ID {
            "88".into()
        }
    }

    pub struct Organization {}

    #[Object]
    impl Organization {
        pub async fn id(&self) -> ID {
            "99".into()
        }
    }

    struct Query;

    #[Object]
    impl Query {
        async fn company(&self) -> Node {
            Entity::Company(Company {}).into()
        }

        async fn organization(&self) -> Node {
            Entity::Organization(Organization {}).into()
        }
    }

    let query = r#"
        {
            company { id }
            organization { id }
        }
    "#;
    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema.execute(query).await.into_result().unwrap().data,
        value!({
            "company": {
                "id": "88",
            },
            "organization": {
                "id": "99",
            }
        })
    );
}

#[tokio::test]
pub async fn test_issue_330() {
    #[derive(Interface)]
    #[graphql(field(
        desc = "The code represented as a number.",
        name = "number",
        ty = "String"
    ))]
    pub enum Code {
        Barcode(Barcode),
        Qrcode(Qrcode),
    }

    pub struct Barcode(String);

    #[Object]
    impl Barcode {
        pub async fn number(&self) -> String {
            format!("barcode:{}", self.0)
        }
    }

    pub struct Qrcode(String);

    #[Object]
    impl Qrcode {
        pub async fn number(&self) -> String {
            format!("qrcode:{}", self.0)
        }
    }

    #[derive(Interface)]
    #[graphql(field(desc = "The article number.", name = "number", ty = "Code"))]
    pub enum Article {
        Book(Book),
    }

    pub struct Book {
        code: String,
    }

    #[Object]
    impl Book {
        pub async fn number(&self) -> Barcode {
            Barcode(self.code.clone())
        }
    }

    struct Query;

    #[Object]
    impl Query {
        pub async fn book(&self) -> Article {
            Book {
                code: "123456".to_string(),
            }
            .into()
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema
            .execute("{ book { number { number } } }")
            .await
            .into_result()
            .unwrap()
            .data,
        value!({
            "book": {
                "number": { "number": "barcode:123456" }
            }
        })
    );
}

#[tokio::test]
pub async fn test_interface_with_oneof_object() {
    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "MyObjAInput")]
    struct MyObjA {
        id: i32,
        title_a: String,
    }

    #[derive(SimpleObject, InputObject)]
    #[graphql(input_name = "MyObjBInput")]
    struct MyObjB {
        id: i32,
        title_b: String,
    }

    #[derive(OneofObject, Interface)]
    #[graphql(input_name = "NodeInput", field(name = "id", ty = "&i32"))]
    enum Node {
        MyObjA(MyObjA),
        MyObjB(MyObjB),
    }

    struct Query;

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

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    let query_a = r#"{
            node(input: { myObjA: { id: 10, titleA: "abc" } }) {
                id
                ... on MyObjA {
                    titleA
                }
                ... on MyObjB {
                    titleB
                }
            }
        }"#;
    assert_eq!(
        schema.execute(query_a).await.into_result().unwrap().data,
        value!({
            "node": {
                "id": 10,
                "titleA": "abc",
            }
        })
    );
    let query_b = r#"{
            node(input: { myObjB: { id: 10, titleB: "abc" } }) {
                id
                ... on MyObjA {
                    titleA
                }
                ... on MyObjB {
                    titleB
                }
            }
        }"#;
    assert_eq!(
        schema.execute(query_b).await.into_result().unwrap().data,
        value!({
            "node": {
                "id": 10,
                "titleB": "abc",
            }
        })
    );
}

#[tokio::test]
pub async fn test_interface_generic_with_where_clause() {
    trait SomeTrait: Send + Sync {
        fn get_value(&self) -> i32;
    }

    struct SomeNode(i32);
    impl SomeTrait for SomeNode {
        fn get_value(&self) -> i32 {
            self.0
        }
    }

    struct GenericNode<M>(M);

    #[Object(name = "GenericNode")]
    impl<M> GenericNode<M>
    where
        M: SomeTrait,
    {
        async fn value(&self) -> i32 {
            self.0.get_value()
        }
    }

    #[derive(Interface)]
    #[graphql(field(name = "value", ty = "i32"))]
    enum NodeInterface<M>
    where
        M: SomeTrait,
    {
        GenericNode(GenericNode<M>),
    }

    struct Query;

    #[Object]
    impl Query {
        async fn node(&self) -> NodeInterface<SomeNode> {
            GenericNode(SomeNode(42)).into()
        }
    }

    let query = r#"{
        node {
            value
        }
    }"#;
    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    assert_eq!(
        schema.execute(query).await.into_result().unwrap().data,
        value!({
            "node": {
                "value": 42,
            }
        })
    );
}

/// Module docs
#[deny(missing_docs)]
pub mod test_interface_docs {
    use gqlrs::*;

    /// Some object
    #[derive(SimpleObject)]
    pub struct MyObj {
        id: i32,
        title: String,
    }

    /// Some interface
    #[derive(Interface)]
    #[graphql(field(name = "id", ty = "&i32", desc = "Get the id"))]
    pub enum Node {
        /// Some objects
        MyObj(MyObj),
    }
}

#[tokio::test]
pub async fn test_interface_option_string_field() {
    #[derive(SimpleObject)]
    struct MyObjA {
        id: i32,
        name: Option<String>,
    }

    #[derive(SimpleObject)]
    struct MyObjB {
        id: i32,
        name: Option<String>,
    }

    #[derive(Interface)]
    #[graphql(field(name = "name", ty = "Option<String>"))]
    enum NamedObj {
        MyObjA(MyObjA),
        MyObjB(MyObjB),
    }

    struct Query;

    #[Object]
    impl Query {
        async fn named_obj_a(&self) -> NamedObj {
            MyObjA {
                id: 1,
                name: Some("Alice".to_string()),
            }
            .into()
        }

        async fn named_obj_b(&self) -> NamedObj {
            MyObjB { id: 2, name: None }.into()
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    let query = r#"{
        namedObjA {
            name
            ... on MyObjA {
                id
            }
        }
        namedObjB {
            name
            ... on MyObjB {
                id
            }
        }
    }"#;
    let result = schema.execute(query).await.into_result().unwrap().data;
    assert_eq!(
        result,
        value!({
            "namedObjA": {
                "name": "Alice",
                "id": 1,
            },
            "namedObjB": {
                "name": null,
                "id": 2,
            }
        })
    );
}

#[tokio::test]
pub async fn test_interface_owned_option_object_field() {
    #[derive(SimpleObject)]
    struct Child {
        value: i32,
    }

    struct MyObj;

    #[Object]
    impl MyObj {
        async fn child(&self) -> Option<Child> {
            Some(Child { value: 7 })
        }
    }

    #[derive(Interface)]
    #[graphql(field(name = "child", ty = "Option<Child>"))]
    enum HasChild {
        MyObj(MyObj),
    }

    struct Query;

    #[Object]
    impl Query {
        async fn has_child(&self) -> HasChild {
            MyObj.into()
        }
    }

    let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    let result = schema
        .execute("{ hasChild { child { value } } }")
        .await
        .into_result()
        .unwrap()
        .data;
    assert_eq!(
        result,
        value!({
            "hasChild": {
                "child": {
                    "value": 7,
                },
            }
        })
    );
}
