// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

// DO NOT MODIFY. Generated by gotmpl.
// source: internal/shared/attrnorm/dedup_test.go.tmpl

package attrnorm

import (
	"testing"

	"github.com/google/go-cmp/cmp"

	"go.opentelemetry.io/otel/attribute"
)

var cmpValue = cmp.AllowUnexported(attribute.Value{})

func TestValue(t *testing.T) {
	tests := []struct {
		name        string
		value       attribute.Value
		want        attribute.Value
		wantChanged bool
	}{
		{
			name: "unique map",
			value: attribute.MapValue(
				attribute.String("one", "1"),
				attribute.String("two", "2"),
			),
			want: attribute.MapValue(
				attribute.String("one", "1"),
				attribute.String("two", "2"),
			),
			wantChanged: false,
		},
		{
			name: "duplicate map",
			value: attribute.MapValue(
				attribute.String("one", "1"),
				attribute.String("one", "2"),
				attribute.String("two", "3"),
			),
			want: attribute.MapValue(
				attribute.String("one", "2"),
				attribute.String("two", "3"),
			),
			wantChanged: true,
		},
		{
			name: "duplicate map after prior key",
			value: attribute.MapValue(
				attribute.String("a", "1"),
				attribute.String("b", "2"),
				attribute.String("b", "3"),
				attribute.String("c", "4"),
			),
			want: attribute.MapValue(
				attribute.String("a", "1"),
				attribute.String("b", "3"),
				attribute.String("c", "4"),
			),
			wantChanged: true,
		},
		{
			name: "nested map",
			value: attribute.MapValue(
				attribute.Map(
					"outer",
					attribute.String("inner", "1"),
					attribute.String("inner", "2"),
				),
			),
			want: attribute.MapValue(
				attribute.Map(
					"outer",
					attribute.String("inner", "2"),
				),
			),
			wantChanged: true,
		},
		{
			name: "map inside slice",
			value: attribute.SliceValue(
				attribute.StringValue("prior"),
				attribute.MapValue(
					attribute.String("inner", "1"),
					attribute.String("inner", "2"),
				),
				attribute.StringValue("tail"),
			),
			want: attribute.SliceValue(
				attribute.StringValue("prior"),
				attribute.MapValue(
					attribute.String("inner", "2"),
				),
				attribute.StringValue("tail"),
			),
			wantChanged: true,
		},
		{
			name: "unique slice",
			value: attribute.SliceValue(
				attribute.StringValue("one"),
				attribute.IntValue(2),
			),
			want: attribute.SliceValue(
				attribute.StringValue("one"),
				attribute.IntValue(2),
			),
			wantChanged: false,
		},
		{
			name: "empty and invalid keys",
			value: attribute.MapValue(
				attribute.KeyValue{},
				attribute.String("", "empty"),
				attribute.String("valid", "value"),
			),
			want: attribute.MapValue(
				attribute.String("", "empty"),
				attribute.String("valid", "value"),
			),
			wantChanged: true,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			got, changed := Value(test.value)
			if changed != test.wantChanged {
				t.Fatalf("Value() changed = %v, want %v", changed, test.wantChanged)
			}
			if diff := cmp.Diff(test.want, got, cmpValue); diff != "" {
				t.Fatalf("Value() mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

func TestValueNoopAllocationFree(t *testing.T) {
	value := attribute.MapValue(
		attribute.String("one", "1"),
		attribute.String("two", "2"),
	)
	var got attribute.Value

	allocs := testing.AllocsPerRun(1000, func() {
		got, _ = Value(value)
	})
	if allocs != 0 {
		t.Fatalf("Value() allocations = %v, want 0", allocs)
	}
	if _, changed := Value(value); changed {
		t.Fatal("Value() changed a no-op input")
	}
	if diff := cmp.Diff(value, got, cmpValue); diff != "" {
		t.Fatalf("Value() mismatch (-want +got):\n%s", diff)
	}
}

func TestValueStorageShapes(t *testing.T) {
	for n := 0; n <= 6; n++ {
		t.Run("map", func(t *testing.T) {
			kvs := make([]attribute.KeyValue, n)
			for i := range kvs {
				kvs[i] = attribute.Int(string(rune('a'+i)), i)
			}
			value := attribute.MapValue(kvs...)

			got, changed := Value(value)
			if changed {
				t.Fatal("Value() changed a no-op input")
			}
			if diff := cmp.Diff(value, got, cmpValue); diff != "" {
				t.Fatalf("Value() mismatch (-want +got):\n%s", diff)
			}
		})
		t.Run("slice", func(t *testing.T) {
			values := make([]attribute.Value, n)
			for i := range values {
				values[i] = attribute.IntValue(i)
			}
			value := attribute.SliceValue(values...)

			got, changed := Value(value)
			if changed {
				t.Fatal("Value() changed a no-op input")
			}
			if diff := cmp.Diff(value, got, cmpValue); diff != "" {
				t.Fatalf("Value() mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

func TestKeyValue(t *testing.T) {
	kv := attribute.Map(
		"map",
		attribute.String("nested", "first"),
		attribute.String("nested", "second"),
	)
	want := attribute.Map(
		"map",
		attribute.String("nested", "second"),
	)

	got, changed := KeyValue(kv)
	if !changed {
		t.Fatal("KeyValue() changed = false, want true")
	}
	if diff := cmp.Diff(want, got, cmpValue); diff != "" {
		t.Fatalf("KeyValue() mismatch (-want +got):\n%s", diff)
	}
}

func TestKeyValuesNoopReturnsInput(t *testing.T) {
	kvs := []attribute.KeyValue{
		attribute.String("one", "1"),
		attribute.Map("two", attribute.String("nested", "value")),
	}

	got, changed := KeyValues(kvs)
	if changed {
		t.Fatal("KeyValues() changed a no-op input")
	}
	if len(got) != len(kvs) {
		t.Fatalf("KeyValues() length = %d, want %d", len(got), len(kvs))
	}
	if &got[0] != &kvs[0] {
		t.Fatal("KeyValues() copied a no-op input")
	}
}

func TestKeyValues(t *testing.T) {
	kvs := []attribute.KeyValue{
		attribute.String("top", "value"),
		attribute.Map(
			"map",
			attribute.String("nested", "first"),
			attribute.String("nested", "second"),
		),
		attribute.String("tail", "value"),
	}
	want := []attribute.KeyValue{
		attribute.String("top", "value"),
		attribute.Map(
			"map",
			attribute.String("nested", "second"),
		),
		attribute.String("tail", "value"),
	}

	got, changed := KeyValues(kvs)
	if !changed {
		t.Fatal("KeyValues() changed = false, want true")
	}
	if diff := cmp.Diff(want, got, cmpValue); diff != "" {
		t.Fatalf("KeyValues() mismatch (-want +got):\n%s", diff)
	}
}

func TestSet(t *testing.T) {
	set := attribute.NewSet(
		attribute.String("a-top", "value"),
		attribute.Map(
			"m-map",
			attribute.String("nested", "first"),
			attribute.String("nested", "second"),
		),
		attribute.String("z-tail", "value"),
	)
	want := attribute.NewSet(
		attribute.String("a-top", "value"),
		attribute.Map(
			"m-map",
			attribute.String("nested", "second"),
		),
		attribute.String("z-tail", "value"),
	)

	got, changed := Set(set)
	if !changed {
		t.Fatal("Set() changed = false, want true")
	}
	if diff := cmp.Diff(want.ToSlice(), got.ToSlice(), cmpValue); diff != "" {
		t.Fatalf("Set() mismatch (-want +got):\n%s", diff)
	}
}

func TestSetNoop(t *testing.T) {
	set := attribute.NewSet(
		attribute.String("top", "value"),
		attribute.Map("map", attribute.String("nested", "value")),
	)

	got, changed := Set(set)
	if changed {
		t.Fatal("Set() changed a no-op input")
	}
	if !got.Equals(&set) {
		t.Fatal("Set() changed a no-op input")
	}
}

func TestSetEmpty(t *testing.T) {
	set := attribute.Set{}

	got, changed := Set(set)
	if changed {
		t.Fatal("Set() changed an empty input")
	}
	if !got.Equals(&set) {
		t.Fatal("Set() changed an empty input")
	}
}

func TestInvalidArrayStorage(t *testing.T) {
	if got := arrayLen("invalid", valueType); got != 0 {
		t.Fatalf("arrayLen() = %d, want 0", got)
	}

	if got := arrayAt[attribute.Value]("invalid", valueType, 0); got.Type() != attribute.EMPTY {
		t.Fatalf("arrayAt() = %v, want empty value", got)
	}
	if got := arrayAt[attribute.Value](
		[1]attribute.Value{attribute.StringValue("value")},
		keyValueType,
		0,
	); got.Type() != attribute.EMPTY {
		t.Fatalf("arrayAt() = %v, want empty value", got)
	}
	if got := arrayAt[attribute.Value](
		[1]attribute.Value{attribute.StringValue("value")},
		valueType,
		-1,
	); got.Type() != attribute.EMPTY {
		t.Fatalf("arrayAt() = %v, want empty value", got)
	}
}
