package parser

import (
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/vektah/gqlparser/v2/ast"
	"github.com/vektah/gqlparser/v2/lexer"
)

func TestParserUtils(t *testing.T) {
	t.Run("test lookaround", func(t *testing.T) {
		p := newParser("asdf 1.0 turtles")
		require.Equal(t, "asdf", p.peek().Value)

		tok, _ := p.expectKeyword("asdf")
		require.Equal(t, "asdf", tok.Value)
		require.Equal(t, "asdf", p.prev.Value)
		require.Nil(t, p.err)

		require.Equal(t, "1.0", p.peek().Value)
		require.Equal(t, "1.0", p.peek().Value)
		tok, _ = p.expect(lexer.Float)
		require.Equal(t, "1.0", tok.Value)
		require.Equal(t, "1.0", p.prev.Value)
		require.Nil(t, p.err)

		require.True(t, p.skip(lexer.Name))
		require.Nil(t, p.err)

		require.Equal(t, lexer.EOF, p.peek().Kind)
		require.Nil(t, p.err)
	})

	t.Run("test many", func(t *testing.T) {
		t.Run("can read array", func(t *testing.T) {
			p := newParser("[a b c d]")

			var arr []string
			p.many(lexer.BracketL, lexer.BracketR, func() {
				arr = append(arr, p.next().Value)
			})
			require.Nil(t, p.err)
			require.Equal(t, []string{"a", "b", "c", "d"}, arr)

			require.Equal(t, lexer.EOF, p.peek().Kind)
			require.Nil(t, p.err)
		})

		t.Run("return if open is not found", func(t *testing.T) {
			p := newParser("turtles are happy")

			p.many(lexer.BracketL, lexer.BracketR, func() {
				t.Error("cb should not be called")
			})
			require.Nil(t, p.err)
			require.Equal(t, "turtles", p.next().Value)
		})

		t.Run("will stop on error", func(t *testing.T) {
			p := newParser("[a b c d]")

			var arr []string
			p.many(lexer.BracketL, lexer.BracketR, func() {
				arr = append(arr, p.next().Value)
				if len(arr) == 2 {
					p.error(p.peek(), "boom")
				}
			})
			require.EqualError(t, p.err, "input.graphql:1: boom")
			require.Equal(t, []string{"a", "b"}, arr)
		})
	})

	t.Run("test some", func(t *testing.T) {
		t.Run("can read array", func(t *testing.T) {
			p := newParser("[a b c d]")

			var arr []string
			p.some(lexer.BracketL, lexer.BracketR, func() {
				arr = append(arr, p.next().Value)
			})
			require.Nil(t, p.err)
			require.Equal(t, []string{"a", "b", "c", "d"}, arr)

			require.Equal(t, lexer.EOF, p.peek().Kind)
			require.Nil(t, p.err)
		})

		t.Run("can't read empty array", func(t *testing.T) {
			p := newParser("[]")

			var arr []string
			p.some(lexer.BracketL, lexer.BracketR, func() {
				arr = append(arr, p.next().Value)
			})
			require.EqualError(t, p.err, "input.graphql:1: expected at least one definition, found ]")
			require.Equal(t, []string(nil), arr)
			require.NotEqual(t, lexer.EOF, p.peek().Kind)
		})

		t.Run("return if open is not found", func(t *testing.T) {
			p := newParser("turtles are happy")

			p.some(lexer.BracketL, lexer.BracketR, func() {
				t.Error("cb should not be called")
			})
			require.Nil(t, p.err)
			require.Equal(t, "turtles", p.next().Value)
		})

		t.Run("will stop on error", func(t *testing.T) {
			p := newParser("[a b c d]")

			var arr []string
			p.some(lexer.BracketL, lexer.BracketR, func() {
				arr = append(arr, p.next().Value)
				if len(arr) == 2 {
					p.error(p.peek(), "boom")
				}
			})
			require.EqualError(t, p.err, "input.graphql:1: boom")
			require.Equal(t, []string{"a", "b"}, arr)
		})
	})

	t.Run("test errors", func(t *testing.T) {
		p := newParser("foo bar")

		p.next()
		p.error(p.peek(), "test error")
		p.error(p.peek(), "secondary error")

		require.EqualError(t, p.err, "input.graphql:1: test error")

		require.Equal(t, "foo", p.peek().Value)
		require.Equal(t, "foo", p.next().Value)
		require.Equal(t, "foo", p.peek().Value)
	})

	t.Run("unexpected error", func(t *testing.T) {
		p := newParser("1 3")
		p.unexpectedError()
		require.EqualError(t, p.err, "input.graphql:1: Unexpected Int \"1\"")
	})

	t.Run("unexpected error", func(t *testing.T) {
		p := newParser("1 3")
		p.unexpectedToken(p.next())
		require.EqualError(t, p.err, "input.graphql:1: Unexpected Int \"1\"")
	})

	t.Run("expect error", func(t *testing.T) {
		p := newParser("foo bar")
		p.expect(lexer.Float)

		require.EqualError(t, p.err, "input.graphql:1: Expected Float, found Name")
	})

	t.Run("expectKeyword error", func(t *testing.T) {
		p := newParser("foo bar")
		p.expectKeyword("baz")

		require.EqualError(t, p.err, "input.graphql:1: Expected \"baz\", found Name \"foo\"")
	})
}

func newParser(input string) parser {
	return parser{lexer: lexer.New(&ast.Source{Input: input, Name: "input.graphql"})}
}
