use crate::parser::types::Field;
use crate::validation::suggestion::make_suggestion;
use crate::validation::visitor::{Visitor, VisitorContext};
use crate::{registry, Positioned};

#[derive(Default)]
pub struct FieldsOnCorrectType;

impl<'a> Visitor<'a> for FieldsOnCorrectType {
    fn enter_field(&mut self, ctx: &mut VisitorContext<'a>, field: &'a Positioned<Field>) {
        if let Some(parent_type) = ctx.parent_type() {
            if let Some(registry::MetaType::Union { .. })
            | Some(registry::MetaType::Interface { .. }) = ctx.parent_type()
            {
                if field.node.name.node == "__typename" {
                    return;
                }
            }

            if parent_type
                .fields()
                .and_then(|fields| fields.get(field.node.name.node.as_str()))
                .is_none()
                && !field
                    .node
                    .directives
                    .iter()
                    .any(|directive| directive.node.name.node == "ifdef")
            {
                ctx.report_error(
                    vec![field.pos],
                    format!(
                        "Unknown field \"{}\" on type \"{}\".{}",
                        field.node.name,
                        parent_type.name(),
                        make_suggestion(
                            " Did you mean",
                            parent_type
                                .fields()
                                .iter()
                                .map(|fields| fields.keys())
                                .flatten()
                                .map(String::as_str),
                            &field.node.name.node,
                        )
                        .unwrap_or_default()
                    ),
                );
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    pub fn factory() -> FieldsOnCorrectType {
        FieldsOnCorrectType
    }

    #[test]
    fn selection_on_object() {
        expect_passes_rule!(
            factory,
            r#"
          fragment objectFieldSelection on Dog {
            __typename
            name
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn aliased_selection_on_object() {
        expect_passes_rule!(
            factory,
            r#"
          fragment aliasedObjectFieldSelection on Dog {
            tn : __typename
            otherName : name
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn selection_on_interface() {
        expect_passes_rule!(
            factory,
            r#"
          fragment interfaceFieldSelection on Pet {
            __typename
            name
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn aliased_selection_on_interface() {
        expect_passes_rule!(
            factory,
            r#"
          fragment interfaceFieldSelection on Pet {
            otherName : name
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn lying_alias_selection() {
        expect_passes_rule!(
            factory,
            r#"
          fragment lyingAliasSelection on Dog {
            name : nickname
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn ignores_unknown_type() {
        expect_passes_rule!(
            factory,
            r#"
          fragment unknownSelection on UnknownType {
            unknownField
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn nested_unknown_fields() {
        expect_fails_rule!(
            factory,
            r#"
          fragment typeKnownAgain on Pet {
            unknown_pet_field {
              ... on Cat {
                unknown_cat_field
              }
            }
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn unknown_field_on_fragment() {
        expect_fails_rule!(
            factory,
            r#"
          fragment fieldNotDefined on Dog {
            meowVolume
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn ignores_deeply_unknown_field() {
        expect_fails_rule!(
            factory,
            r#"
          fragment deepFieldNotDefined on Dog {
            unknown_field {
              deeper_unknown_field
            }
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn unknown_subfield() {
        expect_fails_rule!(
            factory,
            r#"
          fragment subFieldNotDefined on Human {
            pets {
              unknown_field
            }
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn unknown_field_on_inline_fragment() {
        expect_fails_rule!(
            factory,
            r#"
          fragment fieldNotDefined on Pet {
            ... on Dog {
              meowVolume
            }
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn unknown_aliased_target() {
        expect_fails_rule!(
            factory,
            r#"
          fragment aliasedFieldTargetNotDefined on Dog {
            volume : mooVolume
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn unknown_aliased_lying_field_target() {
        expect_fails_rule!(
            factory,
            r#"
          fragment aliasedLyingFieldTargetNotDefined on Dog {
            barkVolume : kawVolume
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn not_defined_on_interface() {
        expect_fails_rule!(
            factory,
            r#"
          fragment notDefinedOnInterface on Pet {
            tailLength
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn defined_in_concrete_types_but_not_interface() {
        expect_fails_rule!(
            factory,
            r#"
          fragment definedOnImplementorsButNotInterface on Pet {
            nickname
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn meta_field_on_union() {
        expect_passes_rule!(
            factory,
            r#"
          fragment definedOnImplementorsButNotInterface on Pet {
            __typename
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn fields_on_union() {
        expect_fails_rule!(
            factory,
            r#"
          fragment definedOnImplementorsQueriedOnUnion on CatOrDog {
            name
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn typename_on_union() {
        expect_passes_rule!(
            factory,
            r#"
          fragment objectFieldSelection on Pet {
            __typename
            ... on Dog {
              name
            }
            ... on Cat {
              name
            }
          }
          { __typename }
        "#,
        );
    }

    #[test]
    fn valid_field_in_inline_fragment() {
        expect_passes_rule!(
            factory,
            r#"
          fragment objectFieldSelection on Pet {
            ... on Dog {
              name
            }
            ... {
              name
            }
          }
          { __typename }
        "#,
        );
    }
}
