// Copyright The Prometheus Authors
// 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 tsdb

import (
	"context"
	"fmt"
	"math"
	"reflect"
	"sort"
	"strconv"
	"strings"
	"sync"
	"testing"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/stretchr/testify/require"

	"github.com/prometheus/prometheus/model/exemplar"
	"github.com/prometheus/prometheus/model/labels"
	"github.com/prometheus/prometheus/storage"
)

var eMetrics = NewExemplarMetrics(prometheus.DefaultRegisterer)

// Tests the same exemplar cases as AddExemplar, but specifically the ValidateExemplar function so it can be relied on externally.
func TestValidateExemplar(t *testing.T) {
	exs, err := NewCircularExemplarStorage(2, eMetrics, 0)
	require.NoError(t, err)
	es := exs.(*CircularExemplarStorage)

	l := labels.FromStrings("service", "asdf")
	e := exemplar.Exemplar{
		Labels: labels.FromStrings("trace_id", "qwerty"),
		Value:  0.1,
		Ts:     1,
	}

	require.NoError(t, es.ValidateExemplar(l, e))
	require.NoError(t, es.AddExemplar(l, e))

	e2 := exemplar.Exemplar{
		Labels: labels.FromStrings("trace_id", "zxcvb"),
		Value:  0.1,
		Ts:     2,
	}

	require.NoError(t, es.ValidateExemplar(l, e2))
	require.NoError(t, es.AddExemplar(l, e2))

	require.Equal(t, es.ValidateExemplar(l, e2), storage.ErrDuplicateExemplar, "error is expected attempting to validate duplicate exemplar")

	e3 := e2
	e3.Ts = 3
	require.Equal(t, es.ValidateExemplar(l, e3), storage.ErrDuplicateExemplar, "error is expected when attempting to add duplicate exemplar, even with different timestamp")

	e3.Ts = 1
	e3.Value = 0.3
	require.Equal(t, es.ValidateExemplar(l, e3), storage.ErrOutOfOrderExemplar)

	e4 := exemplar.Exemplar{
		Labels: labels.FromStrings("a", strings.Repeat("b", exemplar.ExemplarMaxLabelSetLength)),
		Value:  0.1,
		Ts:     2,
	}
	require.Equal(t, storage.ErrExemplarLabelLength, es.ValidateExemplar(l, e4))
}

