package validator_test

import (
	"os"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/vektah/gqlparser/v2"
	"github.com/vektah/gqlparser/v2/ast"
	"github.com/vektah/gqlparser/v2/validator"
)

func TestValidateVars(t *testing.T) {
	schema := gqlparser.MustLoadSchema(&ast.Source{
		Name:  "vars.graphql",
		Input: mustReadFile("./testdata/vars.graphql"),
	})

	t.Run("undefined variable", func(t *testing.T) {
		t.Run("without default", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query($id: Int!) { intArg(i: $id) }`)
			_, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.EqualError(t, gerr, "input: variable.id must be defined")
		})

		t.Run("nil in required value", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query($id: Int!) { intArg(i: $id) }`)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"id": nil,
				},
			)
			require.EqualError(t, gerr, "input: variable.id cannot be null")
		})

		t.Run("with default", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query($id: Int! = 1) { intArg(i: $id) }`)
			vars, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.NoError(t, gerr)
			require.EqualValues(t, 1, vars["id"])
		})

		t.Run("with union", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query($id: Int! = 1) { intArg(i: $id) }`)
			vars, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.NoError(t, gerr)
			require.EqualValues(t, 1, vars["id"])
		})
	})

	t.Run("input object", func(t *testing.T) {
		t.Run("non object", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": "hello",
				},
			)
			require.EqualError(t, gerr, "input: variable.var must be a InputType, not a string")
		})

		t.Run("defaults", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: InputType! = {name: "foo"}) { structArg(i: $var) }`,
			)
			vars, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.NoError(t, gerr)
			require.EqualValues(t, map[string]any{"name": "foo"}, vars["var"])
		})

		t.Run("valid value", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name": "foobar",
					},
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(t, map[string]any{"name": "foobar"}, vars["var"])
		})

		t.Run("null object field", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name":     "foobar",
						"nullName": nil,
					},
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(
				t,
				map[string]any{"name": "foobar", "nullName": nil},
				vars["var"],
			)
		})

		t.Run("missing required values", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{},
				},
			)
			require.EqualError(t, gerr, "input: variable.var.name must be defined")
		})

		t.Run("null required field", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name": nil,
					},
				},
			)
			require.EqualError(t, gerr, "input: variable.var.name cannot be null")
		})

		t.Run("null embedded input object", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name":         "foo",
						"nullEmbedded": nil,
					},
				},
			)
			require.NoError(t, gerr)
		})

		t.Run("unknown field", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name":    "foobar",
						"foobard": true,
					},
				},
			)
			require.EqualError(t, gerr, "input: variable.var.foobard unknown field")
		})

		t.Run("unknown __typefield", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name":       "foobar",
						"__typename": "InputType",
					},
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(
				t,
				map[string]any{"__typename": "InputType", "name": "foobar"},
				vars["var"],
			)
		})

		t.Run("enum input object", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name": "foobar",
						"enum": "A",
					},
				},
			)
			require.NoError(t, gerr)
		})

		t.Run("unknown enum value input object", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name": "foobar",
						"enum": "B",
					},
				},
			)
			require.EqualError(t, gerr, "input: variable.var.enum B is not a valid Enum")
		})
	})

	t.Run("array", func(t *testing.T) {
		t.Run("non-null object value should be coerced to an array", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: [InputType!]) { arrayArg(i: $var) }`,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{"name": "hello"},
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(t, []map[string]any{{"name": "hello"}}, vars["var"])
		})

		t.Run("non-null int value should be coerced to an array", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query foo($var: [Int!]) { intArrayArg(i: $var) }`)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": 5,
				},
			)
			require.NoError(t, gerr)
			expected := []int{5}
			require.EqualValues(t, expected, vars["var"])
		})

		t.Run("non-null int deep value should be coerced to an array", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: [CustomType]) { typeArrayArg(i: $var) }`,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []map[string]any{{"and": 5}},
				},
			)
			require.NoError(t, gerr)
			expected := []map[string]any{{"and": []int{5}}}
			require.EqualValues(t, expected, vars["var"])
		})

		t.Run("defaults", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: [InputType!] = [{name: "foo"}]) { arrayArg(i: $var) }`,
			)
			vars, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.NoError(t, gerr)
			require.EqualValues(t, []any{map[string]any{
				"name": "foo",
			}}, vars["var"])
		})

		t.Run("valid value", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: [InputType!]) { arrayArg(i: $var) }`,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []any{map[string]any{
						"name": "foo",
					}},
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(t, []any{map[string]any{
				"name": "foo",
			}}, vars["var"])
		})

		t.Run("null nested list value", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: [[InputType!]]) { nestedArrayArg(i: $var) }`,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []any{
						[]any{
							map[string]any{
								"name": "foo",
							},
						},
						nil,
					},
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(t, []any{
				[]any{
					map[string]any{
						"name": "foo",
					},
				},
				nil,
			}, vars["var"])
		})

		t.Run("null element value", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: [InputType!]) { arrayArg(i: $var) }`,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []any{nil},
				},
			)
			require.EqualError(t, gerr, "input: variable.var[0] cannot be null")
		})

		t.Run("missing required values", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: [InputType!]) { arrayArg(i: $var) }`,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []any{map[string]any{}},
				},
			)
			require.EqualError(t, gerr, "input: variable.var[0].name must be defined")
		})
		t.Run("invalid variable paths", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var1: InputType!, $var2: InputType!) { a:structArg(i: $var1) b:structArg(i: $var2) }`,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var1": map[string]any{
						"name": "foobar",
					},
					"var2": map[string]any{
						"nullName": "foobar",
					},
				},
			)
			require.EqualError(t, gerr, "input: variable.var2.name must be defined")
		})
	})

	t.Run("Scalars", func(t *testing.T) {
		t.Run("String -> String", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query foo($var: String!) { stringArg(i: $var) }`)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": "asdf",
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(t, "asdf", vars["var"])
		})

		t.Run("Int -> String", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query foo($var: String!) { stringArg(i: $var) }`)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": 1,
				},
			)
			require.EqualError(t, gerr, "input: variable.var cannot use int as String")
		})

		t.Run("Nil -> String", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query foo($var: String!) { stringArg(i: $var) }`)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": nil,
				},
			)
			require.EqualError(t, gerr, "input: variable.var cannot be null")
		})

		t.Run("Undefined -> String!", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query foo($var: String!) { stringArg(i: $var) }`)
			_, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.EqualError(t, gerr, "input: variable.var must be defined")
		})

		t.Run("Undefined -> Int", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query foo($var: Int) { optionalIntArg(i: $var) }`)
			_, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.NoError(t, gerr)
		})

		t.Run("Json Number -> Int", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query foo($var: Int) { optionalIntArg(i: $var) }`)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": 10,
				},
			)
			require.NoError(t, gerr)
			require.Equal(t, 10, vars["var"])
		})

		t.Run("Json Number -> Float", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query foo($var: Float!) { floatArg(i: $var) }`)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": 10.2,
				},
			)
			require.NoError(t, gerr)
			require.Equal(t, 10.2, vars["var"])
		})

		t.Run("Nil -> Int", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query foo($var: Int) { optionalIntArg(i: $var) }`)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": nil,
				},
			)
			require.NoError(t, gerr)
			require.Nil(t, vars["var"])
		})

		t.Run("Bool -> Int", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query foo($var: Int!) { intArg(i: $var) }`)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": true,
				},
			)
			require.EqualError(t, gerr, "input: variable.var cannot use bool as Int")
		})
	})

	t.Run("Int Array", func(t *testing.T) {
		t.Run("Array with null", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(schema, `query foo($var: [Int]) { intArrayArg(i: $var) }`)
			a := 1
			b := 2

			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []*int{&a, &b, nil},
				},
			)
			require.NoError(t, gerr)
		})
	})

	t.Run("String Array", func(t *testing.T) {
		t.Run("Array with null", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: [String]) { stringArrayArg(i: $var) }`,
			)
			a := "1"
			b := "2"

			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []*string{&a, &b, nil},
				},
			)
			require.NoError(t, gerr)
		})
	})

	t.Run("Boolean Array", func(t *testing.T) {
		t.Run("Array with null", func(t *testing.T) {
			//nolint:staticcheck
			q := gqlparser.MustLoadQuery(
				schema,
				`query foo($var: [Boolean]) { boolArrayArg(i: $var) }`,
			)
			a := true
			b := false

			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []*bool{&a, &b, nil},
				},
			)
			require.NoError(t, gerr)
		})
	})
}

