package collections_test

import (
	"fmt"
	"slices"
	"testing"

	"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
	"github.com/go-json-experiment/json"
	"gotest.tools/v3/assert"
)

func TestOrderedMap(t *testing.T) {
	t.Parallel()

	var m collections.OrderedMap[int, string]

	assert.Assert(t, !m.Has(1))

	const (
		N     = 1000
		start = 1
		end   = start + N
	)

	// Seed the map with ascending keys and values for easier testing.
	for i := start; i < end; i++ {
		m.Set(i, padInt(i))
	}

	assert.Equal(t, m.Size(), N)

	// Attempt to overwrite existing keys in reverse order.
	for i := end - 1; i >= start; i-- {
		m.Set(i, padInt(i))
	}

	assert.Equal(t, m.Size(), N)

	for i := start; i < end; i++ {
		v, ok := m.Get(i)
		assert.Assert(t, ok)
		assert.Equal(t, v, padInt(i))
	}

	for k, v := range m.Entries() {
		assert.Equal(t, v, padInt(k))
	}

	keys := slices.Collect(m.Keys())
	assert.Equal(t, len(keys), N)
	assert.Assert(t, slices.IsSorted(keys))

	values := slices.Collect(m.Values())
	assert.Equal(t, len(values), N)
	assert.Assert(t, slices.IsSorted(values))

	var firstKey int
	for k := range m.Keys() {
		firstKey = k
		break
	}
	assert.Equal(t, firstKey, start)

	var firstValue string
	for v := range m.Values() {
		firstValue = v
		break
	}
	assert.Equal(t, firstValue, padInt(start))

	for k, v := range m.Entries() {
		firstKey = k
		firstValue = v
		break
	}

	assert.Equal(t, firstKey, start)
	assert.Equal(t, firstValue, padInt(start))

	for i := start + 1; i < end; i++ {
		v, ok := m.Delete(i)
		assert.Assert(t, ok)
		assert.Equal(t, v, padInt(i))
		assert.Assert(t, !m.Has(i))

		v, ok = m.Get(i)
		assert.Assert(t, !ok)
		assert.Equal(t, v, "")

		v, ok = m.Delete(i)
		assert.Assert(t, !ok)
		assert.Equal(t, v, "")
	}

	assert.Equal(t, m.Size(), 1)
	assert.Assert(t, m.Has(start))

	v, ok := m.Delete(start)
	assert.Assert(t, ok)
	assert.Equal(t, v, padInt(start))

	assert.Equal(t, m.Size(), 0)
}

func TestOrderedMapClone(t *testing.T) {
	t.Parallel()

	m := &collections.OrderedMap[int, string]{}
	m.Set(1, "one")
	m.Set(2, "two")

	clone := m.Clone()

	assert.Assert(t, clone != m)
	assert.Equal(t, clone.Size(), 2)
	assert.DeepEqual(t, slices.Collect(clone.Keys()), []int{1, 2})
	assert.DeepEqual(t, slices.Collect(clone.Values()), []string{"one", "two"})

	v, ok := clone.Get(1)
	assert.Assert(t, ok)
	assert.Equal(t, v, "one")

	m.Delete(1)

	assert.Equal(t, m.Size(), 1)
	assert.Equal(t, clone.Size(), 2)
	assert.DeepEqual(t, slices.Collect(clone.Keys()), []int{1, 2})
	assert.DeepEqual(t, slices.Collect(clone.Values()), []string{"one", "two"})
}

func TestOrderedMapClear(t *testing.T) {
	t.Parallel()

	var m collections.OrderedMap[int, string]
	m.Set(1, "one")
	m.Set(2, "two")

	m.Clear()

	assert.Equal(t, m.Size(), 0)
}

func padInt(n int) string {
	return fmt.Sprintf("%10d", n)
}

func TestOrderedMapWithSizeHint(t *testing.T) { //nolint:paralleltest
	const N = 1024

	allocs := testing.AllocsPerRun(10, func() {
		m := collections.NewOrderedMapWithSizeHint[int, int](N)
		for i := range N {
			m.Set(i, i)
		}
	})

	assert.Assert(t, allocs < 10, "allocs = %v", allocs)
}

func TestOrderedMapUnmarshalJSON(t *testing.T) {
	t.Parallel()

	t.Run("UnmarshalJSONV2", func(t *testing.T) {
		t.Parallel()
		testOrderedMapUnmarshalJSON(t, func(in []byte, out any) error { return json.Unmarshal(in, out) })
	})
}

func testOrderedMapUnmarshalJSON(t *testing.T, unmarshal func([]byte, any) error) {
	var m collections.OrderedMap[string, any]
	err := unmarshal([]byte(`{"a": 1, "b": "two", "c": { "d": 4 } }`), &m)
	assert.NilError(t, err)

	assert.Equal(t, m.Size(), 3)
	assert.Equal(t, m.GetOrZero("a"), float64(1))

	err = unmarshal([]byte(`null`), &m)
	assert.NilError(t, err)

	err = unmarshal([]byte(`"foo"`), &m)
	assert.ErrorContains(t, err, "cannot unmarshal non-object JSON value into Map")

	var invalidMap collections.OrderedMap[int, any]
	err = unmarshal([]byte(`{"a": 1, "b": "two"}`), &invalidMap)
	assert.ErrorContains(t, err, "unmarshal")
}