func TestCircularExemplarStorage_AddExemplar(t *testing.T) {
	series1 := labels.FromStrings("trace_id", "foo")
	series2 := labels.FromStrings("trace_id", "bar")

	series1Matcher := []*labels.Matcher{{
		Type:  labels.MatchEqual,
		Name:  "trace_id",
		Value: series1.Get("trace_id"),
	}}

	series2Matcher := []*labels.Matcher{{
		Type:  labels.MatchEqual,
		Name:  "trace_id",
		Value: series2.Get("trace_id"),
	}}

	testCases := []struct {
		name          string
		size          int64
		exemplars     []exemplar.Exemplar
		wantExemplars []exemplar.Exemplar
		matcher       []*labels.Matcher
		wantError     error
	}{
		{
			name: "insert after newest",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
			},
		},
		{
			name: "insert before oldest",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 2},
				{Labels: series1, Value: 0.2, Ts: 1},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.2, Ts: 1},
				{Labels: series1, Value: 0.1, Ts: 2},
			},
		},
		{
			name: "insert in between",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 3},
				{Labels: series1, Value: 0.3, Ts: 2},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.3, Ts: 2},
				{Labels: series1, Value: 0.2, Ts: 3},
			},
		},
		{
			name: "insert after newest with overflow",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.4, Ts: 4},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.4, Ts: 4},
			},
		},
		{
			name: "insert before oldest with overflow",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.4, Ts: 0},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.4, Ts: 0},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
			},
		},
		{
			name: "insert between with overflow",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 3},
				{Labels: series1, Value: 0.3, Ts: 4},
				{Labels: series1, Value: 0.4, Ts: 2},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.4, Ts: 2},
				{Labels: series1, Value: 0.2, Ts: 3},
				{Labels: series1, Value: 0.3, Ts: 4},
			},
		},
		{
			name: "out-of-order insert where evicted entry is insertion point",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.2, Ts: 2}, // pos 0, linked list middle
				{Labels: series1, Value: 0.1, Ts: 1}, // pos 1, linked list oldest
				{Labels: series1, Value: 0.5, Ts: 5}, // pos 2, linked list newest
				{Labels: series1, Value: 0.3, Ts: 3},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.5, Ts: 5},
			},
		},
		{
			name: "insert out of the OOO window",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 200},
				{Labels: series1, Value: 0.2, Ts: 1},
			},
			wantError: storage.ErrOutOfOrderExemplar,
		},
		{
			name: "insert multiple series",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 3},
				{Labels: series2, Value: 0.3, Ts: 4},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 3},
			},
		},
		{
			name: "insert multiple series with overflow",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series2, Value: 0.1, Ts: 1},
				{Labels: series2, Value: 0.2, Ts: 2},
				{Labels: series2, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.4, Ts: 4},
			},
			matcher: series2Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series2, Value: 0.2, Ts: 2},
				{Labels: series2, Value: 0.3, Ts: 3},
			},
		},
		{
			name: "series1 overflows series2 out-of-order",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series2, Value: 0.1, Ts: 3},
				{Labels: series2, Value: 0.2, Ts: 2},
				{Labels: series2, Value: 0.3, Ts: 4},
				{Labels: series1, Value: 0.4, Ts: 4},
				{Labels: series1, Value: 0.5, Ts: 1},
			},
			matcher: series2Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series2, Value: 0.3, Ts: 4},
			},
		},
		{
			name: "ignore duplicate exemplars",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 3},
				{Labels: series1, Value: 0.1, Ts: 3},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 3},
			},
		},
		{
			name: "ignore duplicate exemplars when buffer is full",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 3},
				{Labels: series1, Value: 0.2, Ts: 4},
				{Labels: series1, Value: 0.3, Ts: 5},
				{Labels: series1, Value: 0.3, Ts: 5},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 3},
				{Labels: series1, Value: 0.2, Ts: 4},
				{Labels: series1, Value: 0.3, Ts: 5},
			},
		},
		{
			name: "empty timestamps are valid",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 0},
				{Labels: series1, Value: 0.2, Ts: 0},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 0},
				{Labels: series1, Value: 0.2, Ts: 0},
			},
		},
		{
			name: "exemplar label length exceeds maximum",
			size: 3,
			exemplars: []exemplar.Exemplar{
				{Labels: labels.FromStrings("a", strings.Repeat("b", exemplar.ExemplarMaxLabelSetLength)), Value: 0.1, Ts: 2},
			},
			wantError: storage.ErrExemplarLabelLength,
		},
		{
			name: "native histograms",
			size: 6,
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
			},
		},
		{
			name: "evict only exemplar for series then re-add",
			size: 2,
			exemplars: []exemplar.Exemplar{
				// series1 at index 0, series2 at index 1, then series1 evicts its own only exemplar
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series2, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
			},
			matcher: series1Matcher,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.3, Ts: 3},
			},
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			exs, err := NewCircularExemplarStorage(tc.size, eMetrics, 100)
			require.NoError(t, err)
			es := exs.(*CircularExemplarStorage)

			// Add exemplars and compare tc.wantErr against the first exemplar failing.
			var addError error
			for i, ex := range tc.exemplars {
				addError = es.AddExemplar(ex.Labels, ex)
				if addError != nil {
					break
				}
				if testing.Verbose() {
					t.Logf("Buffer[%d]:\n%s", i, debugCircularBuffer(es))
				}
			}
			if tc.wantError == nil {
				require.NoError(t, addError)
			} else {
				require.ErrorIs(t, addError, tc.wantError)
			}
			if addError != nil {
				return
			}

			// Ensure exemplars are returned correctly and in-order.
			gotExemplars, err := es.Select(0, 1000, tc.matcher)
			require.NoError(t, err)
			if len(tc.wantExemplars) == 0 {
				require.Empty(t, gotExemplars)
			} else {
				require.Len(t, gotExemplars, 1)
				require.Equal(t, tc.wantExemplars, gotExemplars[0].Exemplars)
			}
		})
	}
}