func TestValidateVarsWithRules(t *testing.T) {
	schema := gqlparser.MustLoadSchema(&ast.Source{
		Name:  "vars.graphql",
		Input: mustReadFile("./testdata/vars.graphql"),
	})

	t.Run("undefined variable", func(t *testing.T) {
		t.Run("without default", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query($id: Int!) { intArg(i: $id) }`,
				nil,
			)
			_, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.EqualError(t, gerr, "input: variable.id must be defined")
		})

		t.Run("nil in required value", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query($id: Int!) { intArg(i: $id) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"id": nil,
				},
			)
			require.EqualError(t, gerr, "input: variable.id cannot be null")
		})

		t.Run("with default", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query($id: Int! = 1) { intArg(i: $id) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.NoError(t, gerr)
			require.EqualValues(t, 1, vars["id"])
		})

		t.Run("with union", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query($id: Int! = 1) { intArg(i: $id) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.NoError(t, gerr)
			require.EqualValues(t, 1, vars["id"])
		})
	})

	t.Run("input object", func(t *testing.T) {
		t.Run("non object", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": "hello",
				},
			)
			require.EqualError(t, gerr, "input: variable.var must be a InputType, not a string")
		})

		t.Run("defaults", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: InputType! = {name: "foo"}) { structArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.NoError(t, gerr)
			require.EqualValues(t, map[string]any{"name": "foo"}, vars["var"])
		})

		t.Run("valid value", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name": "foobar",
					},
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(t, map[string]any{"name": "foobar"}, vars["var"])
		})

		t.Run("null object field", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name":     "foobar",
						"nullName": nil,
					},
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(
				t,
				map[string]any{"name": "foobar", "nullName": nil},
				vars["var"],
			)
		})

		t.Run("missing required values", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{},
				},
			)
			require.EqualError(t, gerr, "input: variable.var.name must be defined")
		})

		t.Run("null required field", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name": nil,
					},
				},
			)
			require.EqualError(t, gerr, "input: variable.var.name cannot be null")
		})

		t.Run("null embedded input object", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name":         "foo",
						"nullEmbedded": nil,
					},
				},
			)
			require.NoError(t, gerr)
		})

		t.Run("unknown field", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name":    "foobar",
						"foobard": true,
					},
				},
			)
			require.EqualError(t, gerr, "input: variable.var.foobard unknown field")
		})

		t.Run("unknown __typefield", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name":       "foobar",
						"__typename": "InputType",
					},
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(
				t,
				map[string]any{"__typename": "InputType", "name": "foobar"},
				vars["var"],
			)
		})

		t.Run("enum input object", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name": "foobar",
						"enum": "A",
					},
				},
			)
			require.NoError(t, gerr)
		})

		t.Run("unknown enum value input object", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: InputType!) { structArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{
						"name": "foobar",
						"enum": "B",
					},
				},
			)
			require.EqualError(t, gerr, "input: variable.var.enum B is not a valid Enum")
		})
	})

	t.Run("array", func(t *testing.T) {
		t.Run("non-null object value should be coerced to an array", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: [InputType!]) { arrayArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": map[string]any{"name": "hello"},
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(t, []map[string]any{{"name": "hello"}}, vars["var"])
		})

		t.Run("non-null int value should be coerced to an array", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: [Int!]) { intArrayArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": 5,
				},
			)
			require.NoError(t, gerr)
			expected := []int{5}
			require.EqualValues(t, expected, vars["var"])
		})

		t.Run("non-null int deep value should be coerced to an array", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: [CustomType]) { typeArrayArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []map[string]any{{"and": 5}},
				},
			)
			require.NoError(t, gerr)
			expected := []map[string]any{{"and": []int{5}}}
			require.EqualValues(t, expected, vars["var"])
		})

		t.Run("defaults", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: [InputType!] = [{name: "foo"}]) { arrayArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.NoError(t, gerr)
			require.EqualValues(t, []any{map[string]any{
				"name": "foo",
			}}, vars["var"])
		})

		t.Run("valid value", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: [InputType!]) { arrayArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []any{map[string]any{
						"name": "foo",
					}},
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(t, []any{map[string]any{
				"name": "foo",
			}}, vars["var"])
		})

		t.Run("null element value", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: [InputType!]) { arrayArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []any{nil},
				},
			)
			require.EqualError(t, gerr, "input: variable.var[0] cannot be null")
		})

		t.Run("missing required values", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: [InputType!]) { arrayArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []any{map[string]any{}},
				},
			)
			require.EqualError(t, gerr, "input: variable.var[0].name must be defined")
		})
		t.Run("invalid variable paths", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var1: InputType!, $var2: InputType!) { a:structArg(i: $var1) b:structArg(i: $var2) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var1": map[string]any{
						"name": "foobar",
					},
					"var2": map[string]any{
						"nullName": "foobar",
					},
				},
			)
			require.EqualError(t, gerr, "input: variable.var2.name must be defined")
		})
	})

	t.Run("Scalars", func(t *testing.T) {
		t.Run("String -> String", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: String!) { stringArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": "asdf",
				},
			)
			require.NoError(t, gerr)
			require.EqualValues(t, "asdf", vars["var"])
		})

		t.Run("Int -> String", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: String!) { stringArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": 1,
				},
			)
			require.EqualError(t, gerr, "input: variable.var cannot use int as String")
		})

		t.Run("Nil -> String", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: String!) { stringArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": nil,
				},
			)
			require.EqualError(t, gerr, "input: variable.var cannot be null")
		})

		t.Run("Undefined -> String!", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: String!) { stringArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.EqualError(t, gerr, "input: variable.var must be defined")
		})

		t.Run("Undefined -> Int", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: Int) { optionalIntArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(schema, q.Operations.ForName(""), nil)
			require.NoError(t, gerr)
		})

		t.Run("Json Number -> Int", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: Int) { optionalIntArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": 10,
				},
			)
			require.NoError(t, gerr)
			require.Equal(t, 10, vars["var"])
		})

		t.Run("Json Number -> Float", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: Float!) { floatArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": 10.2,
				},
			)
			require.NoError(t, gerr)
			require.Equal(t, 10.2, vars["var"])
		})

		t.Run("Nil -> Int", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: Int) { optionalIntArg(i: $var) }`,
				nil,
			)
			vars, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": nil,
				},
			)
			require.NoError(t, gerr)
			require.Nil(t, vars["var"])
		})

		t.Run("Bool -> Int", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: Int!) { intArg(i: $var) }`,
				nil,
			)
			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": true,
				},
			)
			require.EqualError(t, gerr, "input: variable.var cannot use bool as Int")
		})
	})

	t.Run("Int Array", func(t *testing.T) {
		t.Run("Array with null", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: [Int]) { intArrayArg(i: $var) }`,
				nil,
			)
			a := 1
			b := 2

			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []*int{&a, &b, nil},
				},
			)
			require.NoError(t, gerr)
		})
	})

	t.Run("String Array", func(t *testing.T) {
		t.Run("Array with null", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: [String]) { stringArrayArg(i: $var) }`,
				nil,
			)
			a := "1"
			b := "2"

			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []*string{&a, &b, nil},
				},
			)
			require.NoError(t, gerr)
		})
	})

	t.Run("Boolean Array", func(t *testing.T) {
		t.Run("Array with null", func(t *testing.T) {
			q := gqlparser.MustLoadQueryWithRules(
				schema,
				`query foo($var: [Boolean]) { boolArrayArg(i: $var) }`,
				nil,
			)
			a := true
			b := false

			_, gerr := validator.VariableValues(
				schema,
				q.Operations.ForName(""),
				map[string]any{
					"var": []*bool{&a, &b, nil},
				},
			)
			require.NoError(t, gerr)
		})
	})
}

func mustReadFile(name string) string {
	src, err := os.ReadFile(name)
	if err != nil {
		panic(err)
	}

	return string(src)
}
