package macro

import (
	"reflect"
	"strconv"
	"testing"
	"time"
)

// Most important tests to look:
// ../parser/parser_test.go
// ../lexer/lexer_test.go

func TestGoodParamFunc(t *testing.T) {
	good1 := func(min int, max int) func(string) bool {
		return func(paramValue string) bool {
			return true
		}
	}

	good2 := func(min uint64, max uint64) func(string) bool {
		return func(paramValue string) bool {
			return true
		}
	}

	notgood1 := func(min int, max int) bool {
		return false
	}

	if !goodParamFunc(reflect.TypeOf(good1)) {
		t.Fatalf("expected good1 func to be good but it's not")
	}

	if !goodParamFunc(reflect.TypeOf(good2)) {
		t.Fatalf("expected good2 func to be good but it's not")
	}

	if goodParamFunc(reflect.TypeOf(notgood1)) {
		t.Fatalf("expected notgood1 func to be the worst")
	}
}

func TestGoodParamFuncName(t *testing.T) {
	tests := []struct {
		name string
		good bool
	}{
		{"range", true},
		{"_range", true},
		{"range_", true},
		{"r_ange", true},
		// numbers or other symbols are invalid.
		{"range1", false},
		{"2range", false},
		{"r@nge", false},
		{"rang3", false},
	}
	for i, tt := range tests {
		isGood := goodParamFuncName(tt.name)
		if tt.good && !isGood {
			t.Fatalf("tests[%d] - expecting valid name but got invalid for name %s", i, tt.name)
		} else if !tt.good && isGood {
			t.Fatalf("tests[%d] - expecting invalid name but got valid for name %s", i, tt.name)
		}
	}
}

func testEvaluatorRaw(t *testing.T, macroEvaluator *Macro, input string, expectedType reflect.Kind, pass bool, i int) {
	t.Helper()

	if macroEvaluator.Evaluator == nil && pass {
		return // if not evaluator defined then it should allow everything.
	}
	value, passed := macroEvaluator.Evaluator(input)
	if pass != passed {
		t.Fatalf("%s - tests[%d] - expecting[pass] %v but got %v", t.Name(), i, pass, passed)
	}

	if !passed {
		return
	}

	if value == nil && expectedType != reflect.Invalid {
		t.Fatalf("%s - tests[%d] - expecting[value] to not be nil", t.Name(), i)
	}

	if v := reflect.ValueOf(value); v.Kind() != expectedType {
		t.Fatalf("%s - tests[%d] - expecting[value.Kind] %v but got %v", t.Name(), i, expectedType, v.Kind())
	}
}

func TestStringEvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{true, "astring"},                         // 0
		{true, "astringwith_numb3rS_and_symbol$"}, // 1
		{true, "32321"},                           // 2
		{true, "main.css"},                        // 3
		{true, "/assets/main.css"},                // 4
		// false never
	} // 0

	for i, tt := range tests {
		testEvaluatorRaw(t, String, tt.input, reflect.String, tt.pass, i)
	}
}

func TestIntEvaluatorRaw(t *testing.T) {
	x64 := strconv.IntSize == 64

	tests := []struct {
		pass  bool
		input string
	}{
		{false, "astring"},                                 // 0
		{false, "astringwith_numb3rS_and_symbol$"},         // 1
		{true, "32321"},                                    // 2
		{x64, "9223372036854775807" /* max int64 */},       // 3
		{x64, "-9223372036854775808" /* min int64 */},      // 4
		{false, "-18446744073709553213213213213213121615"}, // 5
		{false, "42 18446744073709551615"},                 // 6
		{false, "--42"},                                    // 7
		{true, "+42"},                                      // 8
		{false, "main.css"},                                // 9
		{false, "/assets/main.css"},                        // 10
	}

	for i, tt := range tests {
		testEvaluatorRaw(t, Int, tt.input, reflect.Int, tt.pass, i)
	}
}

func BenchmarkIntEvaluatorRaw(b *testing.B) {
	b.ReportAllocs()

	for i := 0; i < b.N; i++ {
		Int.Evaluator("1234568320")
		Int.Evaluator("-12345678999321")
	}
}

func TestInt8EvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{false, "astring"},                         // 0
		{false, "astringwith_numb3rS_and_symbol$"}, // 1
		{false, "32321"},                           // 2
		{true, "127" /* max int8 */},               // 3
		{true, "-128" /* min int8 */},              // 4
		{false, "128"},                             // 5
		{false, "-129"},                            // 6
		{false, "-18446744073709553213213213213213121615"}, // 7
		{false, "42 18446744073709551615"},                 // 8
		{false, "--42"},                                    // 9
		{true, "+42"},                                      // 10
		{false, "main.css"},                                // 11
		{false, "/assets/main.css"},                        // 12
	}

	for i, tt := range tests {
		testEvaluatorRaw(t, Int8, tt.input, reflect.Int8, tt.pass, i)
	}
}