func TestCircularExemplarStorage_Resize(t *testing.T) {
	series1 := labels.FromStrings("trace_id", "foo")
	series2 := labels.FromStrings("trace_id", "bar")
	matcher1 := []*labels.Matcher{
		labels.MustNewMatcher(labels.MatchRegexp, "trace_id", "(foo|bar)"),
	}

	testCases := []struct {
		name          string
		exemplars     []exemplar.Exemplar
		resize        int64
		wantExemplars []exemplar.Exemplar
		wantNextIndex int
		wantError     error
	}{
		{
			name: "in-order, grow",
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
			},
			resize: 10,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
			},
			wantNextIndex: 3,
		},
		{
			name: "in-order, shrink",
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
			},
			resize: 2,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
			},
			wantNextIndex: 0,
		},
		{
			name: "out-of-order, shrink",
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.1, Ts: 1},
			},
			resize: 2,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
			},
			wantNextIndex: 0,
		},
		{
			name: "out-of-order, grow",
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.2, Ts: 2},
			},
			resize: 5,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
			},
			wantNextIndex: 3,
		},
		{
			name: "duplicate timestamps",
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 1},
				{Labels: series1, Value: 0.3, Ts: 2},
			},
			resize: 3,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 1},
				{Labels: series1, Value: 0.3, Ts: 2},
			},
		},
		{
			name:          "empty input, grow",
			exemplars:     []exemplar.Exemplar{},
			resize:        10,
			wantExemplars: []exemplar.Exemplar{},
			wantNextIndex: 3,
		},
		{
			name:          "empty input, shrink",
			exemplars:     []exemplar.Exemplar{},
			resize:        1,
			wantExemplars: []exemplar.Exemplar{},
			wantNextIndex: 0,
		},
		{
			name: "shrink to zero",
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
			},
			resize:        0,
			wantExemplars: []exemplar.Exemplar{},
			wantNextIndex: 0,
		},
		{
			name: "multiple series, shrink",
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series2, Value: 1.1, Ts: 2},
				{Labels: series1, Value: 0.2, Ts: 3},
				{Labels: series2, Value: 1.2, Ts: 4},
			},
			resize: 2,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.2, Ts: 3},
				{Labels: series2, Value: 1.2, Ts: 4},
			},
			wantNextIndex: 0,
		},
		{
			name: "shrink to one",
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
			},
			resize: 1,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.2, Ts: 2},
			},
			wantNextIndex: 0,
		},
		{
			name: "shrink to two",
			exemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
			},
			resize: 2,
			wantExemplars: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
			},
			wantNextIndex: 0,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			exs, err := NewCircularExemplarStorage(3, eMetrics, 100)
			require.NoError(t, err)
			es := exs.(*CircularExemplarStorage)

			for _, ex := range tc.exemplars {
				require.NoError(t, es.AddExemplar(ex.Labels, ex))
			}

			// Resize the circular buffer.
			if testing.Verbose() {
				t.Logf("Buffer[before-resize]:\n%s", debugCircularBuffer(es))
			}
			es.Resize(tc.resize)
			if testing.Verbose() {
				t.Logf("Buffer[after-resize]:\n%s", debugCircularBuffer(es))
			}

			// Ensure exemplars are returned correctly and in-order.
			gotExemplars, err := es.Select(0, 1000, matcher1)
			require.NoError(t, err)
			flat := make([]exemplar.Exemplar, 0)
			for _, group := range gotExemplars {
				flat = append(flat, group.Exemplars...)
			}
			sort.Slice(flat, func(i, j int) bool {
				return flat[i].Ts < flat[j].Ts
			})
			require.Equal(t, tc.wantExemplars, flat, "exemplar mismatch")
			require.Equal(t, tc.wantNextIndex, es.nextIndex, "next index mismatch")
		})
	}

	resizeTwiceCases := []struct {
		name           string
		addExemplars1  []exemplar.Exemplar
		resize1        int64
		wantExemplars1 []exemplar.Exemplar
		resize2        int64
		addExemplars2  []exemplar.Exemplar
		wantExemplars2 []exemplar.Exemplar
	}{
		{
			name: "shrink then grow ordered",
			addExemplars1: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.4, Ts: 4},
			},
			resize1: 2,
			wantExemplars1: []exemplar.Exemplar{
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.4, Ts: 4},
			},
			resize2: 5,
			addExemplars2: []exemplar.Exemplar{
				{Labels: series1, Value: 0.5, Ts: 5},
				{Labels: series1, Value: 0.6, Ts: 6},
				{Labels: series1, Value: 0.7, Ts: 7},
			},
			wantExemplars2: []exemplar.Exemplar{
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.4, Ts: 4},
				{Labels: series1, Value: 0.5, Ts: 5},
				{Labels: series1, Value: 0.6, Ts: 6},
				{Labels: series1, Value: 0.7, Ts: 7},
			},
		},
		{
			name: "shrink then grow out-of-order",
			addExemplars1: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.4, Ts: 4},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
			},
			resize1: 2,
			wantExemplars1: []exemplar.Exemplar{
				// We delete in the order of ingestion, not temporally.
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
			},
			resize2: 5,
			addExemplars2: []exemplar.Exemplar{
				{Labels: series1, Value: 0.7, Ts: 7},
				{Labels: series1, Value: 0.6, Ts: 6},
				{Labels: series1, Value: 0.5, Ts: 5},
			},
			wantExemplars2: []exemplar.Exemplar{
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.5, Ts: 5},
				{Labels: series1, Value: 0.6, Ts: 6},
				{Labels: series1, Value: 0.7, Ts: 7},
			},
		},
		{
			name: "grow then shrink ordered",
			addExemplars1: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.4, Ts: 4},
			},
			resize1: 5,
			wantExemplars1: []exemplar.Exemplar{
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.4, Ts: 4},
			},
			resize2: 2,
			addExemplars2: []exemplar.Exemplar{
				{Labels: series1, Value: 0.5, Ts: 5},
				{Labels: series1, Value: 0.6, Ts: 6},
				{Labels: series1, Value: 0.7, Ts: 7},
			},
			wantExemplars2: []exemplar.Exemplar{
				{Labels: series1, Value: 0.6, Ts: 6},
				{Labels: series1, Value: 0.7, Ts: 7},
			},
		},
		{
			name: "grow then shrink out-of-order",
			addExemplars1: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.4, Ts: 4},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
			},
			resize1: 5,
			wantExemplars1: []exemplar.Exemplar{
				// We delete in the order of ingestion, not temporally.
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.4, Ts: 4},
			},
			resize2: 2,
			addExemplars2: []exemplar.Exemplar{
				{Labels: series1, Value: 0.7, Ts: 7},
				{Labels: series1, Value: 0.5, Ts: 5},
				{Labels: series1, Value: 0.6, Ts: 6},
			},
			wantExemplars2: []exemplar.Exemplar{
				{Labels: series1, Value: 0.5, Ts: 5},
				{Labels: series1, Value: 0.6, Ts: 6},
			},
		},
		{
			name: "grow non-full buffer then add entries",
			addExemplars1: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
			},
			resize1: 10,
			wantExemplars1: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
			},
			resize2: 10,
			addExemplars2: []exemplar.Exemplar{
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.4, Ts: 4},
			},
			wantExemplars2: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
				{Labels: series1, Value: 0.3, Ts: 3},
				{Labels: series1, Value: 0.4, Ts: 4},
			},
		},
		{
			name: "shrink non-full buffer then add entries",
			addExemplars1: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
			},
			resize1: 2,
			wantExemplars1: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
			},
			resize2: 2,
			addExemplars2: []exemplar.Exemplar{
				{Labels: series1, Value: 0.2, Ts: 2},
			},
			wantExemplars2: []exemplar.Exemplar{
				{Labels: series1, Value: 0.1, Ts: 1},
				{Labels: series1, Value: 0.2, Ts: 2},
			},
		},
	}

	for _, tc := range resizeTwiceCases {
		t.Run(tc.name, func(t *testing.T) {
			exs, err := NewCircularExemplarStorage(3, eMetrics, 100)
			require.NoError(t, err)
			es := exs.(*CircularExemplarStorage)
			for _, ex := range tc.addExemplars1 {
				require.NoError(t, es.AddExemplar(ex.Labels, ex))
			}
			es.Resize(tc.resize1)
			gotExemplars, err := es.Select(0, 1000, matcher1)
			require.NoError(t, err)
			require.Len(t, gotExemplars, 1)
			require.Equal(t, tc.wantExemplars1, gotExemplars[0].Exemplars)
			es.Resize(tc.resize2)
			for _, ex := range tc.addExemplars2 {
				require.NoError(t, es.AddExemplar(ex.Labels, ex))
			}
			if testing.Verbose() {
				t.Logf("Buffer[after-resize2]:\n%s", debugCircularBuffer(es))
			}
			gotExemplars, err = es.Select(0, 1000, matcher1)
			require.NoError(t, err)
			require.Len(t, gotExemplars, 1)
			require.Equal(t, tc.wantExemplars2, gotExemplars[0].Exemplars)
		})
	}
}

