package tests

import (
	"fmt"
	"testing"
	"time"

	"github.com/VictoriaMetrics/VictoriaMetrics/apptest"
	"github.com/VictoriaMetrics/VictoriaMetrics/lib/decimal"
	pb "github.com/VictoriaMetrics/VictoriaMetrics/lib/prompbmarshal"
	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
)

func millis(s string) int64 {
	t, err := time.Parse(time.RFC3339, s)
	if err != nil {
		panic(fmt.Sprintf("could not parse time %q: %v", s, err))
	}
	return t.UnixMilli()
}

func TestSingleInstantQuery(t *testing.T) {
	tc := apptest.NewTestCase(t)
	defer tc.Stop()

	sut := tc.MustStartDefaultVmsingle()

	testInstantQueryWithUTFNames(t, sut)
	testInstantQueryDoesNotReturnStaleNaNs(t, sut)
}

func TestClusterInstantQuery(t *testing.T) {
	tc := apptest.NewTestCase(t)
	defer tc.Stop()

	sut := tc.MustStartDefaultCluster()

	testInstantQueryWithUTFNames(t, sut)
	testInstantQueryDoesNotReturnStaleNaNs(t, sut)
}

func testInstantQueryWithUTFNames(t *testing.T, sut apptest.PrometheusWriteQuerier) {
	data := []pb.TimeSeries{
		{
			Labels: []pb.Label{
				{Name: "__name__", Value: "3fooµ¥"},
				{Name: "3👋tfにちは", Value: "漢©®€£"},
			},
			Samples: []pb.Sample{
				{Value: 1, Timestamp: millis("2024-01-01T00:01:00Z")},
			},
		},
	}

	sut.PrometheusAPIV1Write(t, data, apptest.QueryOpts{})
	sut.ForceFlush(t)

	var got, want *apptest.PrometheusAPIV1QueryResponse
	cmpOptions := []cmp.Option{
		cmpopts.IgnoreFields(apptest.PrometheusAPIV1QueryResponse{}, "Status", "Data.ResultType"),
		cmpopts.EquateNaNs(),
	}

	want = apptest.NewPrometheusAPIV1QueryResponse(t, `{"data": {"result": [{"metric": {"__name__": "3fooµ¥", "3👋tfにちは": "漢©®€£"}}]}}`)
	fn := func(query string) {
		got = sut.PrometheusAPIV1Query(t, query, apptest.QueryOpts{
			Step: "5m",
			Time: "2024-01-01T00:01:00.000Z",
		})
		want.Data.Result[0].Sample = apptest.NewSample(t, "2024-01-01T00:01:00Z", 1)
		if diff := cmp.Diff(want, got, cmpOptions...); diff != "" {
			t.Errorf("unexpected response (-want, +got):\n%s", diff)
		}
	}

	fn(`{"3fooµ¥"}`)
	fn(`{__name__="3fooµ¥"}`)
	fn(`{__name__=~"3fo.*"}`)
	fn(`{__name__=~".*µ¥"}`)
	fn(`{"3fooµ¥", "3👋tfにちは"="漢©®€£"}`)
	fn(`{"3fooµ¥", "3👋tfにちは"=~"漢.*"}`)
	fn(`{"3👋tfにちは"="漢©®€£"}`)
}

var staleNaNsData = func() []pb.TimeSeries {
	return []pb.TimeSeries{
		{
			Labels: []pb.Label{
				{
					Name:  "__name__",
					Value: "metric",
				},
			},
			Samples: []pb.Sample{
				{
					Value:     1,
					Timestamp: millis("2024-01-01T00:01:00Z"),
				},
				{
					Value:     decimal.StaleNaN,
					Timestamp: millis("2024-01-01T00:02:00Z"),
				},
			},
		},
	}
}()

func testInstantQueryDoesNotReturnStaleNaNs(t *testing.T, sut apptest.PrometheusWriteQuerier) {

	sut.PrometheusAPIV1Write(t, staleNaNsData, apptest.QueryOpts{})
	sut.ForceFlush(t)

	var got, want *apptest.PrometheusAPIV1QueryResponse
	cmpOptions := []cmp.Option{
		cmpopts.IgnoreFields(apptest.PrometheusAPIV1QueryResponse{}, "Status", "Data.ResultType"),
		cmpopts.EquateNaNs(),
	}

	// Verify that instant query returns the first point.

	got = sut.PrometheusAPIV1Query(t, "metric", apptest.QueryOpts{
		Step: "5m",
		Time: "2024-01-01T00:01:00.000Z",
	})
	want = apptest.NewPrometheusAPIV1QueryResponse(t, `{"data": {"result": [{"metric": {"__name__": "metric"}}]}}`)
	want.Data.Result[0].Sample = apptest.NewSample(t, "2024-01-01T00:01:00Z", 1)
	if diff := cmp.Diff(want, got, cmpOptions...); diff != "" {
		t.Errorf("unexpected response (-want, +got):\n%s", diff)
	}

	// Verify that instant query does not return stale NaN.

	got = sut.PrometheusAPIV1Query(t, "metric", apptest.QueryOpts{
		Step: "5m",
		Time: "2024-01-01T00:02:00.000Z",
	})
	want = apptest.NewPrometheusAPIV1QueryResponse(t, `{"data": {"result": []}}`)
	// Empty response, stale NaN is not included into response
	if diff := cmp.Diff(want, got, cmpOptions...); diff != "" {
		t.Errorf("unexpected response (-want, +got):\n%s", diff)
	}

	// Verify that instant query with default rollup function returns stale NaN
	// while it must not.
	// See https://github.com/VictoriaMetrics/VictoriaMetrics/issues/5806

	got = sut.PrometheusAPIV1Query(t, "metric[2m]", apptest.QueryOpts{
		Step: "5m",
		Time: "2024-01-01T00:02:00.000Z",
	})
	want = apptest.NewPrometheusAPIV1QueryResponse(t, `{"data": {"result": [{"metric": {"__name__": "metric"}, "values": []}]}}`)
	s := make([]*apptest.Sample, 2)
	s[0] = apptest.NewSample(t, "2024-01-01T00:01:00Z", 1)
	s[1] = apptest.NewSample(t, "2024-01-01T00:02:00Z", decimal.StaleNaN)
	want.Data.Result[0].Samples = s
	if diff := cmp.Diff(want, got, cmpOptions...); diff != "" {
		t.Errorf("unexpected response (-want, +got):\n%s", diff)
	}

	// Verify that exported data contains stale NaN.

	got = sut.PrometheusAPIV1Export(t, `{__name__="metric"}`, apptest.QueryOpts{
		Start: "2024-01-01T00:01:00.000Z",
		End:   "2024-01-01T00:02:00.000Z",
	})
	want = apptest.NewPrometheusAPIV1QueryResponse(t, `{"data": {"result": [{"metric": {"__name__": "metric"}, "values": []}]}}`)
	s = make([]*apptest.Sample, 2)
	s[0] = apptest.NewSample(t, "2024-01-01T00:01:00Z", 1)
	s[1] = apptest.NewSample(t, "2024-01-01T00:02:00Z", decimal.StaleNaN)
	want.Data.Result[0].Samples = s
	if diff := cmp.Diff(want, got, cmpOptions...); diff != "" {
		t.Errorf("unexpected response (-want, +got):\n%s", diff)
	}
}