func TestInt16EvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{false, "astring"},                         // 0
		{false, "astringwith_numb3rS_and_symbol$"}, // 1
		{true, "32321"},                            // 2
		{true, "32767" /* max int16 */},            // 3
		{true, "-32768" /* min int16 */},           // 4
		{false, "-32769"},                          // 5
		{false, "32768"},                           // 6
		{false, "-18446744073709553213213213213213121615"}, // 7
		{false, "42 18446744073709551615"},                 // 8
		{false, "--42"},                                    // 9
		{true, "+42"},                                      // 10
		{false, "main.css"},                                // 11
		{false, "/assets/main.css"},                        // 12
	}

	for i, tt := range tests {
		testEvaluatorRaw(t, Int16, tt.input, reflect.Int16, tt.pass, i)
	}
}

func TestInt32EvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{false, "astring"},                         // 0
		{false, "astringwith_numb3rS_and_symbol$"}, // 1
		{true, "32321"},                            // 2
		{true, "1"},                                // 3
		{true, "42"},                               // 4
		{true, "2147483647" /* max int32 */},       // 5
		{true, "-2147483648" /* min int32 */},      // 6
		{false, "-2147483649"},                     // 7
		{false, "2147483648"},                      // 8
		{false, "-18446744073709553213213213213213121615"}, // 9
		{false, "42 18446744073709551615"},                 // 10
		{false, "--42"},                                    // 11
		{true, "+42"},                                      // 12
		{false, "main.css"},                                // 13
		{false, "/assets/main.css"},                        // 14
	}

	for i, tt := range tests {
		testEvaluatorRaw(t, Int32, tt.input, reflect.Int32, tt.pass, i)
	}
}

func TestInt64EvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{false, "astring"},                                 // 0
		{false, "astringwith_numb3rS_and_symbol$"},         // 1
		{false, "18446744073709551615"},                    // 2
		{false, "92233720368547758079223372036854775807"},  // 3
		{false, "9223372036854775808 9223372036854775808"}, // 4
		{false, "main.css"},                                // 5
		{false, "/assets/main.css"},                        // 6
		{true, "9223372036854775807"},                      // 7
		{true, "-9223372036854775808"},                     // 8
		{true, "-0"},                                       // 9
		{true, "1"},                                        // 10
		{true, "-042"},                                     // 11
		{true, "142"},                                      // 12
	}

	for i, tt := range tests {
		testEvaluatorRaw(t, Int64, tt.input, reflect.Int64, tt.pass, i)
	}
}

func TestUintEvaluatorRaw(t *testing.T) {
	x64 := strconv.IntSize == 64

	tests := []struct {
		pass  bool
		input string
	}{
		{false, "astring"},                             // 0
		{false, "astringwith_numb3rS_and_symbol$"},     // 1
		{true, "32321"},                                // 2
		{true, "1"},                                    // 3
		{true, "42"},                                   // 4
		{x64, "18446744073709551615" /* max uint64 */}, // 5
		{true, "4294967295" /* max uint32 */},          // 6
		{false, "-2147483649"},                         // 7
		{true, "2147483648"},                           // 8
		{false, "-18446744073709553213213213213213121615"}, // 9
		{false, "42 18446744073709551615"},                 // 10
		{false, "--42"},                                    // 11
		{false, "+42"},                                     // 12
		{false, "main.css"},                                // 13
		{false, "/assets/main.css"},                        // 14
	}

	for i, tt := range tests {
		testEvaluatorRaw(t, Uint, tt.input, reflect.Uint, tt.pass, i)
	}
}

func TestUint8EvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{false, "astring"},                                 // 0
		{false, "astringwith_numb3rS_and_symbol$"},         // 1
		{false, "-9223372036854775808"},                    // 2
		{false, "main.css"},                                // 3
		{false, "/assets/main.css"},                        // 4
		{false, "92233720368547758079223372036854775807"},  // 5
		{false, "9223372036854775808 9223372036854775808"}, // 6
		{false, "-1"},                                      // 7
		{false, "-0"},                                      // 8
		{false, "+1"},                                      // 9
		{false, "18446744073709551615"},                    // 10
		{false, "9223372036854775807"},                     // 11
		{true, "021"},                                      // 12 - leading zeroes are allowed.
		{false, "300"},                                     // 13
		{true, "0"},                                        // 14
		{true, "255"},                                      // 15
		{true, "21"},                                       // 16
	}

	for i, tt := range tests {
		testEvaluatorRaw(t, Uint8, tt.input, reflect.Uint8, tt.pass, i)
	}
}