func TestStorageOverflow(t *testing.T) {
	// Test that circular buffer index and assignment
	// works properly, adding more exemplars than can
	// be stored and then querying for them.
	exs, err := NewCircularExemplarStorage(5, eMetrics, 0)
	require.NoError(t, err)
	es := exs.(*CircularExemplarStorage)

	lName, lValue := "service", "asdf"
	l := labels.FromStrings(lName, lValue)

	var eList []exemplar.Exemplar
	for i := 0; i < len(es.exemplars)+1; i++ {
		e := exemplar.Exemplar{
			Labels: labels.FromStrings("trace_id", "a"),
			Value:  float64(i+1) / 10,
			Ts:     int64(101 + i),
		}
		es.AddExemplar(l, e)
		eList = append(eList, e)
	}
	require.True(t, (es.exemplars[0].exemplar.Ts == 106), "exemplar was not stored correctly")

	m, err := labels.NewMatcher(labels.MatchEqual, lName, lValue)
	require.NoError(t, err, "error creating label matcher for exemplar query")
	ret, err := es.Select(100, 110, []*labels.Matcher{m})
	require.NoError(t, err)
	require.Len(t, ret, 1, "select should have returned samples for a single series only")

	require.True(t, reflect.DeepEqual(eList[1:], ret[0].Exemplars), "select did not return expected exemplars\n\texpected: %+v\n\tactual: %+v\n", eList[1:], ret[0].Exemplars)
}

