use indexmap::{IndexMap, IndexSet};
use crate::{
    dynamic::{InputValue, SchemaError, TypeRef},
    registry::{Deprecation, MetaField, MetaType, Registry},
};
#[derive(Debug)]
pub struct InterfaceField {
    pub(crate) name: String,
    pub(crate) description: Option<String>,
    pub(crate) arguments: IndexMap<String, InputValue>,
    pub(crate) ty: TypeRef,
    pub(crate) deprecation: Deprecation,
    pub(crate) external: bool,
    pub(crate) requires: Option<String>,
    pub(crate) provides: Option<String>,
    pub(crate) shareable: bool,
    pub(crate) inaccessible: bool,
    pub(crate) tags: Vec<String>,
    pub(crate) override_from: Option<String>,
}
impl InterfaceField {
    pub fn new(name: impl Into<String>, ty: impl Into<TypeRef>) -> Self {
        Self {
            name: name.into(),
            description: None,
            arguments: Default::default(),
            ty: ty.into(),
            deprecation: Deprecation::NoDeprecated,
            external: false,
            requires: None,
            provides: None,
            shareable: false,
            inaccessible: false,
            tags: Vec::new(),
            override_from: None,
        }
    }
    impl_set_description!();
    impl_set_deprecation!();
    impl_set_external!();
    impl_set_requires!();
    impl_set_provides!();
    impl_set_shareable!();
    impl_set_inaccessible!();
    impl_set_tags!();
    impl_set_override_from!();
    #[inline]
    pub fn argument(mut self, input_value: InputValue) -> Self {
        self.arguments.insert(input_value.name.clone(), input_value);
        self
    }
}
#[derive(Debug)]
pub struct Interface {
    pub(crate) name: String,
    pub(crate) description: Option<String>,
    pub(crate) fields: IndexMap<String, InterfaceField>,
    pub(crate) implements: IndexSet<String>,
    keys: Vec<String>,
    extends: bool,
    inaccessible: bool,
    tags: Vec<String>,
}
impl Interface {
    #[inline]
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            description: None,
            fields: Default::default(),
            implements: Default::default(),
            keys: Vec::new(),
            extends: false,
            inaccessible: false,
            tags: Vec::new(),
        }
    }
    impl_set_description!();
    impl_set_extends!();
    impl_set_inaccessible!();
    impl_set_tags!();
    #[inline]
    pub fn field(mut self, field: InterfaceField) -> Self {
        assert!(
            !self.fields.contains_key(&field.name),
            "Field `{}` already exists",
            field.name
        );
        self.fields.insert(field.name.clone(), field);
        self
    }
    #[inline]
    pub fn implement(mut self, interface: impl Into<String>) -> Self {
        let interface = interface.into();
        assert!(
            !self.implements.contains(&interface),
            "Implement `{}` already exists",
            interface
        );
        self.implements.insert(interface);
        self
    }
    pub fn key(mut self, fields: impl Into<String>) -> Self {
        self.keys.push(fields.into());
        self
    }
    #[inline]
    pub fn type_name(&self) -> &str {
        &self.name
    }
    #[inline]
    pub(crate) fn is_entity(&self) -> bool {
        !self.keys.is_empty()
    }
    pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> {
        let mut fields = IndexMap::new();
        for field in self.fields.values() {
            let mut args = IndexMap::new();
            for argument in field.arguments.values() {
                args.insert(argument.name.clone(), argument.to_meta_input_value());
            }
            fields.insert(
                field.name.clone(),
                MetaField {
                    name: field.name.clone(),
                    description: field.description.clone(),
                    args,
                    ty: field.ty.to_string(),
                    deprecation: field.deprecation.clone(),
                    cache_control: Default::default(),
                    external: field.external,
                    requires: field.requires.clone(),
                    provides: field.provides.clone(),
                    visible: None,
                    shareable: field.shareable,
                    inaccessible: field.inaccessible,
                    tags: field.tags.clone(),
                    override_from: field.override_from.clone(),
                    compute_complexity: None,
                    directive_invocations: vec![],
                },
            );
        }
        registry.types.insert(
            self.name.clone(),
            MetaType::Interface {
                name: self.name.clone(),
                description: self.description.clone(),
                fields,
                possible_types: Default::default(),
                extends: self.extends,
                keys: if !self.keys.is_empty() {
                    Some(self.keys.clone())
                } else {
                    None
                },
                visible: None,
                inaccessible: self.inaccessible,
                tags: self.tags.clone(),
                rust_typename: None,
                directive_invocations: vec![],
            },
        );
        Ok(())
    }
}
#[cfg(test)]
mod tests {
    use async_graphql_parser::Pos;
    use crate::{dynamic::*, value, PathSegment, ServerError, Value};
    #[tokio::test]
    async fn basic_interface() {
        let obj_a = Object::new("MyObjA")
            .implement("MyInterface")
            .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| {
                FieldFuture::new(async { Ok(Some(Value::from(100))) })
            }))
            .field(Field::new("b", TypeRef::named(TypeRef::INT), |_| {
                FieldFuture::new(async { Ok(Some(Value::from(200))) })
            }));
        let obj_b = Object::new("MyObjB")
            .implement("MyInterface")
            .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| {
                FieldFuture::new(async { Ok(Some(Value::from(300))) })
            }))
            .field(Field::new("c", TypeRef::named(TypeRef::INT), |_| {
                FieldFuture::new(async { Ok(Some(Value::from(400))) })
            }));
        let interface = Interface::new("MyInterface")
            .field(InterfaceField::new("a", TypeRef::named(TypeRef::INT)));
        let query = Object::new("Query")
            .field(Field::new(
                "valueA",
                TypeRef::named_nn(interface.type_name()),
                |_| FieldFuture::new(async { Ok(Some(FieldValue::NULL.with_type("MyObjA"))) }),
            ))
            .field(Field::new(
                "valueB",
                TypeRef::named_nn(interface.type_name()),
                |_| FieldFuture::new(async { Ok(Some(FieldValue::NULL.with_type("MyObjB"))) }),
            ));
        let schema = Schema::build(query.type_name(), None, None)
            .register(obj_a)
            .register(obj_b)
            .register(interface)
            .register(query)
            .finish()
            .unwrap();
        let query = r#"
        fragment A on MyObjA {
            b
        }
        fragment B on MyObjB {
            c
        }
        
        {
            valueA { __typename a ...A ...B }
            valueB { __typename a ...A ...B }
        }
        "#;
        assert_eq!(
            schema.execute(query).await.into_result().unwrap().data,
            value!({
                "valueA": {
                    "__typename": "MyObjA",
                    "a": 100,
                    "b": 200,
                },
                "valueB": {
                    "__typename": "MyObjB",
                    "a": 300,
                    "c": 400,
                }
            })
        );
    }
    #[tokio::test]
    async fn does_not_implement() {
        let obj_a = Object::new("MyObjA")
            .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| {
                FieldFuture::new(async { Ok(Some(Value::from(100))) })
            }))
            .field(Field::new("b", TypeRef::named(TypeRef::INT), |_| {
                FieldFuture::new(async { Ok(Some(Value::from(200))) })
            }));
        let interface = Interface::new("MyInterface")
            .field(InterfaceField::new("a", TypeRef::named(TypeRef::INT)));
        let query = Object::new("Query").field(Field::new(
            "valueA",
            TypeRef::named_nn(interface.type_name()),
            |_| FieldFuture::new(async { Ok(Some(FieldValue::NULL.with_type("MyObjA"))) }),
        ));
        let schema = Schema::build(query.type_name(), None, None)
            .register(obj_a)
            .register(interface)
            .register(query)
            .finish()
            .unwrap();
        let query = r#"
        {
            valueA { a }
        }
        "#;
        assert_eq!(
            schema.execute(query).await.into_result().unwrap_err(),
            vec![ServerError {
                message: "internal: object \"MyObjA\" does not implement interface \"MyInterface\""
                    .to_owned(),
                source: None,
                locations: vec![Pos {
                    column: 13,
                    line: 3
                }],
                path: vec![PathSegment::Field("valueA".to_owned())],
                extensions: None,
            }]
        );
    }
    #[tokio::test]
    async fn query_type_condition() {
        struct MyObjA;
        let obj_a = Object::new("MyObjA")
            .implement("MyInterface")
            .field(Field::new("a", TypeRef::named(TypeRef::INT), |_| {
                FieldFuture::new(async { Ok(Some(Value::from(100))) })
            }))
            .field(Field::new("b", TypeRef::named(TypeRef::INT), |_| {
                FieldFuture::new(async { Ok(Some(Value::from(200))) })
            }));
        let interface = Interface::new("MyInterface")
            .field(InterfaceField::new("a", TypeRef::named(TypeRef::INT)));
        let query = Object::new("Query");
        let query = query.field(Field::new(
            "valueA",
            TypeRef::named_nn(obj_a.type_name()),
            |_| FieldFuture::new(async { Ok(Some(FieldValue::owned_any(MyObjA))) }),
        ));
        let schema = Schema::build(query.type_name(), None, None)
            .register(obj_a)
            .register(interface)
            .register(query)
            .finish()
            .unwrap();
        let query = r#"
        {
            valueA { __typename
            b
            ... on MyInterface { a } }
        }
        "#;
        assert_eq!(
            schema.execute(query).await.into_result().unwrap().data,
            value!({
                "valueA": {
                    "__typename": "MyObjA",
                    "b": 200,
                    "a": 100,
                }
            })
        );
    }
}