package reflect

import (
	"net/url"
	"reflect"
	rfl "reflect"
	"testing"
)

func TestStructFieldsMap(t *testing.T) {
	type NestedStr struct {
		BBB string
		CCC int
	}
	type Str struct {
		Name   []string `json:"name" codec:"flatten"`
		XXX    string   `json:"xxx"`
		Nested NestedStr
	}

	val := &Str{Name: []string{"first", "second"}, XXX: "ttt", Nested: NestedStr{BBB: "ddd", CCC: 9}}
	fields, err := StructFieldsMap(val)
	if err != nil {
		t.Fatal(err)
	}
	if v, ok := fields["Nested.BBB"]; !ok || v != "ddd" {
		t.Fatalf("invalid field from %v", fields)
	}
}

func TestStructFields(t *testing.T) {
	type NestedStr struct {
		BBB string
		CCC int
	}
	type Str struct {
		Name   []string `json:"name" codec:"flatten"`
		XXX    string   `json:"xxx"`
		Nested NestedStr
	}

	val := &Str{Name: []string{"first", "second"}, XXX: "ttt", Nested: NestedStr{BBB: "ddd", CCC: 9}}
	fields, err := StructFields(val)
	if err != nil {
		t.Fatal(err)
	}
	var ok bool
	for _, field := range fields {
		if field.Path == "Nested.CCC" {
			ok = true
		}
	}
	if !ok {
		t.Fatalf("struct fields returns invalid path: %v", fields)
	}
}

func TestStructByPath(t *testing.T) {
	type NestedStr struct {
		BBB string
		CCC int
	}
	type Str struct {
		Name   []string `json:"name" codec:"flatten"`
		XXX    string   `json:"xxx"`
		Nested NestedStr
	}

	val := &Str{Name: []string{"first", "second"}, XXX: "ttt", Nested: NestedStr{BBB: "ddd", CCC: 9}}
	field, err := StructFieldByPath(val, "Nested.CCC")
	if err != nil {
		t.Fatal(err)
	}
	if rfl.Indirect(reflect.ValueOf(field)).Int() != 9 {
		t.Fatalf("invalid elem returned: %v", field)
	}
}

func TestStructByTag(t *testing.T) {
	type Str struct {
		Name []string `json:"name" codec:"flatten"`
	}

	val := &Str{Name: []string{"first", "second"}}

	iface, err := StructFieldByTag(val, "codec", "flatten")
	if err != nil {
		t.Fatal(err)
	}

	if v, ok := iface.(*[]string); !ok {
		t.Fatalf("not *[]string %v", iface)
	} else if len(*v) != 2 {
		t.Fatalf("invalid number %v", iface)
	}
}

func TestStructByName(t *testing.T) {
	type Str struct {
		Name []string `json:"name" codec:"flatten"`
	}

	val := &Str{Name: []string{"first", "second"}}

	iface, err := StructFieldByName(val, "Name")
	if err != nil {
		t.Fatal(err)
	}

	if v, ok := iface.(*[]string); !ok {
		t.Fatalf("not *[]string %v", iface)
	} else if len(*v) != 2 {
		t.Fatalf("invalid number %v", iface)
	}
}

func TestStructURLValues(t *testing.T) {
	type Str struct {
		Str  *Str   `json:"str"`
		Name string `json:"name"`
		Args []int  `json:"args"`
	}

	val := &Str{Name: "test_name", Args: []int{1, 2, 3}, Str: &Str{Name: "nested_name"}}
	data, err := StructURLValues(val, "", []string{"json"})
	if err != nil {
		t.Fatal(err)
	}

	if data.Get("name") != "test_name" {
		t.Fatalf("invalid data: %v", data)
	}
}

func TestURLSliceVars(t *testing.T) {
	u, err := url.Parse("http://localhost/v1/test/call/my_name?key=arg1&key=arg2&key=arg3")
	if err != nil {
		t.Fatal(err)
	}

	mp, err := URLMap(u.RawQuery)
	if err != nil {
		t.Fatal(err)
	}

	v, ok := mp["key"]
	if !ok {
		t.Fatalf("key not exists: %#+v", mp)
	}

	vm, ok := v.([]interface{})
	if !ok {
		t.Fatalf("invalid key value")
	}

	if len(vm) != 3 {
		t.Fatalf("missing key value: %#+v", mp)
	}
}

func TestURLVars(t *testing.T) {
	u, err := url.Parse("http://localhost/v1/test/call/my_name?req=key&arg1=arg1&arg2=12345&nested.string_args=str1&nested.string_args=str2&arg2=54321")
	if err != nil {
		t.Fatal(err)
	}

	mp, err := URLMap(u.RawQuery)
	if err != nil {
		t.Fatal(err)
	}
	_ = mp
}

func TestIsZero(t *testing.T) {
	testStr1 := struct {
		Name   string
		Value  string
		Nested struct {
			NestedName string
		}
	}{
		Name:  "test_name",
		Value: "test_value",
	}
	testStr1.Nested.NestedName = "nested_name"

	if ok := IsZero(testStr1); ok {
		t.Fatalf("zero ret on non zero struct: %#+v", testStr1)
	}

	testStr1.Name = ""
	testStr1.Value = ""
	testStr1.Nested.NestedName = ""
	if ok := IsZero(testStr1); !ok {
		t.Fatalf("non zero ret on zero struct: %#+v", testStr1)
	}

	type testStr3 struct {
		Nested string
	}
	type testStr2 struct {
		Nested *testStr3
		Name   string
	}
	vtest := &testStr2{
		Name:   "test_name",
		Nested: &testStr3{Nested: "nested_name"},
	}
	if ok := IsZero(vtest); ok {
		t.Fatalf("zero ret on non zero struct: %#+v", vtest)
	}
	vtest.Nested = nil
	vtest.Name = ""
	if ok := IsZero(vtest); !ok {
		t.Fatalf("non zero ret on zero struct: %#+v", vtest)
	}

	// t.Logf("XX %#+v\n", ok)
}