package syntax_test

import (
	"testing"

	"code.justin.tv/cplat/twitchling/internal/syntax"
	"code.justin.tv/cplat/twitchling/internal/syntax/ast"
	"github.com/stretchr/testify/assert"
)

func TestParse(t *testing.T) {
	t.Run("parses pure text as a single fragment", func(t *testing.T) {
		input := "this is test text"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		textSpan, ok := root[0].(*ast.TextSpan)

		assert.True(t, ok)
		assert.Equal(t, input, textSpan.Value())
	})

	t.Run("parses text containing keywords as a single fragment", func(t *testing.T) {
		input := "one other two many"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		textSpan, ok := root[0].(*ast.TextSpan)

		assert.True(t, ok)
		assert.Equal(t, input, textSpan.Value())
	})

	t.Run("parses text containing numbers as a single fragment", func(t *testing.T) {
		input := "1 other 2 many"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		textSpan, ok := root[0].(*ast.TextSpan)

		assert.True(t, ok)
		assert.Equal(t, input, textSpan.Value())
	})

	t.Run("parses text containing escaped text as a single fragment", func(t *testing.T) {
		input := "'{'} '' '</em>"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		textSpan, ok := root[0].(*ast.TextSpan)

		assert.True(t, ok)
		assert.Equal(t, "{} ' </em>", textSpan.Value())
	})

	t.Run("parses element", func(t *testing.T) {
		input := "<em>test</em>"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		element, ok := root[0].(*ast.Element)
		assert.True(t, ok)

		inner, ok := element.Nodes()[0].(*ast.TextSpan)
		assert.True(t, ok)
		assert.Equal(t, "test", inner.Value())
	})

	t.Run("parses element with spaces in name", func(t *testing.T) {
		input := "< em >test</ em     >"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		element, ok := root[0].(*ast.Element)
		assert.True(t, ok)

		inner, ok := element.Nodes()[0].(*ast.TextSpan)
		assert.True(t, ok)
		assert.Equal(t, "test", inner.Value())
	})

	t.Run("fails in element with invalid name", func(t *testing.T) {
		input := "<em x>test</em>"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.Error(t, err)
		assert.Nil(t, root)
	})

	t.Run("fails on close element without open", func(t *testing.T) {
		input := "</em>"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.Error(t, err)
		assert.Len(t, root, 0)
	})

	t.Run("fails on mismatched open and close element", func(t *testing.T) {
		input := "<em></i>"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.Error(t, err)
		assert.Len(t, root, 0)
	})

	t.Run("fails on unsupported element", func(t *testing.T) {
		input := "<thisBreaks>"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.Error(t, err)
		assert.Len(t, root, 0)
	})

	t.Run("parses nested element", func(t *testing.T) {
		input := "<em><em>test</em></em>"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		element, ok := root[0].(*ast.Element)
		assert.True(t, ok)

		innerElement, ok := element.Nodes()[0].(*ast.Element)
		assert.True(t, ok)

		inner, ok := innerElement.Nodes()[0].(*ast.TextSpan)
		assert.True(t, ok)
		assert.Equal(t, "test", inner.Value())
	})

	t.Run("parses basic placeholder argument", func(t *testing.T) {
		input := "{testArg}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		_, ok := root[0].(*ast.Argument)
		assert.True(t, ok)
	})

	t.Run("parses basic placeholder argument ending in number", func(t *testing.T) {
		input := "{testArg2}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		_, ok := root[0].(*ast.Argument)
		assert.True(t, ok)
	})

	t.Run("parses basic placeholder argument inside tag", func(t *testing.T) {
		input := "<em>{testArg}</em>"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		element, ok := root[0].(*ast.Element)
		assert.True(t, ok)

		_, ok = element.Nodes()[0].(*ast.Argument)
		assert.True(t, ok)
	})

	t.Run("errors out with invalid placeholder argument name", func(t *testing.T) {
		input := "{5testArg}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.Error(t, err)
		assert.Nil(t, root)
	})

	t.Run("parses number function", func(t *testing.T) {
		input := "{testArg, number}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		function, ok := root[0].(*ast.NumberFunction)
		assert.True(t, ok)
		assert.Equal(t, "testArg", function.Argument())
	})

	t.Run("parses number function with integer format", func(t *testing.T) {
		input := "{testArg, number, integer}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		function, ok := root[0].(*ast.NumberFunction)
		assert.True(t, ok)
		assert.Equal(t, "testArg", function.Argument())
		assert.Equal(t, ast.NumberFormat_Integer, function.Format())
	})

	t.Run("parses number function with currency format", func(t *testing.T) {
		input := "{testArg, number, currency}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		function, ok := root[0].(*ast.NumberFunction)
		assert.True(t, ok)
		assert.Equal(t, "testArg", function.Argument())
		assert.Equal(t, ast.NumberFormat_Currency, function.Format())
	})

	t.Run("parses number function with percent format", func(t *testing.T) {
		input := "{testArg, number, percent}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		function, ok := root[0].(*ast.NumberFunction)
		assert.True(t, ok)
		assert.Equal(t, "testArg", function.Argument())
		assert.Equal(t, ast.NumberFormat_Percent, function.Format())
	})

	t.Run("parses number function inside tag", func(t *testing.T) {
		input := "<em>{testArg, number}</em>"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		element, ok := root[0].(*ast.Element)
		assert.True(t, ok)

		_, ok = element.Nodes()[0].(*ast.NumberFunction)
		assert.True(t, ok)
	})

	t.Run("returns error when unmatched closing curly brace is found", func(t *testing.T) {
		input := "{testArg}}"
		_, err := syntax.Parse(input)

		pErr, ok := err.(*syntax.ParseError)
		assert.True(t, ok)
		assert.Equal(t, 0, pErr.StartLine())
		assert.Equal(t, 9, pErr.StartColumn())
		assert.Equal(t, "unexpected closing brace", pErr.Message())
		assert.Len(t, pErr.Context(), 1)
		assert.Equal(t, pErr.Context()[0], input)
	})

	t.Run("parses plural function with other variant", func(t *testing.T) {
		input := "{testArg, plural, other {test text}}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		function, ok := root[0].(*ast.PluralFunction)
		assert.True(t, ok)
		assert.Equal(t, "testArg", function.Argument())

		other, ok := function.Variants()[ast.PluralForm_Other]
		assert.True(t, ok)

		body, ok := other[0].(*ast.TextSpan)
		assert.True(t, ok)
		assert.Equal(t, "test text", body.Value())
	})

	t.Run("parses plural function with all plural forms", func(t *testing.T) {
		input := "{testArg, plural, zero {test text} one {test text} two {test text} few {test text} many {test text} other {test text}}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)
		assert.Len(t, root, 1)

		function, ok := root[0].(*ast.PluralFunction)
		assert.True(t, ok)
		assert.Equal(t, "testArg", function.Argument())
		assert.Len(t, function.Variants(), 6)

		for _, nodes := range function.Variants() {
			body, ok := nodes[0].(*ast.TextSpan)
			assert.True(t, ok)
			assert.Equal(t, "test text", body.Value())
		}
	})

	t.Run("plural fails when missing other form", func(t *testing.T) {
		input := "{testArg, plural, zero {test text}}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.Error(t, err)
		assert.Nil(t, root)
	})

	t.Run("plural fails with duplicate forms", func(t *testing.T) {
		input := "{testArg, plural, other {test text} other {test text}}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.Error(t, err)
		assert.Nil(t, root)
	})

	t.Run("plural fails with no forms", func(t *testing.T) {
		input := "{testArg, plural, }"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.Error(t, err)
		assert.Nil(t, root)
	})

	t.Run("parses plural function with plural placeholder", func(t *testing.T) {
		input := "{testArg, plural, other {# tests!}}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)

		function, ok := root[0].(*ast.PluralFunction)
		assert.True(t, ok)
		assert.Equal(t, "testArg", function.Argument())

		other, ok := function.Variants()[ast.PluralForm_Other]
		assert.True(t, ok)

		body, ok := other[0].(*ast.Argument)
		assert.True(t, ok)
		assert.Equal(t, "testArg", body.Argument())
	})

	t.Run("parses plural function with plural placeholder inside element", func(t *testing.T) {
		input := "{testArg, plural, other {<em>#</em> tests!}}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)

		function, ok := root[0].(*ast.PluralFunction)
		assert.True(t, ok)
		assert.Equal(t, "testArg", function.Argument())

		other, ok := function.Variants()[ast.PluralForm_Other]
		assert.True(t, ok)

		element, ok := other[0].(*ast.Element)
		assert.True(t, ok)

		body, ok := element.Nodes()[0].(*ast.Argument)
		assert.True(t, ok)
		assert.Equal(t, "testArg", body.Argument())
	})

	t.Run("parses plural placeholder at root as plain text", func(t *testing.T) {
		input := "# test"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)

		textSpan, ok := root[0].(*ast.TextSpan)
		assert.True(t, ok)
		assert.Equal(t, input, textSpan.Value())
	})

	t.Run("parses number placeholder in middle of string", func(t *testing.T) {
		input := "{arg, plural, other {number # in middle}}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)

		plural, ok := root[0].(*ast.PluralFunction)
		assert.True(t, ok)

		nodes, ok := plural.Variants()[ast.PluralForm_Other]
		assert.True(t, ok)
		assert.NotNil(t, nodes)
		assert.Len(t, nodes, 3)

		argument, ok := nodes[1].(*ast.Argument)
		assert.True(t, ok)
		assert.NotNil(t, argument)
	})

	t.Run("parses number placeholder at end of string", func(t *testing.T) {
		input := "{arg, plural, other {number #}}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)

		plural, ok := root[0].(*ast.PluralFunction)
		assert.True(t, ok)

		nodes, ok := plural.Variants()[ast.PluralForm_Other]
		assert.True(t, ok)
		assert.NotNil(t, nodes)
		assert.Len(t, nodes, 2)

		argument, ok := nodes[1].(*ast.Argument)
		assert.True(t, ok)
		assert.NotNil(t, argument)
	})

	t.Run("parses number placeholder nested in element in middle of string", func(t *testing.T) {
		input := "{arg, plural, other {<em>number # in middle</em>}}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)

		plural, ok := root[0].(*ast.PluralFunction)
		assert.True(t, ok)

		nodes, ok := plural.Variants()[ast.PluralForm_Other]
		assert.True(t, ok)
		assert.NotNil(t, nodes)
		assert.Len(t, nodes, 1)

		element, ok := nodes[0].(*ast.Element)
		assert.True(t, ok)
		assert.NotNil(t, element)
		assert.Len(t, element.Nodes(), 3)

		argument, ok := element.Nodes()[1].(*ast.Argument)
		assert.True(t, ok)
		assert.NotNil(t, argument)
	})

	t.Run("parses number placeholder nested in element at end of string", func(t *testing.T) {
		input := "{arg, plural, other {<em>number #</em>}}"
		parseResult, err := syntax.Parse(input)
		root := parseResult.Root()

		assert.NoError(t, err)
		assert.NotNil(t, root)

		plural, ok := root[0].(*ast.PluralFunction)
		assert.True(t, ok)

		nodes, ok := plural.Variants()[ast.PluralForm_Other]
		assert.True(t, ok)
		assert.NotNil(t, nodes)
		assert.Len(t, nodes, 1)

		element, ok := nodes[0].(*ast.Element)
		assert.True(t, ok)
		assert.NotNil(t, element)
		assert.Len(t, element.Nodes(), 2)

		argument, ok := element.Nodes()[1].(*ast.Argument)
		assert.True(t, ok)
		assert.NotNil(t, argument)
	})

	t.Run("parses singular < as error", func(t *testing.T) {
		input := "<"
		_, err := syntax.Parse(input)

		assert.Error(t, err)
	})

	t.Run("parses singular { as error", func(t *testing.T) {
		input := "{"
		_, err := syntax.Parse(input)

		assert.Error(t, err)
	})

	t.Run("returns error if string starts with NULL terminator", func(t *testing.T) {
		input := "\x00 {arg}"
		_, err := syntax.Parse(input)

		assert.Error(t, err)
	})
}

func BenchmarkParse(b *testing.B) {
	b.Run("input with plural and tags", func(b *testing.B) {
		for n := 0; n < b.N; n++ {
			input := "{testArg, plural, zero {<em>test</em> text} one {<em>test</em> text} two {<em>test</em> text} few {<em>test</em> text} many {<em>test</em> text} other {<em>test</em> text}}"
			_, _ = syntax.Parse(input)
		}
	})
}
