/*
 * MinIO Cloud Storage, (C) 2019 MinIO, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sql

import (
	"bytes"
	"testing"

	"github.com/alecthomas/participle"
	"github.com/alecthomas/participle/lexer"
)

func TestJSONPathElement(t *testing.T) {
	p := participle.MustBuild(
		&JSONPathElement{},
		participle.Lexer(sqlLexer),
		participle.CaseInsensitive("Keyword"),
		participle.CaseInsensitive("Timeword"),
	)

	j := JSONPathElement{}
	cases := []string{
		// Key
		"['name']", ".name", `."name"`,

		// Index
		"[2]", "[0]", "[100]",

		// Object wilcard
		".*",

		// array wildcard
		"[*]",
	}
	for i, tc := range cases {
		err := p.ParseString(tc, &j)
		if err != nil {
			t.Fatalf("%d: %v", i, err)
		}
		// repr.Println(j, repr.Indent("  "), repr.OmitEmpty(true))
	}
}

func TestJSONPath(t *testing.T) {
	p := participle.MustBuild(
		&JSONPath{},
		participle.Lexer(sqlLexer),
		participle.CaseInsensitive("Keyword"),
		participle.CaseInsensitive("Timeword"),
	)

	j := JSONPath{}
	cases := []string{
		"S3Object",
		"S3Object.id",
		"S3Object.book.title",
		"S3Object.id[1]",
		"S3Object.id['abc']",
		"S3Object.id['ab']",
		"S3Object.words.*.id",
		"S3Object.words.name[*].val",
		"S3Object.words.name[*].val[*]",
		"S3Object.words.name[*].val.*",
	}
	for i, tc := range cases {
		err := p.ParseString(tc, &j)
		if err != nil {
			t.Fatalf("%d: %v", i, err)
		}
		// repr.Println(j, repr.Indent("  "), repr.OmitEmpty(true))
	}

}

func TestIdentifierParsing(t *testing.T) {
	p := participle.MustBuild(
		&Identifier{},
		participle.Lexer(sqlLexer),
		participle.CaseInsensitive("Keyword"),
	)

	id := Identifier{}
	validCases := []string{
		"a",
		"_a",
		"abc_a",
		"a2",
		`"abc"`,
		`"abc\a""ac"`,
	}
	for i, tc := range validCases {
		err := p.ParseString(tc, &id)
		if err != nil {
			t.Fatalf("%d: %v", i, err)
		}
		// repr.Println(id, repr.Indent("  "), repr.OmitEmpty(true))
	}

	invalidCases := []string{
		"+a",
		"-a",
		"1a",
		`"ab`,
		`abc"`,
		`aa""a`,
		`"a"a"`,
	}
	for i, tc := range invalidCases {
		err := p.ParseString(tc, &id)
		if err == nil {
			t.Fatalf("%d: %v", i, err)
		}
		// fmt.Println(tc, err)
	}
}

func TestLiteralStringParsing(t *testing.T) {
	var k ObjectKey
	p := participle.MustBuild(
		&ObjectKey{},
		participle.Lexer(sqlLexer),
		participle.CaseInsensitive("Keyword"),
	)

	validCases := []string{
		"['abc']",
		"['ab''c']",
		"['a''b''c']",
		"['abc-x_1##@(*&(#*))/\\']",
	}
	for i, tc := range validCases {
		err := p.ParseString(tc, &k)
		if err != nil {
			t.Fatalf("%d: %v", i, err)
		}
		if string(*k.Lit) == "" {
			t.Fatalf("Incorrect parse %#v", k)
		}
		// repr.Println(k, repr.Indent("  "), repr.OmitEmpty(true))
	}

	invalidCases := []string{
		"['abc'']",
		"['-abc'sc']",
		"[abc']",
		"['ac]",
	}
	for i, tc := range invalidCases {
		err := p.ParseString(tc, &k)
		if err == nil {
			t.Fatalf("%d: %v", i, err)
		}
		// fmt.Println(tc, err)
	}
}

func TestFunctionParsing(t *testing.T) {
	var fex FuncExpr
	p := participle.MustBuild(
		&FuncExpr{},
		participle.Lexer(sqlLexer),
		participle.CaseInsensitive("Keyword"),
		participle.CaseInsensitive("Timeword"),
	)

	validCases := []string{
		"count(*)",
		"sum(2 + s.id)",
		"sum(t)",
		"avg(s.id[1])",
		"coalesce(s.id[1], 2, 2 + 3)",

		"cast(s as string)",
		"cast(s AS INT)",
		"cast(s as DECIMAL)",
		"extract(YEAR from '2018-01-09')",
		"extract(month from '2018-01-09')",

		"extract(hour from '2018-01-09')",
		"extract(day from '2018-01-09')",
		"substring('abcd' from 2 for 2)",
		"substring('abcd' from 2)",
		"substring('abcd' , 2 , 2)",

		"substring('abcd' , 22 )",
		"trim('  aab  ')",
		"trim(leading from '  aab  ')",
		"trim(trailing from '  aab  ')",
		"trim(both from '  aab  ')",

		"trim(both '12' from '  aab  ')",
		"trim(leading '12' from '  aab  ')",
		"trim(trailing '12' from '  aab  ')",
		"count(23)",
	}
	for i, tc := range validCases {
		err := p.ParseString(tc, &fex)
		if err != nil {
			t.Fatalf("%d: %v", i, err)
		}
		// repr.Println(fex, repr.Indent("  "), repr.OmitEmpty(true))
	}
}

func TestSqlLexer(t *testing.T) {
	// s := bytes.NewBuffer([]byte("s.['name'].*.[*].abc.[\"abc\"]"))
	s := bytes.NewBuffer([]byte("S3Object.words.*.id"))
	// s := bytes.NewBuffer([]byte("COUNT(Id)"))
	lex, err := sqlLexer.Lex(s)
	if err != nil {
		t.Fatal(err)
	}
	tokens, err := lexer.ConsumeAll(lex)
	if err != nil {
		t.Fatal(err)
	}
	// for i, t := range tokens {
	// 	fmt.Printf("%d: %#v\n", i, t)
	// }
	if len(tokens) != 7 {
		t.Fatalf("Expected 7 got %d", len(tokens))
	}
}

func TestSelectWhere(t *testing.T) {
	p := participle.MustBuild(
		&Select{},
		participle.Lexer(sqlLexer),
		participle.CaseInsensitive("Keyword"),
	)

	s := Select{}
	cases := []string{
		"select * from s3object",
		"select a, b from s3object s",
		"select a, b from s3object as s",
		"select a, b from s3object as s where a = 1",
		"select a, b from s3object s where a = 1",
		"select a, b from s3object where a = 1",
	}
	for i, tc := range cases {
		err := p.ParseString(tc, &s)
		if err != nil {
			t.Fatalf("%d: %v", i, err)
		}

		// repr.Println(s, repr.Indent("  "), repr.OmitEmpty(true))
	}
}

func TestLikeClause(t *testing.T) {
	p := participle.MustBuild(
		&Select{},
		participle.Lexer(sqlLexer),
		participle.CaseInsensitive("Keyword"),
	)

	s := Select{}
	cases := []string{
		`select * from s3object where Name like 'abcd'`,
		`select Name like 'abc' from s3object`,
		`select * from s3object where Name not like 'abc'`,
		`select * from s3object where Name like 'abc' escape 't'`,
		`select * from s3object where Name like 'a\%' escape '?'`,
		`select * from s3object where Name not like 'abc\' escape '?'`,
		`select * from s3object where Name like 'a\%' escape LOWER('?')`,
		`select * from s3object where Name not like LOWER('Bc\') escape '?'`,
	}
	for i, tc := range cases {
		err := p.ParseString(tc, &s)
		if err != nil {
			t.Errorf("%d: %v", i, err)
		}
	}
}

func TestBetweenClause(t *testing.T) {
	p := participle.MustBuild(
		&Select{},
		participle.Lexer(sqlLexer),
		participle.CaseInsensitive("Keyword"),
	)

	s := Select{}
	cases := []string{
		`select * from s3object where Id between 1 and 2`,
		`select * from s3object where Id between 1 and 2 and name = 'Ab'`,
		`select * from s3object where Id not between 1 and 2`,
		`select * from s3object where Id not between 1 and 2 and name = 'Bc'`,
	}
	for i, tc := range cases {
		err := p.ParseString(tc, &s)
		if err != nil {
			t.Errorf("%d: %v", i, err)
		}
	}
}

func TestFromClauseJSONPath(t *testing.T) {
	p := participle.MustBuild(
		&Select{},
		participle.Lexer(sqlLexer),
		participle.CaseInsensitive("Keyword"),
	)

	s := Select{}
	cases := []string{
		"select * from s3object",
		"select * from s3object[*].name",
		"select * from s3object[*].books[*]",
		"select * from s3object[*].books[*].name",
		"select * from s3object where name > 2",
		"select * from s3object[*].name where name > 2",
		"select * from s3object[*].books[*] where name > 2",
		"select * from s3object[*].books[*].name where name > 2",
		"select * from s3object[*].books[*] s",
		"select * from s3object[*].books[*].name as s",
		"select * from s3object s where name > 2",
		"select * from s3object[*].name as s where name > 2",
	}
	for i, tc := range cases {
		err := p.ParseString(tc, &s)
		if err != nil {
			t.Fatalf("%d: %v", i, err)
		}

		// repr.Println(s, repr.Indent("  "), repr.OmitEmpty(true))
	}

}

func TestSelectParsing(t *testing.T) {
	p := participle.MustBuild(
		&Select{},
		participle.Lexer(sqlLexer),
		participle.CaseInsensitive("Keyword"),
	)

	s := Select{}
	cases := []string{
		"select * from s3object where name > 2 or value > 1 or word > 2",
		"select s.word.id + 2 from s3object s",
		"select 1-2-3 from s3object s limit 1",
	}
	for i, tc := range cases {
		err := p.ParseString(tc, &s)
		if err != nil {
			t.Fatalf("%d: %v", i, err)
		}

		// repr.Println(s, repr.Indent("  "), repr.OmitEmpty(true))
	}
}

func TestSqlLexerArithOps(t *testing.T) {
	s := bytes.NewBuffer([]byte("year from select month hour distinct"))
	lex, err := sqlLexer.Lex(s)
	if err != nil {
		t.Fatal(err)
	}
	tokens, err := lexer.ConsumeAll(lex)
	if err != nil {
		t.Fatal(err)
	}
	if len(tokens) != 7 {
		t.Errorf("Expected 7 got %d", len(tokens))
	}
	// for i, t := range tokens {
	// 	fmt.Printf("%d: %#v\n", i, t)
	// }
}