package form

import (
	"fmt"
	"net/url"
	"reflect"
	"strconv"
	"testing"

	"google.golang.org/protobuf/reflect/protoreflect"

	"github.com/go-kratos/kratos/v2/internal/testdata/complex"
)

func TestDecodeValues(t *testing.T) {
	form, err := url.ParseQuery("a=19&age=18&b=true&bool=false&byte=MTIz&bytes=MTIz&count=3&d=22.22&double=12.33&duration=" +
		"2m0.000000022s&field=1%2C2&float=12.34&id=2233&int32=32&int64=64&numberOne=2233&price=11.23&sex=woman&simples=3344&" +
		"simples=5566&string=go-kratos&timestamp=1970-01-01T00%3A00%3A20.000000002Z&uint32=32&uint64=64&very_simple.component=5566")
	if err != nil {
		t.Fatal(err)
	}

	comp := &complex.Complex{}
	err = DecodeValues(comp, form)
	if err != nil {
		t.Fatal(err)
	}
	if comp.Id != int64(2233) {
		t.Errorf("want %v, got %v", int64(2233), comp.Id)
	}
	if comp.NoOne != "2233" {
		t.Errorf("want %v, got %v", "2233", comp.NoOne)
	}
	if comp.Simple == nil {
		t.Fatalf("want %v, got %v", nil, comp.Simple)
	}
	if comp.Simple.Component != "5566" {
		t.Errorf("want %v, got %v", "5566", comp.Simple.Component)
	}
	if len(comp.Simples) != 2 {
		t.Fatalf("want %v, got %v", 2, len(comp.Simples))
	}
	if comp.Simples[0] != "3344" {
		t.Errorf("want %v, got %v", "3344", comp.Simples[0])
	}
	if comp.Simples[1] != "5566" {
		t.Errorf("want %v, got %v", "5566", comp.Simples[1])
	}
}

func TestGetFieldDescriptor(t *testing.T) {
	comp := &complex.Complex{}

	field := getFieldDescriptor(comp.ProtoReflect(), "id")
	if field.Kind() != protoreflect.Int64Kind {
		t.Errorf("want: %d, got: %d", protoreflect.Int64Kind, field.Kind())
	}

	field = getFieldDescriptor(comp.ProtoReflect(), "simples")
	if field.Kind() != protoreflect.StringKind {
		t.Errorf("want: %d, got: %d", protoreflect.StringKind, field.Kind())
	}
}

func TestPopulateRepeatedField(t *testing.T) {
	query, err := url.ParseQuery("simples=3344&simples=5566")
	if err != nil {
		t.Fatal(err)
	}
	comp := &complex.Complex{}
	field := getFieldDescriptor(comp.ProtoReflect(), "simples")

	err = populateRepeatedField(field, comp.ProtoReflect().Mutable(field).List(), query["simples"])
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual([]string{"3344", "5566"}, comp.GetSimples()) {
		t.Errorf("want: %v, got: %v", []string{"3344", "5566"}, comp.GetSimples())
	}
}

func TestPopulateMapField(t *testing.T) {
	query, err := url.ParseQuery("map%5Bkratos%5D=https://go-kratos.dev/")
	if err != nil {
		t.Fatal(err)
	}
	comp := &complex.Complex{}
	field := getFieldDescriptor(comp.ProtoReflect(), "map")
	// Fill the comp map field with the url query values
	err = populateMapField(field, comp.ProtoReflect().Mutable(field).Map(), []string{"kratos"}, query["map[kratos]"])
	if err != nil {
		t.Fatal(err)
	}
	// Get the comp map field value
	if query["map[kratos]"][0] != comp.Map["kratos"] {
		t.Errorf("want: %s, got: %s", query["map[kratos]"], comp.Map["kratos"])
	}
}

func TestParseField(t *testing.T) {
	tests := []struct {
		name                    string
		fieldName               string
		protoReflectKind        protoreflect.Kind
		value                   string
		targetProtoReflectValue protoreflect.Value
		targetErr               error
	}{
		{
			name:                    "BoolKind",
			fieldName:               "b",
			protoReflectKind:        protoreflect.BoolKind,
			value:                   "true",
			targetProtoReflectValue: protoreflect.ValueOfBool(true),
			targetErr:               nil,
		},
		{
			name:                    "BoolKind",
			fieldName:               "b",
			protoReflectKind:        protoreflect.BoolKind,
			value:                   "a",
			targetProtoReflectValue: protoreflect.Value{},
			targetErr:               &strconv.NumError{Func: "ParseBool", Num: "a", Err: strconv.ErrSyntax},
		},
		{
			name:                    "EnumKind",
			fieldName:               "sex",
			protoReflectKind:        protoreflect.EnumKind,
			value:                   "1",
			targetProtoReflectValue: protoreflect.ValueOfEnum(1),
			targetErr:               nil,
		},
		{
			name:                    "EnumKind",
			fieldName:               "sex",
			protoReflectKind:        protoreflect.EnumKind,
			value:                   "2",
			targetProtoReflectValue: protoreflect.Value{},
			targetErr:               fmt.Errorf("%q is not a valid value", "2"),
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			comp := &complex.Complex{}
			field := getFieldDescriptor(comp.ProtoReflect(), test.fieldName)
			if test.protoReflectKind != field.Kind() {
				t.Fatalf("want: %d, got: %d", test.protoReflectKind, field.Kind())
			}
			val, err := parseField(field, test.value)
			if !reflect.DeepEqual(test.targetErr, err) {
				t.Fatalf("want: %s, got: %s", test.targetErr, err)
			}
			if !reflect.DeepEqual(test.targetProtoReflectValue, val) {
				t.Errorf("want: %s, got: %s", test.targetProtoReflectValue, val)
			}
		})
	}
}

func TestJsonSnakeCase(t *testing.T) {
	tests := []struct {
		camelCase string
		snakeCase string
	}{
		{
			"userId", "user_id",
		},
		{
			"user", "user",
		},
		{
			"userIdAndUsername", "user_id_and_username",
		},
		{
			"", "",
		},
	}
	for _, test := range tests {
		t.Run(test.camelCase, func(t *testing.T) {
			snake := jsonSnakeCase(test.camelCase)
			if snake != test.snakeCase {
				t.Errorf("want: %s, got: %s", test.snakeCase, snake)
			}
		})
	}
}

func TestIsASCIIUpper(t *testing.T) {
	tests := []struct {
		b     byte
		upper bool
	}{
		{
			'A', true,
		},
		{
			'a', false,
		},
		{
			',', false,
		},
		{
			'1', false,
		},
		{
			' ', false,
		},
	}
	for _, test := range tests {
		t.Run(string(test.b), func(t *testing.T) {
			upper := isASCIIUpper(test.b)
			if test.upper != upper {
				t.Errorf("'%s' is not ascii upper", string(test.b))
			}
		})
	}
}