package eval

import (
	"testing"

	"github.com/grafana/grafana-plugin-sdk-go/data"
	"github.com/stretchr/testify/require"

	"github.com/grafana/grafana/pkg/expr/classic"
	"github.com/grafana/grafana/pkg/util"
)

func TestExtractEvalString(t *testing.T) {
	cases := []struct {
		desc      string
		inFrame   *data.Frame
		outString string
	}{
		{
			desc: "1 EvalMatch",
			inFrame: newMetaFrame([]classic.EvalMatch{
				{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(32.3)},
			}, util.Pointer(1.0)),
			outString: `[ var='0' metric='Test' labels={host=foo} value=32.3 ]`,
		},
		{
			desc: "2 EvalMatches",
			inFrame: newMetaFrame([]classic.EvalMatch{
				{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(32.3)},
				{Metric: "Test", Labels: data.Labels{"host": "baz"}, Value: util.Pointer(10.0)},
			}, util.Pointer(1.0), withRefID("A")),
			outString: `[ var='A0' metric='Test' labels={host=foo} value=32.3 ], [ var='A1' metric='Test' labels={host=baz} value=10 ]`,
		},
		{
			desc: "3 EvalMatches",
			inFrame: newMetaFrame([]classic.EvalMatch{
				{Metric: "Test", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(32.3)},
				{Metric: "Test", Labels: data.Labels{"host": "baz"}, Value: util.Pointer(10.0)},
				{Metric: "TestA", Labels: data.Labels{"host": "zip"}, Value: util.Pointer(11.0)},
			}, util.Pointer(1.0), withRefID("A")),
			outString: `[ var='A0' metric='Test' labels={host=foo} value=32.3 ], [ var='A1' metric='Test' labels={host=baz} value=10 ], [ var='A2' metric='TestA' labels={host=zip} value=11 ]`,
		},
		{
			desc: "Captures are sorted in ascending order of var",
			inFrame: newMetaFrame([]NumberValueCapture{
				{Var: "B", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
				{Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(10.0)},
			}, util.Pointer(1.0)),
			outString: `[ var='A' labels={host=foo} value=10 ], [ var='B' labels={host=foo} value=1 ]`,
		},
	}
	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			require.Equal(t, tc.outString, extractEvalString(tc.inFrame))
		})
	}
}

func TestExtractValues(t *testing.T) {
	cases := []struct {
		desc    string
		inFrame *data.Frame
		values  map[string]NumberValueCapture
	}{{
		desc:    "No values in frame returns nil",
		inFrame: newMetaFrame(nil, util.Pointer(1.0)),
		values:  nil,
	}, {
		desc: "Classic condition frame with one match",
		inFrame: newMetaFrame([]classic.EvalMatch{
			{Metric: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
		}, util.Pointer(1.0), withRefID("A")),
		values: map[string]NumberValueCapture{
			"A0": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
		},
	}, {
		desc: "Classic condition frame with multiple matches",
		inFrame: newMetaFrame([]classic.EvalMatch{
			{Metric: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
			{Metric: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(3.0)},
		}, util.Pointer(1.0), withRefID("A")),
		values: map[string]NumberValueCapture{
			"A0": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
			"A1": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(3.0)},
		},
	}, {
		desc: "Nil value",
		inFrame: newMetaFrame([]NumberValueCapture{
			{Var: "A", Labels: data.Labels{"host": "foo"}, Value: nil},
		}, util.Pointer(1.0)),
		values: map[string]NumberValueCapture{
			"A": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: nil},
		},
	}, {
		desc: "1 value",
		inFrame: newMetaFrame([]NumberValueCapture{
			{Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
		}, util.Pointer(1.0)),
		values: map[string]NumberValueCapture{
			"A": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
		},
	}, {
		desc: "2 values",
		inFrame: newMetaFrame([]NumberValueCapture{
			{Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
			{Var: "B", Labels: nil, Value: util.Pointer(2.0)},
		}, util.Pointer(1.0)),
		values: map[string]NumberValueCapture{
			"A": {Var: "A", Labels: data.Labels{"host": "foo"}, Value: util.Pointer(1.0)},
			"B": {Var: "B", Value: util.Pointer(2.0)},
		},
	}}
	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			require.Equal(t, tc.values, extractValues(tc.inFrame))
		})
	}
}

type frameCallback func(frame *data.Frame)

func withRefID(refID string) frameCallback {
	return func(frame *data.Frame) {
		frame.RefID = refID
	}
}

func newMetaFrame(custom any, val *float64, callbacks ...frameCallback) *data.Frame {
	f := data.NewFrame("",
		data.NewField("", nil, []*float64{val})).
		SetMeta(&data.FrameMeta{
			Custom: custom,
		})

	for _, cb := range callbacks {
		cb(f)
	}

	return f
}
