package mapstructure

import (
	"encoding/json"
	"testing"
)

type Person struct {
	Name   string
	Age    int
	Emails []string
	Extra  map[string]string
}

func Benchmark_Decode(b *testing.B) {
	input := map[string]any{
		"name":   "Mitchell",
		"age":    91,
		"emails": []string{"one", "two", "three"},
		"extra": map[string]string{
			"twitter": "mitchellh",
		},
	}

	var result Person
	for i := 0; i < b.N; i++ {
		Decode(input, &result)
	}
}

// decodeViaJSON takes the map data and passes it through encoding/json to convert it into the
// given Go native structure pointed to by v. v must be a pointer to a struct.
func decodeViaJSON(data any, v any) error {
	// Perform the task by simply marshalling the input into JSON,
	// then unmarshalling it into target native Go struct.
	b, err := json.Marshal(data)
	if err != nil {
		return err
	}
	return json.Unmarshal(b, v)
}

func Benchmark_DecodeViaJSON(b *testing.B) {
	input := map[string]any{
		"name":   "Mitchell",
		"age":    91,
		"emails": []string{"one", "two", "three"},
		"extra": map[string]string{
			"twitter": "mitchellh",
		},
	}

	var result Person
	for i := 0; i < b.N; i++ {
		decodeViaJSON(input, &result)
	}
}

func Benchmark_JSONUnmarshal(b *testing.B) {
	input := map[string]any{
		"name":   "Mitchell",
		"age":    91,
		"emails": []string{"one", "two", "three"},
		"extra": map[string]string{
			"twitter": "mitchellh",
		},
	}

	inputB, err := json.Marshal(input)
	if err != nil {
		b.Fatal("Failed to marshal test input:", err)
	}

	var result Person
	for i := 0; i < b.N; i++ {
		json.Unmarshal(inputB, &result)
	}
}

func Benchmark_DecodeBasic(b *testing.B) {
	input := map[string]any{
		"vstring":     "foo",
		"vint":        42,
		"Vuint":       42,
		"vbool":       true,
		"Vfloat":      42.42,
		"vsilent":     true,
		"vdata":       42,
		"vjsonInt":    json.Number("1234"),
		"vjsonFloat":  json.Number("1234.5"),
		"vjsonNumber": json.Number("1234.5"),
	}

	for i := 0; i < b.N; i++ {
		var result Basic
		Decode(input, &result)
	}
}

func Benchmark_DecodeEmbedded(b *testing.B) {
	input := map[string]any{
		"vstring": "foo",
		"Basic": map[string]any{
			"vstring": "innerfoo",
		},
		"vunique": "bar",
	}

	var result Embedded
	for i := 0; i < b.N; i++ {
		Decode(input, &result)
	}
}

func Benchmark_DecodeTypeConversion(b *testing.B) {
	input := map[string]any{
		"IntToFloat":    42,
		"IntToUint":     42,
		"IntToBool":     1,
		"IntToString":   42,
		"UintToInt":     42,
		"UintToFloat":   42,
		"UintToBool":    42,
		"UintToString":  42,
		"BoolToInt":     true,
		"BoolToUint":    true,
		"BoolToFloat":   true,
		"BoolToString":  true,
		"FloatToInt":    42.42,
		"FloatToUint":   42.42,
		"FloatToBool":   42.42,
		"FloatToString": 42.42,
		"StringToInt":   "42",
		"StringToUint":  "42",
		"StringToBool":  "1",
		"StringToFloat": "42.42",
		"SliceToMap":    []any{},
		"MapToSlice":    map[string]any{},
	}

	var resultStrict TypeConversionResult
	for i := 0; i < b.N; i++ {
		Decode(input, &resultStrict)
	}
}

func Benchmark_DecodeMap(b *testing.B) {
	input := map[string]any{
		"vfoo": "foo",
		"vother": map[any]any{
			"foo": "foo",
			"bar": "bar",
		},
	}

	var result Map
	for i := 0; i < b.N; i++ {
		Decode(input, &result)
	}
}

func Benchmark_DecodeMapOfStruct(b *testing.B) {
	input := map[string]any{
		"value": map[string]any{
			"foo": map[string]string{"vstring": "one"},
			"bar": map[string]string{"vstring": "two"},
		},
	}

	var result MapOfStruct
	for i := 0; i < b.N; i++ {
		Decode(input, &result)
	}
}

func Benchmark_DecodeSlice(b *testing.B) {
	input := map[string]any{
		"vfoo": "foo",
		"vbar": []string{"foo", "bar", "baz"},
	}

	var result Slice
	for i := 0; i < b.N; i++ {
		Decode(input, &result)
	}
}

func Benchmark_DecodeSliceOfStruct(b *testing.B) {
	input := map[string]any{
		"value": []map[string]any{
			{"vstring": "one"},
			{"vstring": "two"},
		},
	}

	var result SliceOfStruct
	for i := 0; i < b.N; i++ {
		Decode(input, &result)
	}
}

func Benchmark_DecodeWeaklyTypedInput(b *testing.B) {
	// This input can come from anywhere, but typically comes from
	// something like decoding JSON, generated by a weakly typed language
	// such as PHP.
	input := map[string]any{
		"name":   123,              // number => string
		"age":    "42",             // string => number
		"emails": map[string]any{}, // empty map => empty array
	}

	var result Person
	config := &DecoderConfig{
		WeaklyTypedInput: true,
		Result:           &result,
	}

	decoder, err := NewDecoder(config)
	if err != nil {
		panic(err)
	}

	for i := 0; i < b.N; i++ {
		decoder.Decode(input)
	}
}

func Benchmark_DecodeMetadata(b *testing.B) {
	input := map[string]any{
		"name":  "Mitchell",
		"age":   91,
		"email": "foo@bar.com",
	}

	var md Metadata
	var result Person
	config := &DecoderConfig{
		Metadata: &md,
		Result:   &result,
	}

	decoder, err := NewDecoder(config)
	if err != nil {
		panic(err)
	}

	for i := 0; i < b.N; i++ {
		decoder.Decode(input)
	}
}

func Benchmark_DecodeMetadataEmbedded(b *testing.B) {
	input := map[string]any{
		"vstring": "foo",
		"vunique": "bar",
	}

	var md Metadata
	var result EmbeddedSquash
	config := &DecoderConfig{
		Metadata: &md,
		Result:   &result,
	}

	decoder, err := NewDecoder(config)
	if err != nil {
		b.Fatalf("err: %s", err)
	}

	for i := 0; i < b.N; i++ {
		decoder.Decode(input)
	}
}

func Benchmark_DecodeTagged(b *testing.B) {
	input := map[string]any{
		"foo": "bar",
		"bar": "value",
	}

	var result Tagged
	for i := 0; i < b.N; i++ {
		Decode(input, &result)
	}
}

func Benchmark_DecodeWithRemainingFields(b *testing.B) {
	type Person struct {
		Name  string
		Other map[string]any `mapstructure:",remain"`
	}

	input := map[string]any{
		"name": "Luffy",
		"age":  19,
		"powers": []string{
			"Rubber Man",
			"Conqueror Haki",
		},
	}

	for i := 0; i < b.N; i++ {
		// Decoding Map -> Struct
		var person Person
		_ = Decode(input, &person)

		// Decoding Struct -> Map
		result := make(map[string]any)
		_ = Decode(&person, &result)
	}
}
