# Copyright 2017-present Kensho Technologies, LLC.
import string
from typing import cast
import unittest

from graphql import parse
from graphql.utilities.build_ast_schema import build_ast_schema
import six

from ..compiler.compiler_frontend import graphql_to_ir
from ..exceptions import GraphQLCompilationError, GraphQLParsingError, GraphQLValidationError
from ..schema import TypeEquivalenceHintsType
from .test_helpers import get_schema


class IrGenerationErrorTests(unittest.TestCase):
    """Ensure illegal inputs raise proper exceptions."""

    def setUp(self) -> None:
        """Initialize the test schema once for all tests, and disable max diff limits."""
        self.maxDiff = None
        self.schema = get_schema()

    def test_repeated_field_name(self) -> None:
        repeated_property_field = """{
            Animal {
                name @output(out_name: "name")
                name
            }
        }"""

        repeated_property_field_with_directives = """{
            Animal {
                name @output(out_name: "name")
                name @filter(op_name: "=", value: ["$wanted"])
            }
        }"""

        repeated_vertex_field = """{
            Animal {
                out_Animal_ParentOf {
                    name @output(out_name: "child_name")
                }
                out_Animal_ParentOf {
                    uuid @output(out_name: "child_uuid")
                }
            }
        }"""

        for graphql in (
            repeated_property_field,
            repeated_property_field_with_directives,
            repeated_vertex_field,
        ):
            with self.assertRaises(GraphQLCompilationError):
                graphql_to_ir(self.schema, graphql)

    def test_output_source_directive_constraints(self) -> None:
        output_source_not_on_last_vertex_element = """{
            Animal @filter(op_name: "name_or_alias", value: ["$animal_name"]) {
                out_Animal_ParentOf @output_source {
                    name @output(out_name: "parent_name")
                }
                out_Animal_FedAt {
                    name @output(out_name: "event_name")
                    event_date @output(out_name: "event_date")
                }
            }
        }"""

        output_source_on_non_vertex_element = """{
            Animal @filter(op_name: "name_or_alias", value: ["$animal_name"]) {
                out_Animal_FedAt {
                    name @output(out_name: "event_name") @output_source
                    event_date @output(out_name: "event_date")
                }
            }
        }"""

        multiple_output_sources = """{
            Animal @filter(op_name: "name_or_alias", value: ["$animal_name"]) {
                out_Animal_FedAt @output_source {
                    name @output(out_name: "event_name") @output_source
                    event_date @output(out_name: "event_date")
                }
            }
        }"""

        output_source_on_optional_vertex = """{
            Animal @filter(op_name: "name_or_alias", value: ["$animal_name"]) {
                out_Animal_FedAt @output_source @optional {
                    name @output(out_name: "event_name")
                    event_date @output(out_name: "event_date")
                }
            }
        }"""

        output_source_on_fold_vertex = """{
            Animal @filter(op_name: "name_or_alias", value: ["$animal_name"]) {
                out_Animal_FedAt @output_source @fold {
                    name @output(out_name: "event_name")
                    event_date @output(out_name: "event_date")
                }
            }
        }"""

        for graphql in (
            output_source_not_on_last_vertex_element,
            output_source_on_non_vertex_element,
            multiple_output_sources,
            output_source_on_optional_vertex,
            output_source_on_fold_vertex,
        ):
            with self.assertRaises(GraphQLCompilationError):
                graphql_to_ir(self.schema, graphql)

    def test_optional_directive_constraints(self) -> None:
        optional_on_property_field = """{
            Animal @filter(op_name: "name_or_alias", value: ["$animal_name"]) {
                out_Animal_ParentOf {
                    name @output(out_name: "parent_name") @optional
                }
            }
        }"""

        optional_on_root_vertex = """{
            Animal @optional {
                name @output(out_name: "uuid")
            }
        }"""

        output_source_inside_optional_block = """{
            Animal {
                out_Animal_ParentOf @optional {
                    out_Animal_FedAt @output_source {
                        uuid @output(out_name: "uuid")
                    }
                }
            }
        }"""

        recurse_traversal_inside_optional_block = """{
            Animal {
                out_Animal_ParentOf @optional {
                    out_Animal_FedAt @recurse(depth: 3) {
                        uuid @output(out_name: "uuid")
                    }
                }
            }
        }"""

        fold_traversal_inside_optional_block = """{
            Animal {
                out_Animal_ParentOf @optional {
                    out_Animal_FedAt @fold {
                        uuid @output(out_name: "uuid")
                    }
                }
            }
        }"""

        optional_on_output_source_vertex_field = """{
            Animal {
                out_Animal_ParentOf @optional @output_source {
                    uuid @output(out_name: "uuid")
                }
            }
        }"""

        optional_on_recurse_vertex_field = """{
            Animal {
                out_Animal_ParentOf @optional @recurse(depth: 3) {
                    uuid @output(out_name: "uuid")
                }
            }
        }"""

        optional_on_fold_vertex_field = """{
            Animal {
                out_Animal_ParentOf @optional @fold {
                    uuid @output(out_name: "uuid")
                }
            }
        }"""

        for graphql in (
            optional_on_property_field,
            optional_on_root_vertex,
            output_source_inside_optional_block,
            recurse_traversal_inside_optional_block,
            fold_traversal_inside_optional_block,
            optional_on_output_source_vertex_field,
            optional_on_recurse_vertex_field,
            optional_on_fold_vertex_field,
        ):
            with self.assertRaises(GraphQLCompilationError):
                graphql_to_ir(self.schema, graphql)

    def test_starts_with_op_filter_missing_value_argument(self) -> None:
        graphql_input = """{
            Animal {
                name @filter(op_name: "starts_with")
                     @output(out_name: "animal_name")
            }
        }"""

        with self.assertRaises(GraphQLValidationError):
            graphql_to_ir(self.schema, graphql_input)

    def test_fold_directive_constraints(self) -> None:
        fold_on_property_field = """{
            Animal @filter(op_name: "name_or_alias", value: ["$animal_name"]) {
                out_Animal_ParentOf {
                    name @output(out_name: "parent_name") @fold
                }
            }
        }"""

        fold_on_root_vertex = """{
            Animal @fold {
                name @output(out_name: "uuid")
            }
        }"""

        multi_level_outputs_inside_fold_block = """{
            Animal {
                out_Animal_ParentOf @fold {
                    uuid @output(out_name: "uuid")
                    out_Animal_FedAt {
                        uuid @output(out_name: "uuid")
                    }
                }
            }
        }"""

        traversal_inside_fold_block_after_output = """{
            Animal {
                out_Animal_ParentOf @fold {
                    in_Animal_ParentOf {
                        uuid @output(out_name: "uuid")
                        out_Animal_FedAt {
                            uuid
                        }
                    }
                }
            }
        }"""

        no_outputs_or_filters_inside_fold_block = """{
            Animal {
                uuid @output(out_name: "uuid")
                out_Animal_ParentOf @fold {
                    name
                }
            }
        }"""

        list_output_inside_fold_block = """{
            Animal {
                uuid @output(out_name: "uuid")
                out_Animal_ParentOf @fold {
                    alias @output(out_name: "disallowed_folded_list_output")
                }
            }
        }"""

        fold_within_fold = """{
            Animal {
                out_Animal_ParentOf @fold {
                    out_Animal_FedAt @fold {
                        uuid @output(out_name: "uuid")
                    }
                }
            }
        }"""

        optional_within_fold = """{
            Animal {
                out_Animal_ParentOf @fold {
                    out_Animal_FedAt @optional {
                        uuid @output(out_name: "uuid")
                    }
                }
            }
        }"""

        recurse_within_fold = """{
            Animal {
                out_Animal_ParentOf @fold {
                    in_Animal_ParentOf @recurse(depth: 2) {
                        uuid @output(out_name: "uuid")
                    }
                }
            }
        }"""

        output_source_within_fold = """{
            Animal {
                out_Animal_ParentOf @fold {
                    out_Animal_FedAt @output_source {