func TestSelectExemplar(t *testing.T) {
	exs, err := NewCircularExemplarStorage(5, eMetrics, 0)
	require.NoError(t, err)
	es := exs.(*CircularExemplarStorage)

	lName, lValue := "service", "asdf"
	l := labels.FromStrings(lName, lValue)
	e := exemplar.Exemplar{
		Labels: labels.FromStrings("trace_id", "querty"),
		Value:  0.1,
		Ts:     12,
	}

	err = es.AddExemplar(l, e)
	require.NoError(t, err, "adding exemplar failed")
	require.True(t, reflect.DeepEqual(es.exemplars[0].exemplar, e), "exemplar was not stored correctly")

	m, err := labels.NewMatcher(labels.MatchEqual, lName, lValue)
	require.NoError(t, err, "error creating label matcher for exemplar query")
	ret, err := es.Select(0, 100, []*labels.Matcher{m})
	require.NoError(t, err)
	require.Len(t, ret, 1, "select should have returned samples for a single series only")

	expectedResult := []exemplar.Exemplar{e}
	require.True(t, reflect.DeepEqual(expectedResult, ret[0].Exemplars), "select did not return expected exemplars\n\texpected: %+v\n\tactual: %+v\n", expectedResult, ret[0].Exemplars)
}

func TestSelectExemplar_MultiSeries(t *testing.T) {
	exs, err := NewCircularExemplarStorage(5, eMetrics, 0)
	require.NoError(t, err)
	es := exs.(*CircularExemplarStorage)

	l1Name := "test_metric"
	l1 := labels.FromStrings(labels.MetricName, l1Name, "service", "asdf")
	l2Name := "test_metric2"
	l2 := labels.FromStrings(labels.MetricName, l2Name, "service", "qwer")

	for i := 0; i < len(es.exemplars); i++ {
		e1 := exemplar.Exemplar{
			Labels: labels.FromStrings("trace_id", "a"),
			Value:  float64(i+1) / 10,
			Ts:     int64(101 + i),
		}
		err = es.AddExemplar(l1, e1)
		require.NoError(t, err)

		e2 := exemplar.Exemplar{
			Labels: labels.FromStrings("trace_id", "b"),
			Value:  float64(i+1) / 10,
			Ts:     int64(101 + i),
		}
		err = es.AddExemplar(l2, e2)
		require.NoError(t, err)
	}

	m, err := labels.NewMatcher(labels.MatchEqual, labels.MetricName, l2Name)
	require.NoError(t, err, "error creating label matcher for exemplar query")
	ret, err := es.Select(100, 200, []*labels.Matcher{m})
	require.NoError(t, err)
	require.Len(t, ret, 1, "select should have returned samples for a single series only")
	require.Len(t, ret[0].Exemplars, 3, "didn't get expected 8 exemplars, got %d", len(ret[0].Exemplars))

	m, err = labels.NewMatcher(labels.MatchEqual, labels.MetricName, l1Name)
	require.NoError(t, err, "error creating label matcher for exemplar query")
	ret, err = es.Select(100, 200, []*labels.Matcher{m})
	require.NoError(t, err)
	require.Len(t, ret, 1, "select should have returned samples for a single series only")
	require.Len(t, ret[0].Exemplars, 2, "didn't get expected 8 exemplars, got %d", len(ret[0].Exemplars))
}

func TestSelectExemplar_TimeRange(t *testing.T) {
	var lenEs int64 = 5
	exs, err := NewCircularExemplarStorage(lenEs, eMetrics, 0)
	require.NoError(t, err)
	es := exs.(*CircularExemplarStorage)

	lName, lValue := "service", "asdf"
	l := labels.FromStrings(lName, lValue)

	for i := 0; int64(i) < lenEs; i++ {
		err := es.AddExemplar(l, exemplar.Exemplar{
			Labels: labels.FromStrings("trace_id", strconv.Itoa(i)),
			Value:  0.1,
			Ts:     int64(101 + i),
		})
		require.NoError(t, err)
		require.Equal(t, es.index[string(l.Bytes(nil))].newest, i, "exemplar was not stored correctly")
	}

	m, err := labels.NewMatcher(labels.MatchEqual, lName, lValue)
	require.NoError(t, err, "error creating label matcher for exemplar query")
	ret, err := es.Select(102, 104, []*labels.Matcher{m})
	require.NoError(t, err)
	require.Len(t, ret, 1, "select should have returned samples for a single series only")
	require.Len(t, ret[0].Exemplars, 3, "didn't get expected two exemplars %d, %+v", len(ret[0].Exemplars), ret)
}

