use std::collections::HashMap;

use super::ValidationRule;
use crate::ast::{AstNodeWithName, OperationVisitor, OperationVisitorContext};
use crate::static_graphql::query::*;
use crate::validation::utils::{ValidationError, ValidationErrorContext};

/// Unique operation names
///
/// A GraphQL document is only valid if all defined operations have unique names.
///
/// See https://spec.graphql.org/draft/#sec-Operation-Name-Uniqueness
pub struct UniqueOperationNames<'doc> {
    findings_counter: HashMap<&'doc str, i32>,
}

impl<'doc> OperationVisitor<'doc, ValidationErrorContext> for UniqueOperationNames<'doc> {
    fn enter_operation_definition(
        &mut self,
        _: &mut OperationVisitorContext,
        _: &mut ValidationErrorContext,
        operation_definition: &'doc OperationDefinition,
    ) {
        if let Some(name) = operation_definition.node_name() {
            self.store_finding(name);
        }
    }

    fn leave_document(
        &mut self,
        _: &mut OperationVisitorContext,
        user_context: &mut ValidationErrorContext,
        _: &Document,
    ) {
        self.findings_counter
            .iter()
            .filter(|(_, count)| **count > 1)
            .for_each(|(name, _)| {
                user_context.report_error(ValidationError {
                    error_code: self.error_code(),
                    message: format!("There can be only one operation named \"{}\".", name),
                    locations: vec![],
                });
            });
    }
}

impl Default for UniqueOperationNames<'_> {
    fn default() -> Self {
        Self::new()
    }
}

impl<'doc> UniqueOperationNames<'doc> {
    pub fn new() -> Self {
        Self {
            findings_counter: HashMap::new(),
        }
    }

    fn store_finding(&mut self, name: &'doc str) {
        let value = *self.findings_counter.entry(name).or_insert(0);
        self.findings_counter.insert(name, value + 1);
    }
}

impl ValidationRule for UniqueOperationNames<'_> {
    fn error_code(&self) -> &'static str {
        "UniqueOperationNames"
    }

    fn visitor<'doc>(&self) -> super::ValidationVisitor<'doc> {
        Box::new(UniqueOperationNames::new())
    }
}

#[test]
fn no_operations() {
    use crate::validation::test_utils::*;

    let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
    let errors = test_operation_with_schema(
        "fragment fragA on Type {
          field
        }",
        TEST_SCHEMA,
        &mut plan,
    );

    assert_eq!(get_messages(&errors).len(), 0);
}

#[test]
fn one_anon_operation() {
    use crate::validation::test_utils::*;

    let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
    let errors = test_operation_with_schema(
        "{
          field
        }",
        TEST_SCHEMA,
        &mut plan,
    );

    let messages = get_messages(&errors);
    assert_eq!(messages.len(), 0);
}

#[test]
fn one_named_operation() {
    use crate::validation::test_utils::*;

    let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
    let errors = test_operation_with_schema(
        "query Foo {
          field
        }",
        TEST_SCHEMA,
        &mut plan,
    );

    let messages = get_messages(&errors);
    assert_eq!(messages.len(), 0);
}

#[test]
fn multiple_operations() {
    use crate::validation::test_utils::*;

    let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
    let errors = test_operation_with_schema(
        "query Foo {
          field
        }
        query Bar {
          field
        }",
        TEST_SCHEMA,
        &mut plan,
    );

    let messages = get_messages(&errors);
    assert_eq!(messages.len(), 0);
}

#[test]
fn multiple_operations_of_different_types() {
    use crate::validation::test_utils::*;

    let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
    let errors = test_operation_with_schema(
        "query Foo {
          field
        }
        mutation Bar {
          field
        }
        subscription Baz {
          field
        }",
        TEST_SCHEMA,
        &mut plan,
    );

    let messages = get_messages(&errors);
    assert_eq!(messages.len(), 0);
}

#[test]
fn fragment_and_operation_named_the_same() {
    use crate::validation::test_utils::*;

    let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
    let errors = test_operation_with_schema(
        "query Foo {
          ...Foo
        }
        fragment Foo on Type {
          field
        }",
        TEST_SCHEMA,
        &mut plan,
    );

    let messages = get_messages(&errors);
    assert_eq!(messages.len(), 0);
}

#[test]
fn multiple_operations_of_same_name() {
    use crate::validation::test_utils::*;

    let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
    let errors = test_operation_with_schema(
        "query Foo {
          fieldA
        }
        query Foo {
          fieldB
        }",
        TEST_SCHEMA,
        &mut plan,
    );

    let messages = get_messages(&errors);
    assert_eq!(messages.len(), 1);
    assert_eq!(
        messages,
        vec!["There can be only one operation named \"Foo\".",]
    );
}

#[test]
fn multiple_ops_of_same_name_of_different_types_mutation() {
    use crate::validation::test_utils::*;

    let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
    let errors = test_operation_with_schema(
        "query Foo {
          fieldA
        }
        mutation Foo {
          fieldB
        }",
        TEST_SCHEMA,
        &mut plan,
    );

    let messages = get_messages(&errors);
    assert_eq!(messages.len(), 1);
    assert_eq!(
        messages,
        vec!["There can be only one operation named \"Foo\".",]
    );
}

#[test]
fn multiple_ops_of_same_name_of_different_types_subscription() {
    use crate::validation::test_utils::*;

    let mut plan = create_plan_from_rule(Box::new(UniqueOperationNames::new()));
    let errors = test_operation_with_schema(
        "query Foo {
          fieldA
        }
        subscription Foo {
          fieldB
        }",
        TEST_SCHEMA,
        &mut plan,
    );

    let messages = get_messages(&errors);
    assert_eq!(messages.len(), 1);
    assert_eq!(
        messages,
        vec!["There can be only one operation named \"Foo\".",]
    );
}