func TestUint16EvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{false, "astring"},                         // 0
		{false, "astringwith_numb3rS_and_symbol$"}, // 1
		{true, "32321"},                            // 2
		{true, "65535" /* max uint16 */},           // 3
		{true, "0" /* min uint16 */},               // 4
		{false, "-32769"},                          // 5
		{true, "32768"},                            // 6
		{false, "-18446744073709553213213213213213121615"}, // 7
		{false, "42 18446744073709551615"},                 // 8
		{false, "--42"},                                    // 9
		{false, "+42"},                                     // 10
		{false, "main.css"},                                // 11
		{false, "/assets/main.css"},                        // 12
	}

	for i, tt := range tests {
		testEvaluatorRaw(t, Uint16, tt.input, reflect.Uint16, tt.pass, i)
	}
}

func TestUint32EvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{false, "astring"},                         // 0
		{false, "astringwith_numb3rS_and_symbol$"}, // 1
		{true, "32321"},                            // 2
		{true, "1"},                                // 3
		{true, "42"},                               // 4
		{true, "4294967295" /* max uint32*/},       // 5
		{true, "0" /* min uint32 */},               // 6
		{false, "-2147483649"},                     // 7
		{true, "2147483648"},                       // 8
		{false, "-18446744073709553213213213213213121615"}, // 9
		{false, "42 18446744073709551615"},                 // 10
		{false, "--42"},                                    // 11
		{false, "+42"},                                     // 12
		{false, "main.css"},                                // 13
		{false, "/assets/main.css"},                        // 14
	}

	for i, tt := range tests {
		testEvaluatorRaw(t, Uint32, tt.input, reflect.Uint32, tt.pass, i)
	}
}

func TestUint64EvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{false, "astring"},                                 // 0
		{false, "astringwith_numb3rS_and_symbol$"},         // 1
		{false, "-9223372036854775808"},                    // 2
		{false, "main.css"},                                // 3
		{false, "/assets/main.css"},                        // 4
		{false, "92233720368547758079223372036854775807"},  // 5
		{false, "9223372036854775808 9223372036854775808"}, // 6
		{false, "-1"},                                      // 7
		{false, "-0"},                                      // 8
		{false, "+1"},                                      // 9
		{true, "18446744073709551615"},                     // 10
		{true, "9223372036854775807"},                      // 11
		{true, "0"},                                        // 12
	}

	for i, tt := range tests {
		testEvaluatorRaw(t, Uint64, tt.input, reflect.Uint64, tt.pass, i)
	}
}

func TestAlphabeticalEvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{true, "astring"},                          // 0
		{false, "astringwith_numb3rS_and_symbol$"}, // 1
		{false, "32321"},                           // 2
		{false, "main.css"},                        // 3
		{false, "/assets/main.css"},                // 4
	}

	for i, tt := range tests {
		testEvaluatorRaw(t, Alphabetical, tt.input, reflect.String, tt.pass, i)
	}
}

func TestFileEvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{true, "astring"},                          // 0
		{false, "astringwith_numb3rS_and_symbol$"}, // 1
		{true, "32321"},                            // 2
		{true, "main.css"},                         // 3
		{false, "/assets/main.css"},                // 4
	}

	for i, tt := range tests {
		testEvaluatorRaw(t, File, tt.input, reflect.String, tt.pass, i)
	}
}

func TestPathEvaluatorRaw(t *testing.T) {
	pathTests := []struct {
		pass  bool
		input string
	}{
		{true, "astring"},                         // 0
		{true, "astringwith_numb3rS_and_symbol$"}, // 1
		{true, "32321"},                           // 2
		{true, "main.css"},                        // 3
		{true, "/assets/main.css"},                // 4
		{true, "disk/assets/main.css"},            // 5
	}

	for i, tt := range pathTests {
		testEvaluatorRaw(t, Path, tt.input, reflect.String, tt.pass, i)
	}
}

func TestUUIDEvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{true, "978ad967-5fad-4c82-af99-580097ace662"}, // v4
		{true, "c7067f9c-6d43-11eb-9439-0242ac130002"}, // v1
		{false, "astring"},                         // 2
		{false, "astringwith_numb3rS_and_symbol$"}, // 3
		{false, "32321"},                           // 4
		{false, "main.css"},                        // 5
		{false, "/assets/main.css"},                // 6
	}
	for i, tt := range tests {
		testEvaluatorRaw(t, UUID, tt.input, reflect.String, tt.pass, i)
	}
}

func TestMailEvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{true, "kataras2006@hotmail.com"}, // 0
		{true, "iris-go@outlook.com"},     // 1
		{true, "iris-go@mail"},            // 2
		{true, "kataras@k.c"},             // 3
		{false, "www.kataras@"},           // 4
		{false, "name"},                   // 5
		{false, "b-c@"},                   // 6
	}
	for i, tt := range tests {
		testEvaluatorRaw(t, Mail, tt.input, reflect.String, tt.pass, i)
	}
}

func TestEmailEvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass  bool
		input string
	}{
		{true, "kataras2006@hotmail.com"}, // 0
		{true, "iris-go@outlook.com"},     // 1
		{false, "iris-go@mail"},           // 2
		{false, "kataras@k.c"},            // 3
		{false, "www.kataras@"},           // 4
		{false, "name"},                   // 5
		{false, "b-c@"},                   // 6
	}
	for i, tt := range tests {
		testEvaluatorRaw(t, Email, tt.input, reflect.String, tt.pass, i)
	}
}

func TestDateEvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass            bool
		input           string
		timeStringValue string
	}{
		{true, "2022/04/21", "2022-04-21 00:00:00 +0000 UTC"}, // 0
		{true, "2022/12/05", "2022-12-05 00:00:00 +0000 UTC"}, // 1
		{false, "2022/4", ""},      // 2
		{false, "1/4/1", ""},       // 3
		{false, "2022/4/", ""},     // 4
		{false, "2022/4/21/0", ""}, // 5
		{false, "1993", ""},        // 6
	}
	for i, tt := range tests {
		testEvaluatorRaw(t, Date, tt.input, reflect.TypeOf(time.Time{}).Kind(), tt.pass, i)

		if v, ok := Date.Evaluator(tt.input); ok {
			if value, ok := v.(time.Time); ok {
				if expected, got := tt.timeStringValue, value.String(); expected != got {
					t.Fatalf("[%d] expected: %s but got: %s", i, expected, got)
				}
			} else {
				t.Fatalf("[%d] expected to be able to cast as time.Time directly", i)
			}
		}
	}
}

func TestWeekdayEvaluatorRaw(t *testing.T) {
	tests := []struct {
		pass     bool
		input    string
		expected time.Weekday
	}{
		{true, "Monday", time.Monday},        // 0
		{true, "monday", time.Monday},        // 1
		{false, "Sundays", time.Weekday(-1)}, // 2
		{false, "sundays", time.Weekday(-1)}, // 3
		{false, "-1", time.Weekday(-1)},      // 4
		{true, "0000002", time.Tuesday},      // 5
		{true, "3", time.Wednesday},          // 6
		{true, "6", time.Saturday},           // 7
	}
	for i, tt := range tests {
		testEvaluatorRaw(t, Weekday, tt.input, reflect.TypeOf(time.Weekday(0)).Kind(), tt.pass, i)

		if v, ok := Weekday.Evaluator(tt.input); ok {
			if value, ok := v.(time.Weekday); ok {
				if expected, got := tt.expected, value; expected != got {
					t.Fatalf("[%d] expected: %s but got: %s", i, expected, got)
				}
			} else {
				t.Fatalf("[%d] expected to be able to cast as time.Weekday directly", i)
			}
		}
	}
}

func TestConvertBuilderFunc(t *testing.T) {
	fn := func(min uint64, slice []string) func(string) bool {
		return func(paramValue string) bool {
			if expected, got := "ok", paramValue; expected != got {
				t.Fatalf("paramValue is not the expected one: %s vs %s", expected, got)
			}

			if expected, got := uint64(1), min; expected != got {
				t.Fatalf("min argument is not the expected one: %d vs %d", expected, got)
			}

			if expected, got := []string{"name1", "name2"}, slice; len(expected) == len(got) {
				if expected, got := "name1", slice[0]; expected != got {
					t.Fatalf("slice argument[%d] does not contain the expected value: %s vs %s", 0, expected, got)
				}

				if expected, got := "name2", slice[1]; expected != got {
					t.Fatalf("slice argument[%d] does not contain the expected value: %s vs %s", 1, expected, got)
				}
			} else {
				t.Fatalf("slice argument is not the expected one, the length is difference: %d vs %d", len(expected), len(got))
			}

			return true
		}
	}

	evalFunc := convertBuilderFunc(fn)
	if !evalFunc([]string{"1", "[name1,name2]"}).Call([]reflect.Value{reflect.ValueOf("ok")})[0].Interface().(bool) {
		t.Fatalf("failed, it should fail already")
	}

	fnSimplify := func(requestParamValue string) bool {
		return requestParamValue == "kataras"
	}

	evalFunc = convertBuilderFunc(fnSimplify)
	if !evalFunc([]string{}).Call([]reflect.Value{reflect.ValueOf("kataras")})[0].Interface().(bool) {
		t.Fatalf("it should pass, the combile arguments are empty and the given request value is the expected one")
	}

	defer func() {
		if r := recover(); r == nil {
			t.Fatalf("it should panic, the combile arguments are more than one")
		}
	}()

	// should panic.
	evalFunc([]string{"1"}).Call([]reflect.Value{reflect.ValueOf("kataras")})
}
