use super::rules::ValidationRule;
use super::utils::ValidationError;
use super::validate::validate;
use super::validate::ValidationPlan;

#[cfg(test)]
pub static INTROSPECTION_SCHEMA: &str = "
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

scalar Boolean
scalar Float
scalar Int
scalar ID
scalar String

type Query {
  __schema: __Schema!
  __type(name: String!): __Type
}

type __Schema {
  types: [__Type!]!
  queryType: __Type!
  mutationType: __Type
  subscriptionType: __Type
  directives: [__Directive!]!
}

type __Type {
  kind: __TypeKind!
  name: String
  description: String

  # OBJECT and INTERFACE only
  fields(includeDeprecated: Boolean = false): [__Field!]

  # OBJECT only
  interfaces: [__Type!]

  # INTERFACE and UNION only
  possibleTypes: [__Type!]

  # ENUM only
  enumValues(includeDeprecated: Boolean = false): [__EnumValue!]

  # INPUT_OBJECT only
  inputFields: [__InputValue!]

  # NON_NULL and LIST only
  ofType: __Type
}

type __Field {
  name: String!
  description: String
  args: [__InputValue!]!
  type: __Type!
  isDeprecated: Boolean!
  deprecationReason: String
}

type __InputValue {
  name: String!
  description: String
  type: __Type!
  defaultValue: String
}

type __EnumValue {
  name: String!
  description: String
  isDeprecated: Boolean!
  deprecationReason: String
}

enum __TypeKind {
  SCALAR
  OBJECT
  INTERFACE
  UNION
  ENUM
  INPUT_OBJECT
  LIST
  NON_NULL
}

type __Directive {
  name: String!
  description: String
  locations: [__DirectiveLocation!]!
  args: [__InputValue!]!
}

enum __DirectiveLocation {
  QUERY
  MUTATION
  SUBSCRIPTION
  FIELD
  FRAGMENT_DEFINITION
  FRAGMENT_SPREAD
  INLINE_FRAGMENT
  SCHEMA
  SCALAR
  OBJECT
  FIELD_DEFINITION
  ARGUMENT_DEFINITION
  INTERFACE
  UNION
  ENUM
  ENUM_VALUE
  INPUT_OBJECT
  INPUT_FIELD_DEFINITION
}";

#[cfg(test)]
pub static TEST_SCHEMA: &str = "
interface Mammal {
  mother: Mammal
  father: Mammal
}
interface Pet {
  name(surname: Boolean): String
}
interface Canine implements Mammal {
  name(surname: Boolean): String
  mother: Canine
  father: Canine
}
enum DogCommand {
  SIT
  HEEL
  DOWN
}
type Dog implements Pet & Mammal & Canine {
  name(surname: Boolean): String
  nickname: String
  barkVolume: Int
  barks: Boolean
  doesKnowCommand(dogCommand: DogCommand): Boolean
  isHouseTrained(atOtherHomes: Boolean = true): Boolean
  isAtLocation(x: Int, y: Int): Boolean
  mother: Dog
  father: Dog
}
type Cat implements Pet {
  name(surname: Boolean): String
  nickname: String
  meows: Boolean
  meowsVolume: Int
  furColor: FurColor
}
union CatOrDog = Cat | Dog
type Human {
  name(surname: Boolean): String
  pets: [Pet]
  relatives: [Human]
}
enum FurColor {
  BROWN
  BLACK
  TAN
  SPOTTED
  NO_FUR
  UNKNOWN
}
input ComplexInput {
  requiredField: Boolean!
  nonNullField: Boolean! = false
  intField: Int
  stringField: String
  booleanField: Boolean
  stringListField: [String]
}
type ComplicatedArgs {
  # TODO List
  # TODO Coercion
  # TODO NotNulls
  intArgField(intArg: Int): String
  nonNullIntArgField(nonNullIntArg: Int!): String
  stringArgField(stringArg: String): String
  booleanArgField(booleanArg: Boolean): String
  enumArgField(enumArg: FurColor): String
  floatArgField(floatArg: Float): String
  idArgField(idArg: ID): String
  stringListArgField(stringListArg: [String]): String
  stringListNonNullArgField(stringListNonNullArg: [String!]): String
  complexArgField(complexArg: ComplexInput): String
  multipleReqs(req1: Int!, req2: Int!): String
  nonNullFieldWithDefault(arg: Int! = 0): String
  multipleOpts(opt1: Int = 0, opt2: Int = 0): String
  multipleOptAndReq(req1: Int!, req2: Int!, opt1: Int = 0, opt2: Int = 0): String
}
type QueryRoot {
  human(id: ID): Human
  dog: Dog
  cat: Cat
  pet: Pet
  catOrDog: CatOrDog
  complicatedArgs: ComplicatedArgs
}

type SubscriptionRoot {
  fieldB: String
}

type MutationRoot {
  fieldB: String
}

schema {
  subscription: SubscriptionRoot
  mutation: MutationRoot
  query: QueryRoot
}
directive @onField on FIELD
directive @onQuery on QUERY
directive @onMutation on MUTATION
directive @onSubscription on SUBSCRIPTION
directive @onFragmentDefinition on FRAGMENT_DEFINITION
directive @onFragmentSpread on FRAGMENT_SPREAD
directive @onInlineFragment on INLINE_FRAGMENT
directive @testDirective on FIELD | FRAGMENT_DEFINITION

# doesn't work see https://github.com/graphql-rust/graphql-parser/issues/60
# directive @onVariableDefinition on VARIABLE_DEFINITION

directive @repeatable repeatable on FIELD | FRAGMENT_DEFINITION
";

#[cfg(test)]
pub fn create_plan_from_rule(rule: Box<dyn ValidationRule>) -> ValidationPlan {
    let mut rules = Vec::new();
    rules.push(rule);

    ValidationPlan::from(rules)
}

#[cfg(test)]
pub fn get_messages(validation_errors: &[ValidationError]) -> Vec<&String> {
    validation_errors
        .iter()
        .map(|m| &m.message)
        .collect::<Vec<&String>>()
}

#[cfg(test)]
fn string_to_static_str(s: String) -> &'static str {
    Box::leak(s.into_boxed_str())
}

#[cfg(test)]
pub fn test_operation_with_schema<'a>(
    operation: &'a str,
    schema: &'a str,
    plan: &'a ValidationPlan,
) -> Vec<ValidationError> {
    let schema_clone = string_to_static_str(schema.to_string() + INTROSPECTION_SCHEMA);
    let schema_ast = crate::parser::parse_schema(schema_clone).expect("Failed to parse schema");

    let operation_ast = crate::parser::parse_query(operation).unwrap().into_static();

    validate(&schema_ast, &operation_ast, plan)
}
