package backtesting

import (
	"context"
	"errors"
	"math/rand"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"

	"github.com/grafana/grafana/pkg/services/ngalert/eval"
	"github.com/grafana/grafana/pkg/services/ngalert/eval/eval_mocks"
)

func TestQueryEvaluator_Eval(t *testing.T) {
	ctx := context.Background()
	interval := time.Duration(rand.Int63n(9)+1) * time.Second
	times := rand.Intn(11) + 5
	from := time.Now().Add(-time.Duration(times) * interval)

	t.Run("should evaluate query", func(t *testing.T) {
		m := &eval_mocks.ConditionEvaluatorMock{}
		expectedResults := eval.Results{}
		m.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(expectedResults, nil)
		evaluator := queryEvaluator{
			eval: m,
		}

		intervals := make([]time.Time, times)

		err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) error {
			intervals[idx] = now
			return nil
		})
		require.NoError(t, err)
		require.Len(t, intervals, times)

		expected := from
		for idx, actual := range intervals {
			assert.Equalf(t, expected, actual, "item at index %d is not times of interval %v", idx, interval)
			expected = expected.Add(interval)
		}
		m.AssertNumberOfCalls(t, "Evaluate", times)
		for _, now := range intervals {
			m.AssertCalled(t, "Evaluate", ctx, now)
		}
	})

	t.Run("should stop evaluation if error", func(t *testing.T) {
		t.Run("when evaluation fails", func(t *testing.T) {
			m := &eval_mocks.ConditionEvaluatorMock{}
			expectedResults := eval.Results{}
			expectedError := errors.New("test")
			m.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(expectedResults, nil).Times(3)
			m.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(nil, expectedError).Once()
			evaluator := queryEvaluator{
				eval: m,
			}

			intervals := make([]time.Time, 0, times)

			err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) error {
				intervals = append(intervals, now)
				return nil
			})
			require.ErrorIs(t, err, expectedError)
			require.Len(t, intervals, 3)
		})

		t.Run("when callback fails", func(t *testing.T) {
			m := &eval_mocks.ConditionEvaluatorMock{}
			expectedResults := eval.Results{}
			expectedError := errors.New("test")
			m.EXPECT().Evaluate(mock.Anything, mock.Anything).Return(expectedResults, nil)
			evaluator := queryEvaluator{
				eval: m,
			}

			intervals := make([]time.Time, 0, times)

			err := evaluator.Eval(ctx, from, interval, times, func(idx int, now time.Time, results eval.Results) error {
				if len(intervals) > 3 {
					return expectedError
				}
				intervals = append(intervals, now)
				return nil
			})
			require.ErrorIs(t, err, expectedError)
		})
	})
}
