// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT

package collections

import (
	"sort"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"golang.org/x/exp/maps"
)

func TestCopyMapHasSameValues(t *testing.T) {
	m := map[string]interface{}{
		"foo": 1,
		"bar": 2,
		"baz": 3,
	}
	copied := maps.Clone(m)
	assertMapsEqual(t, m, copied)
}

func TestCopyMapDoesNotShareReferenceToOriginalMap(t *testing.T) {
	m := map[string]interface{}{
		"foo": 1,
		"bar": 2,
		"baz": 3,
	}
	copied := maps.Clone(m)
	assertMapsEqual(t, m, copied)
	delete(m, "foo")
	_, ok := m["foo"]
	assert.False(t, ok)

	val, ok := copied["foo"]
	assert.True(t, ok)
	assert.Equal(t, 1, val)
}

// TODO: could change the implementation to recurse and do a deep copy of everything in the
// input map, but not necessary at the moment. Documenting current behavior here.
func TestCopyMapKeepsShallowReferenceToValuesInMap(t *testing.T) {
	m := map[string]interface{}{
		"foo": 1,
		"bar": 2,
		"baz": map[string]int{"baz": 3, "foo": 1},
	}
	copied := maps.Clone(m)
	assertMapsEqual(t, m, copied)

	baz, ok := m["baz"]
	assert.True(t, ok)
	bazMap, ok := baz.(map[string]int)
	assert.True(t, ok)

	copiedBaz, ok := copied["baz"]
	assert.True(t, ok)
	copiedBazMap, ok := copiedBaz.(map[string]int)
	assert.True(t, ok)
	_, ok = copiedBazMap["baz"]
	assert.True(t, ok)

	// delete from original map
	delete(bazMap, "baz")
	_, ok = bazMap["baz"]
	assert.False(t, ok)
	// deleting from the original map reference also removes it from the copy
	_, ok = copiedBazMap["baz"]
	assert.False(t, ok)
}

func TestMergeMaps(t *testing.T) {
	m1 := map[string]int{"first": 1, "overwrite": 1}
	m2 := map[string]int{"second": 2, "overwrite": 2}
	got := MergeMaps(m1, m2)
	require.Len(t, got, 3)
	value, ok := got["overwrite"]
	require.True(t, ok)
	require.Equal(t, 2, value)
}

func TestGetOrDefault(t *testing.T) {
	m1 := map[string]int{"first": 1, "second": 2}
	got := GetOrDefault(m1, "first", 0)
	require.Equal(t, 1, got)
	got = GetOrDefault(m1, "missing", 0)
	require.Equal(t, 0, got)
}

func TestKeys(t *testing.T) {
	m1 := map[string]int{"first": 1, "second": 2}
	got := maps.Keys(m1)
	sort.Strings(got)
	require.Equal(t, []string{"first", "second"}, got)
}

func TestValues(t *testing.T) {
	m1 := map[string]int{"first": 1, "second": 2}
	got := maps.Values(m1)
	sort.Ints(got)
	require.Equal(t, []int{1, 2}, got)
}

func TestMapSlice(t *testing.T) {
	s := []string{"test", "value"}
	got := MapSlice(s, strings.ToUpper)
	require.Equal(t, []string{"TEST", "VALUE"}, got)
}

func TestWithNewKeys(t *testing.T) {
	base := map[string]int{
		"one": 100,
		"two": 500,
	}
	mapper := map[string]string{
		"two": "five",
	}

	got := WithNewKeys(base, mapper)
	require.Equal(t, map[string]int{
		"one":  100,
		"five": 500,
	}, got)
}

func TestPair(t *testing.T) {
	pair := NewPair("key", "value")
	require.Equal(t, "key", pair.Key)
	require.Equal(t, "value", pair.Value)
}

func TestSet(t *testing.T) {
	set := NewSet(1, 2)
	require.True(t, set.Contains(1))
	set.Remove(1)
	require.False(t, set.Contains(1))
	require.Equal(t, []int{2}, maps.Keys(set))

	other := NewSet(1, 2, 3)
	// different sizes
	assert.False(t, set.Equal(other))
	set.Add(1, 4)
	// same size, different keys
	assert.False(t, set.Equal(other))
	set.Remove(4)
	// set {1,2}, other {1,2,3}
	assert.True(t, other.ContainsAll(set))
	assert.False(t, set.ContainsAll(other))
	set.Add(3)
	assert.True(t, set.Equal(other))
}

func TestRange(t *testing.T) {
	fn := func(key int) bool {
		return key > 0
	}
	assert.True(t, Range([]int{1, 2, 3}, fn))
	assert.False(t, Range([]int{1, 0, -1}, fn))
}

func assertMapsEqual(t *testing.T, m1, m2 map[string]interface{}) {
	t.Helper()

	assert.Equal(t, len(m1), len(m2))

	for k, expected := range m1 {
		actual, ok := m2[k]
		assert.True(t, ok)
		assert.Equal(t, expected, actual)
	}
}