// Test to ensure that even though a series matches more than one matcher from the
// query that it's exemplars are only included in the result a single time.
func TestSelectExemplar_DuplicateSeries(t *testing.T) {
	exs, err := NewCircularExemplarStorage(4, eMetrics, 0)
	require.NoError(t, err)
	es := exs.(*CircularExemplarStorage)

	e := exemplar.Exemplar{
		Labels: labels.FromStrings("trace_id", "qwerty"),
		Value:  0.1,
		Ts:     12,
	}

	lName0, lValue0 := "service", "asdf"
	lName1, lValue1 := "cluster", "us-central1"
	l := labels.FromStrings(lName0, lValue0, lName1, lValue1)

	// Lets just assume somehow the PromQL expression generated two separate lists of matchers,
	// both of which can select this particular series.
	m := [][]*labels.Matcher{
		{
			labels.MustNewMatcher(labels.MatchEqual, lName0, lValue0),
		},
		{
			labels.MustNewMatcher(labels.MatchEqual, lName1, lValue1),
		},
	}

	err = es.AddExemplar(l, e)
	require.NoError(t, err, "adding exemplar failed")
	require.True(t, reflect.DeepEqual(es.exemplars[0].exemplar, e), "exemplar was not stored correctly")

	ret, err := es.Select(0, 100, m...)
	require.NoError(t, err)
	require.Len(t, ret, 1, "select should have returned samples for a single series only")
}

func TestIndexOverwrite(t *testing.T) {
	exs, err := NewCircularExemplarStorage(2, eMetrics, 0)
	require.NoError(t, err)
	es := exs.(*CircularExemplarStorage)

	l1 := labels.FromStrings("service", "asdf")
	l2 := labels.FromStrings("service", "qwer")

	err = es.AddExemplar(l1, exemplar.Exemplar{Value: 1, Ts: 1})
	require.NoError(t, err)
	err = es.AddExemplar(l2, exemplar.Exemplar{Value: 2, Ts: 2})
	require.NoError(t, err)
	err = es.AddExemplar(l2, exemplar.Exemplar{Value: 3, Ts: 3})
	require.NoError(t, err)

	// Ensure index GC'ing is taking place, there should no longer be any
	// index entry for series l1 since we just wrote two exemplars for series l2.
	_, ok := es.index[string(l1.Bytes(nil))]
	require.False(t, ok)
	require.Equal(t, &indexEntry{1, 0, l2}, es.index[string(l2.Bytes(nil))])

	err = es.AddExemplar(l1, exemplar.Exemplar{Value: 4, Ts: 4})
	require.NoError(t, err)

	i := es.index[string(l2.Bytes(nil))]
	require.Equal(t, &indexEntry{0, 0, l2}, i)
}

