use graphgate_planner::IntrospectionSelectionSet;
use graphgate_schema::{ComposedSchema, MetaType, TypeKind};
use once_cell::sync::Lazy;
use parser::types::{BaseType, Type};
use value::{ConstValue, Name};

use super::enum_value::IntrospectionEnumValue;
use super::field::IntrospectionField;
use super::input_value::IntrospectionInputValue;
use super::resolver::{is_include_deprecated, resolve_obj, Resolver};

static SCALAR: Lazy<Name> = Lazy::new(|| Name::new("SCALAR"));
static OBJECT: Lazy<Name> = Lazy::new(|| Name::new("OBJECT"));
static INTERFACE: Lazy<Name> = Lazy::new(|| Name::new("INTERFACE"));
static UNION: Lazy<Name> = Lazy::new(|| Name::new("UNION"));
static ENUM: Lazy<Name> = Lazy::new(|| Name::new("ENUM"));
static INPUT_OBJECT: Lazy<Name> = Lazy::new(|| Name::new("INPUT_OBJECT"));
static NON_NULL: Lazy<Name> = Lazy::new(|| Name::new("NON_NULL"));
static LIST: Lazy<Name> = Lazy::new(|| Name::new("LIST"));

pub enum IntrospectionType<'a> {
    Named(&'a MetaType),
    NonNull(Box<IntrospectionType<'a>>),
    List(Box<IntrospectionType<'a>>),
}

impl<'a> IntrospectionType<'a> {
    pub fn new(ty: &'a Type, schema: &'a ComposedSchema) -> Self {
        if ty.nullable {
            Self::new_base(&ty.base, schema)
        } else {
            IntrospectionType::NonNull(Box::new(Self::new_base(&ty.base, schema)))
        }
    }

    fn new_base(ty: &'a BaseType, schema: &'a ComposedSchema) -> Self {
        match ty {
            BaseType::Named(name) => IntrospectionType::Named(
                schema
                    .types
                    .get(name)
                    .expect("The query validator should find this error."),
            ),
            BaseType::List(ty) => IntrospectionType::List(Box::new(Self::new(ty, schema))),
        }
    }
}

impl<'a> Resolver for IntrospectionType<'a> {
    fn resolve(
        &self,
        selection_set: &IntrospectionSelectionSet,
        schema: &ComposedSchema,
    ) -> ConstValue {
        resolve_obj(selection_set, |name, field| match name {
            "kind" => match self {
                Self::Named(ty) => match ty.kind {
                    TypeKind::Scalar => ConstValue::Enum(SCALAR.clone()),
                    TypeKind::Object => ConstValue::Enum(OBJECT.clone()),
                    TypeKind::Interface => ConstValue::Enum(INTERFACE.clone()),
                    TypeKind::Union => ConstValue::Enum(UNION.clone()),
                    TypeKind::Enum => ConstValue::Enum(ENUM.clone()),
                    TypeKind::InputObject => ConstValue::Enum(INPUT_OBJECT.clone()),
                },
                Self::NonNull(_) => ConstValue::Enum(NON_NULL.clone()),
                Self::List(_) => ConstValue::Enum(LIST.clone()),
            },
            "name" => match self {
                Self::Named(ty) => ConstValue::String(ty.name.to_string()),
                _ => ConstValue::Null,
            },
            "description" => match self {
                Self::Named(ty) => ty
                    .description
                    .as_ref()
                    .map(|description| ConstValue::String(description.clone()))
                    .unwrap_or_default(),
                _ => ConstValue::Null,
            },
            "fields" => match self {
                Self::Named(ty)
                    if ty.kind == TypeKind::Object || ty.kind == TypeKind::Interface =>
                {
                    ConstValue::List(
                        ty.fields
                            .values()
                            .filter(|item| !item.name.starts_with("__"))
                            .filter(|item| {
                                if is_include_deprecated(&field.arguments) {
                                    true
                                } else {
                                    !item.deprecation.is_deprecated()
                                }
                            })
                            .map(|f| IntrospectionField(f).resolve(&field.selection_set, schema))
                            .collect(),
                    )
                }
                _ => ConstValue::Null,
            },
            "interfaces" => match self {
                Self::Named(ty) if ty.kind == TypeKind::Object => ConstValue::List(
                    ty.implements
                        .iter()
                        .map(|name| {
                            IntrospectionType::Named(
                                schema
                                    .types
                                    .get(name)
                                    .expect("The query validator should find this error."),
                            )
                            .resolve(&field.selection_set, schema)
                        })
                        .collect(),
                ),
                _ => ConstValue::Null,
            },
            "possibleTypes" => match self {
                Self::Named(ty) if ty.kind == TypeKind::Interface || ty.kind == TypeKind::Union => {
                    ConstValue::List(
                        ty.possible_types
                            .iter()
                            .map(|name| {
                                IntrospectionType::Named(
                                    schema
                                        .types
                                        .get(name)
                                        .expect("The query validator should find this error."),
                                )
                                .resolve(&field.selection_set, schema)
                            })
                            .collect(),
                    )
                }
                _ => ConstValue::Null,
            },
            "enumValues" => match self {
                Self::Named(ty) if ty.kind == TypeKind::Enum => ConstValue::List(
                    ty.enum_values
                        .values()
                        .filter(|item| {
                            if is_include_deprecated(&field.arguments) {
                                true
                            } else {
                                !item.deprecation.is_deprecated()
                            }
                        })
                        .map(|value| {
                            IntrospectionEnumValue(value).resolve(&field.selection_set, schema)
                        })
                        .collect(),
                ),
                _ => ConstValue::Null,
            },
            "inputFields" => match self {
                Self::Named(ty) if ty.kind == TypeKind::InputObject => ConstValue::List(
                    ty.input_fields
                        .values()
                        .map(|value| {
                            IntrospectionInputValue(value).resolve(&field.selection_set, schema)
                        })
                        .collect(),
                ),
                _ => ConstValue::Null,
            },
            "ofType" => match self {
                Self::Named(_) => ConstValue::Null,
                Self::List(ty) | Self::NonNull(ty) => ty.resolve(&field.selection_set, schema),
            },
            _ => ConstValue::Null,
        })
    }
}
