// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package utils

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestRangeMapUint32(t *testing.T) {
	r := NewRangeMap[uint32, uint32](2)

	// getting value for any key should be 0 default
	value, err := r.GetValue(33333)
	require.NoError(t, err)
	require.Equal(t, uint32(0), value)

	expectedRanges := []rangeVal[uint32, uint32]{
		{
			start: 0,
			end:   0,
			value: 0,
		},
	}
	require.Equal(t, expectedRanges, r.ranges)

	// add an exclusion, should create a new range
	err = r.ExcludeRange(10, 11)
	require.NoError(t, err)

	expectedRanges = []rangeVal[uint32, uint32]{
		{
			start: 0,
			end:   9,
			value: 0,
		},
		{
			start: 11,
			end:   0,
			value: 1,
		},
	}
	require.Equal(t, expectedRanges, r.ranges)

	// getting value in old range should return 0
	value, err = r.GetValue(6)
	require.NoError(t, err)
	require.Equal(t, uint32(0), value)

	// newer should return 1
	value, err = r.GetValue(11)
	require.NoError(t, err)
	require.Equal(t, uint32(1), value)

	// excluded range should return error
	_, err = r.GetValue(10)
	require.ErrorIs(t, err, errKeyExcluded)

	// out-of-order exclusion should return error
	err = r.ExcludeRange(9, 10)
	require.ErrorIs(t, err, errReversedOrder)

	// flipped exclusion should return error
	err = r.ExcludeRange(12, 11)
	require.ErrorIs(t, err, errReversedOrder)
	err = r.ExcludeRange(11, 11)
	require.ErrorIs(t, err, errReversedOrder)

	// add adjacent exclusion range of length = 1
	err = r.ExcludeRange(11, 12)
	require.NoError(t, err)

	expectedRanges = []rangeVal[uint32, uint32]{
		{
			start: 0,
			end:   9,
			value: 0,
		},
		{
			start: 12,
			end:   0,
			value: 2,
		},
	}
	require.Equal(t, expectedRanges, r.ranges)

	// excluded range should return error, now is excluded because exclusion range could be extended
	_, err = r.GetValue(11)
	require.ErrorIs(t, err, errKeyExcluded)

	// getting value in old range should return 0
	_, err = r.GetValue(6)
	require.NoError(t, err)

	// newer should return 2
	value, err = r.GetValue(12)
	require.NoError(t, err)
	require.Equal(t, uint32(2), value)

	// add adjacent exclusion range of length = 10
	err = r.ExcludeRange(12, 22)
	require.NoError(t, err)

	expectedRanges = []rangeVal[uint32, uint32]{
		{
			start: 0,
			end:   9,
			value: 0,
		},
		{
			start: 22,
			end:   0,
			value: 12,
		},
	}
	require.Equal(t, expectedRanges, r.ranges)

	// excluded range should return error, now is excluded because exclusion range could be extended
	_, err = r.GetValue(15)
	require.ErrorIs(t, err, errKeyExcluded)

	// newer should return 12
	value, err = r.GetValue(25)
	require.NoError(t, err)
	require.Equal(t, uint32(12), value)

	// add a disjoint exclusion of length = 4
	err = r.ExcludeRange(26, 30)
	require.NoError(t, err)

	expectedRanges = []rangeVal[uint32, uint32]{
		{
			start: 0,
			end:   9,
			value: 0,
		},
		{
			start: 22,
			end:   25,
			value: 12,
		},
		{
			start: 30,
			end:   0,
			value: 16,
		},
	}
	require.Equal(t, expectedRanges, r.ranges)

	// get a value from newly closed range [22, 25]
	value, err = r.GetValue(23)
	require.NoError(t, err)
	require.Equal(t, uint32(12), value)

	// add a disjoint exclusion of length = 1
	err = r.ExcludeRange(50, 51)
	require.NoError(t, err)

	// previously first range would have been pruned due to size limitations
	expectedRanges = []rangeVal[uint32, uint32]{
		{
			start: 22,
			end:   25,
			value: 12,
		},
		{
			start: 30,
			end:   49,
			value: 16,
		},
		{
			start: 51,
			end:   0,
			value: 17,
		},
	}
	require.Equal(t, expectedRanges, r.ranges)

	// excluded range should return error
	_, err = r.GetValue(50)
	require.ErrorIs(t, err, errKeyExcluded)
	_, err = r.GetValue(28)
	require.ErrorIs(t, err, errKeyExcluded)
	_, err = r.GetValue(17)
	require.ErrorIs(t, err, errKeyTooOld)

	// previously valid, but aged out key should return error
	_, err = r.GetValue(5)
	require.ErrorIs(t, err, errKeyTooOld)

	// valid range access should return values
	value, err = r.GetValue(24)
	require.NoError(t, err)
	require.Equal(t, uint32(12), value)

	value, err = r.GetValue(34)
	require.NoError(t, err)
	require.Equal(t, uint32(16), value)

	value, err = r.GetValue(49)
	require.NoError(t, err)
	require.Equal(t, uint32(16), value)

	value, err = r.GetValue(55555555)
	require.NoError(t, err)
	require.Equal(t, uint32(17), value)

	// reset
	r.ClearAndResetValue(24, 23)
	expectedRanges = []rangeVal[uint32, uint32]{
		{
			start: 24,
			end:   0,
			value: 23,
		},
	}
	require.Equal(t, expectedRanges, r.ranges)

	value, err = r.GetValue(55555555)
	require.NoError(t, err)
	require.Equal(t, uint32(23), value)

	// decrement value and ensure that any key after start in ClearAndResetValue above returns that value
	// (as given end is higher than open range start, open range should be closed and a new range added)
	r.DecValue(34, 12)

	expectedRanges = []rangeVal[uint32, uint32]{
		{
			start: 24,
			end:   34,
			value: 23,
		},
		{
			start: 35,
			end:   0,
			value: 11,
		},
	}
	require.Equal(t, expectedRanges, r.ranges)

	value, err = r.GetValue(55555555)
	require.NoError(t, err)
	require.Equal(t, uint32(11), value)

	// add an exclusion and then decrement value
	err = r.ExcludeRange(40, 45)
	require.NoError(t, err)

	expectedRanges = []rangeVal[uint32, uint32]{
		{
			start: 24,
			end:   34,
			value: 23,
		},
		{
			start: 35,
			end:   39,
			value: 11,
		},
		{
			start: 45,
			end:   0,
			value: 16,
		},
	}
	require.Equal(t, expectedRanges, r.ranges)

	// before first range access
	_, err = r.GetValue(5)
	require.ErrorIs(t, err, errKeyTooOld)

	// first range access
	value, err = r.GetValue(25)
	require.NoError(t, err)
	require.Equal(t, uint32(23), value)

	// second range access
	value, err = r.GetValue(35)
	require.NoError(t, err)
	require.Equal(t, uint32(11), value)

	// open range access
	value, err = r.GetValue(55555555)
	require.NoError(t, err)
	require.Equal(t, uint32(16), value)

	r.DecValue(66, 6)

	expectedRanges = []rangeVal[uint32, uint32]{
		{
			start: 35,
			end:   39,
			value: 11,
		},
		{
			start: 45,
			end:   66,
			value: 16,
		},
		{
			start: 67,
			end:   0,
			value: 10,
		},
	}
	require.Equal(t, expectedRanges, r.ranges)

	// aged out range access
	_, err = r.GetValue(25)
	require.ErrorIs(t, err, errKeyTooOld)

	// access closed range before decrementing value
	value, err = r.GetValue(66)
	require.NoError(t, err)
	require.Equal(t, uint32(16), value)

	// open range access
	value, err = r.GetValue(67)
	require.NoError(t, err)
	require.Equal(t, uint32(10), value)

	// decrement with old end and check that open range gets decremented
	r.DecValue(66, 6)

	expectedRanges = []rangeVal[uint32, uint32]{
		{
			start: 35,
			end:   39,
			value: 11,
		},
		{
			start: 45,
			end:   66,
			value: 16,
		},
		{
			start: 67,
			end:   0,
			value: 4,
		},
	}
	require.Equal(t, expectedRanges, r.ranges)

	// open range access should get decremented value
	value, err = r.GetValue(67)
	require.NoError(t, err)
	require.Equal(t, uint32(4), value)
}