func TestResize(t *testing.T) {
	testCases := []struct {
		name              string
		startSize         int64
		newCount          int64
		expectedSeries    []int
		notExpectedSeries []int
		expectedMigrated  int
	}{
		{
			name:              "Grow",
			startSize:         100,
			newCount:          200,
			expectedSeries:    []int{99, 98, 1, 0},
			notExpectedSeries: []int{100},
			expectedMigrated:  100,
		},
		{
			name:              "Shrink",
			startSize:         100,
			newCount:          50,
			expectedSeries:    []int{99, 98, 50},
			notExpectedSeries: []int{49, 1, 0},
			expectedMigrated:  50,
		},
		{
			name:              "ShrinkToZero",
			startSize:         100,
			newCount:          0,
			expectedSeries:    []int{},
			notExpectedSeries: []int{},
			expectedMigrated:  0,
		},
		{
			name:              "Negative",
			startSize:         100,
			newCount:          -1,
			expectedSeries:    []int{},
			notExpectedSeries: []int{},
			expectedMigrated:  0,
		},
		{
			name:              "NegativeToNegative",
			startSize:         -1,
			newCount:          -2,
			expectedSeries:    []int{},
			notExpectedSeries: []int{},
			expectedMigrated:  0,
		},
		{
			name:              "GrowFromZero",
			startSize:         0,
			newCount:          10,
			expectedSeries:    []int{},
			notExpectedSeries: []int{},
			expectedMigrated:  0,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			exs, err := NewCircularExemplarStorage(tc.startSize, eMetrics, 0)
			require.NoError(t, err)
			es := exs.(*CircularExemplarStorage)

			for i := 0; int64(i) < tc.startSize; i++ {
				err = es.AddExemplar(labels.FromStrings("service", strconv.Itoa(i)), exemplar.Exemplar{
					Value: float64(i),
					Ts:    int64(i),
				})
				require.NoError(t, err)
			}

			if testing.Verbose() {
				t.Logf("Buffer[before-resize]:\n%s", debugCircularBuffer(es))
			}
			resized := es.Resize(tc.newCount)
			if testing.Verbose() {
				t.Logf("Buffer[after-resize]:\n%s", debugCircularBuffer(es))
			}

			require.Equal(t, tc.expectedMigrated, resized)

			q, err := es.Querier(context.TODO())
			require.NoError(t, err)

			matchers := []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "service", "")}

			for _, expected := range tc.expectedSeries {
				matchers[0].Value = strconv.Itoa(expected)
				ex, err := q.Select(0, math.MaxInt64, matchers)
				require.NoError(t, err)
				require.NotEmpty(t, ex)
			}

			for _, notExpected := range tc.notExpectedSeries {
				matchers[0].Value = strconv.Itoa(notExpected)
				ex, err := q.Select(0, math.MaxInt64, matchers)
				require.NoError(t, err)
				require.Empty(t, ex)
			}
		})
	}
}

func BenchmarkAddExemplar(b *testing.B) {
	// We need to include these labels since we do length calculation
	// before adding.
	exLabels := labels.FromStrings("trace_id", "89620921")

	for _, capacity := range []int{1000, 10000, 100000} {
		for _, n := range []int{10000, 100000, 1000000} {
			b.Run(fmt.Sprintf("%d/%d", n, capacity), func(b *testing.B) {
				for b.Loop() {
					b.StopTimer()
					exs, err := NewCircularExemplarStorage(int64(capacity), eMetrics, 0)
					require.NoError(b, err)
					es := exs.(*CircularExemplarStorage)
					var l labels.Labels
					b.StartTimer()

					for i := range n {
						if i%100 == 0 {
							l = labels.FromStrings("service", strconv.Itoa(i))
						}
						err = es.AddExemplar(l, exemplar.Exemplar{Value: float64(i), Ts: int64(i), Labels: exLabels})
						if err != nil {
							require.NoError(b, err)
						}
					}
				}
			})
		}
	}
}

func BenchmarkAddExemplar_OutOfOrder(b *testing.B) {
	// We need to include these labels since we do length calculation
	// before adding.
	exLabels := labels.FromStrings("trace_id", "89620921")

	const (
		capacity = 5000
	)

	fillOneSeries := func(es *CircularExemplarStorage) {
		for i := range capacity {
			e := exemplar.Exemplar{Value: float64(i), Ts: int64(i), Labels: exLabels}
			if err := es.AddExemplar(exLabels, e); err != nil {
				panic(err)
			}
		}
	}

	fillMultipleSeries := func(es *CircularExemplarStorage) {
		for i := range capacity {
			l := labels.FromStrings("service", strconv.Itoa(i))
			e := exemplar.Exemplar{Value: float64(i), Ts: int64(i), Labels: l}
			if err := es.AddExemplar(l, e); err != nil {
				panic(err)
			}
		}
	}

	outOfOrder := func(ts *int64, _ *labels.Labels) {
		switch *ts % 3 {
		case 0:
			return
		case 1:
			*ts = capacity - *ts
		case 2:
			*ts = (capacity - *ts) + 100
		}
	}

	reverseOrder := func(ts *int64, _ *labels.Labels) {
		*ts = capacity - *ts
	}

	multipleSeries := func(f func(*int64, *labels.Labels)) func(*int64, *labels.Labels) {
		return func(ts *int64, l *labels.Labels) {
			f(ts, l)
			*l = labels.FromStrings("service", strconv.Itoa(int(*ts)))
		}
	}

	for fillName, setup := range map[string]func(es *CircularExemplarStorage){
		"empty":         func(*CircularExemplarStorage) {},
		"full-one":      fillOneSeries,
		"full-multiple": fillMultipleSeries,
	} {
		for orderName, forEach := range map[string]func(ts *int64, l *labels.Labels){
			"in-order":           func(*int64, *labels.Labels) {},
			"reverse":            reverseOrder,
			"out-of-order":       outOfOrder,
			"multi-in-order":     multipleSeries(func(*int64, *labels.Labels) {}),
			"multi-reverse":      multipleSeries(reverseOrder),
			"multi-out-of-order": multipleSeries(outOfOrder),
		} {
			b.Run(fmt.Sprintf("%s/%s", fillName, orderName), func(b *testing.B) {
				exs, err := NewCircularExemplarStorage(int64(capacity), eMetrics, 100000)
				require.NoError(b, err)
				es := exs.(*CircularExemplarStorage)
				l := labels.FromStrings("service", "0")
				setup(es)
				b.ResetTimer()
				for b.Loop() {
					for i := range capacity {
						ts := int64(i)
						forEach(&ts, &l)
						err = es.AddExemplar(l, exemplar.Exemplar{Value: float64(i), Ts: ts, Labels: l})
						if err != nil {
							b.Fatalf("Failed to insert item %d %s: %v", i, l, err)
						}
					}
				}
			})
		}
	}
}

func BenchmarkResizeExemplars(b *testing.B) {
	testCases := []struct {
		name         string
		startSize    int64
		endSize      int64
		numExemplars int
	}{
		{
			name:         "grow",
			startSize:    100000,
			endSize:      200000,
			numExemplars: 150000,
		},
		{
			name:         "shrink",
			startSize:    100000,
			endSize:      50000,
			numExemplars: 100000,
		},
		{
			name:         "grow",
			startSize:    1000000,
			endSize:      2000000,
			numExemplars: 1500000,
		},
		{
			name:         "shrink",
			startSize:    1000000,
			endSize:      500000,
			numExemplars: 1000000,
		},
	}

	for _, tc := range testCases {
		b.Run(fmt.Sprintf("%s-%d-to-%d", tc.name, tc.startSize, tc.endSize), func(b *testing.B) {
			for b.Loop() {
				b.StopTimer()
				exs, err := NewCircularExemplarStorage(tc.startSize, eMetrics, 0)
				require.NoError(b, err)
				es := exs.(*CircularExemplarStorage)

				var l labels.Labels
				for i := 0; i < int(float64(tc.startSize)*float64(1.5)); i++ {
					if i%100 == 0 {
						l = labels.FromStrings("service", strconv.Itoa(i))
					}

					err = es.AddExemplar(l, exemplar.Exemplar{Value: float64(i), Ts: int64(i)})
					if err != nil {
						require.NoError(b, err)
					}
				}
				b.StartTimer()
				es.Resize(tc.endSize)
			}
		})
	}
}

// TestCircularExemplarStorage_Concurrent_AddExemplar_Resize tries to provoke a data race between AddExemplar and Resize.
// Run with race detection enabled.
func TestCircularExemplarStorage_Concurrent_AddExemplar_Resize(t *testing.T) {
	exs, err := NewCircularExemplarStorage(0, eMetrics, 0)
	require.NoError(t, err)
	es := exs.(*CircularExemplarStorage)

	l := labels.FromStrings("service", "asdf")
	e := exemplar.Exemplar{
		Labels: labels.FromStrings("trace_id", "qwerty"),
		Value:  0.1,
		Ts:     1,
	}

	var wg sync.WaitGroup
	wg.Add(1)
	t.Cleanup(wg.Wait)

	started := make(chan struct{})

	go func() {
		defer wg.Done()

		<-started
		for range 100 {
			require.NoError(t, es.AddExemplar(l, e))
		}
	}()

	for i := range 100 {
		es.Resize(int64(i + 1))
		if i == 0 {
			close(started)
		}
	}
}

// debugCircularBuffer iterates all exemplars in the circular exemplar storage
// and returns them as a string. The textual representation contains index
// pointers and helps debugging exemplar storage.
func debugCircularBuffer(ce *CircularExemplarStorage) string {
	var sb strings.Builder
	for i, e := range ce.exemplars {
		if e.ref == nil {
			continue
		}
		sb.WriteString(fmt.Sprintf(
			"i: %d, ts: %d, next: %d, prev: %d",
			i, e.exemplar.Ts, e.next, e.prev,
		))
		for _, idx := range ce.index {
			if i == idx.newest {
				sb.WriteString(" <- newest " + idx.seriesLabels.String())
			}
			if i == idx.oldest {
				sb.WriteString(" <- oldest " + idx.seriesLabels.String())
			}
		}
		sb.WriteString("\n")
	}
	sb.WriteString(fmt.Sprintf("Next index: %d\n", ce.nextIndex))
	return sb.String()
}
