package influxdb

import (
	"encoding/json"
	"io"
	"strings"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/grafana/grafana-plugin-sdk-go/backend"
	"github.com/grafana/grafana-plugin-sdk-go/data"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/xorcare/pointer"
)

func prepare(text string) io.ReadCloser {
	return io.NopCloser(strings.NewReader(text))
}

func addQueryToQueries(query Query) []Query {
	var queries []Query
	query.RefID = "A"
	query.RawQuery = "Test raw query"
	queries = append(queries, query)
	return queries
}

func TestInfluxdbResponseParser(t *testing.T) {
	t.Run("Influxdb response parser should handle invalid JSON", func(t *testing.T) {
		parser := &ResponseParser{}

		response := `{ invalid }`

		query := &Query{}

		result := parser.Parse(prepare(response), addQueryToQueries(*query))

		require.Nil(t, result.Responses["A"].Frames)
		require.Error(t, result.Responses["A"].Error)
	})

	t.Run("Influxdb response parser should parse everything normally", func(t *testing.T) {
		parser := &ResponseParser{}

		response := `
		{
			"results": [
				{
					"series": [
						{
							"name": "cpu",
							"columns": ["time","mean","path","isActive"],
							"tags": {"datacenter": "America"},
							"values": [
								[111,222,"/usr/path",true],
								[111,222,"/usr/path",false],
								[111,null,"/usr/path",true]
							]
						}
					]
				}
			]
		}
		`

		query := &Query{}
		labels, err := data.LabelsFromString("datacenter=America")
		require.Nil(t, err)

		floatField := data.NewField("value", labels, []*float64{
			pointer.Float64(222), pointer.Float64(222), nil,
		})
		floatField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean { datacenter: America }"}
		floatFrame := data.NewFrame("cpu.mean { datacenter: America }",
			data.NewField("time", nil,
				[]time.Time{
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
				}),
			floatField,
		)
		floatFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}

		stringField := data.NewField("value", labels, []string{
			"/usr/path", "/usr/path", "/usr/path",
		})
		stringField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.path { datacenter: America }"}
		stringFrame := data.NewFrame("cpu.path { datacenter: America }",
			data.NewField("time", nil,
				[]time.Time{
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
				}),
			stringField,
		)
		stringFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}

		boolField := data.NewField("value", labels, []bool{
			true, false, true,
		})
		boolField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.isActive { datacenter: America }"}
		boolFrame := data.NewFrame("cpu.isActive { datacenter: America }",
			data.NewField("time", nil,
				[]time.Time{
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
				}),
			boolField,
		)
		boolFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}

		result := parser.Parse(prepare(response), addQueryToQueries(*query))

		frame := result.Responses["A"]
		if diff := cmp.Diff(floatFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
			t.Errorf("Result mismatch (-want +got):\n%s", diff)
		}
		if diff := cmp.Diff(stringFrame, frame.Frames[1], data.FrameTestCompareOptions()...); diff != "" {
			t.Errorf("Result mismatch (-want +got):\n%s", diff)
		}
		if diff := cmp.Diff(boolFrame, frame.Frames[2], data.FrameTestCompareOptions()...); diff != "" {
			t.Errorf("Result mismatch (-want +got):\n%s", diff)
		}
	})

	t.Run("Influxdb response parser should parse metricFindQueries normally", func(t *testing.T) {
		parser := &ResponseParser{}

		response := `
		{
			"results": [
				{
					"series": [
						{
							"refId": "metricFindQuery",
							"name": "cpu",
							"values": [
								["cpu"],
								["disk"],
								["logs"]
							]
						}
					]
				}
			]
		}
		`

		var queries []Query
		queries = append(queries, Query{RefID: "metricFindQuery"})
		newField := data.NewField("value", nil, []string{
			"cpu", "disk", "logs",
		})
		testFrame := data.NewFrame("cpu",
			newField,
		)

		result := parser.Parse(prepare(response), queries)

		frame := result.Responses["metricFindQuery"]
		if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
			t.Errorf("Result mismatch (-want +got):\n%s", diff)
		}
	})

	t.Run("Influxdb response parser should parse metricFindQueries->SHOW TAG VALUES normally", func(t *testing.T) {
		parser := &ResponseParser{}

		response := `
		{
			"results": [
				{
					"series": [
						{
							"name": "cpu",
							"values": [
								["values", "cpu-total"],
								["values", "cpu0"],
								["values", "cpu1"]
							]
						}
					]
				}
			]
		}
		`

		var queries []Query
		queries = append(queries, Query{RawQuery: "SHOW TAG VALUES", RefID: "metricFindQuery"})
		newField := data.NewField("value", nil, []string{
			"cpu-total", "cpu0", "cpu1",
		})
		testFrame := data.NewFrame("cpu",
			newField,
		)

		result := parser.Parse(prepare(response), queries)

		frame := result.Responses["metricFindQuery"]
		if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
			t.Errorf("Result mismatch (-want +got):\n%s", diff)
		}
	})

	t.Run("Influxdb response parser should parse two responses with different refIDs", func(t *testing.T) {
		parser := &ResponseParser{}

		response := `
		{
			"results": [
				{
					"series": [{}]
				},
				{
					"series": [{}]
				}
			]
		}
		`

		query := &Query{}
		var queries = addQueryToQueries(*query)
		queryB := &Query{}
		queryB.RefID = "B"
		queries = append(queries, *queryB)
		result := parser.Parse(prepare(response), queries)

		assert.Len(t, result.Responses, 2)
		assert.Contains(t, result.Responses, "A")
		assert.Contains(t, result.Responses, "B")
		assert.NotContains(t, result.Responses, "C")
	})

	t.Run("Influxdb response parser populates the RawQuery in the response meta ExecutedQueryString", func(t *testing.T) {
		parser := &ResponseParser{}

		response := `
		{
			"results": [
				{
					"series": [
						{
							"name": "cpu",
							"columns": ["time","cpu"],
							"values": [
								["values", "cpu-total"],
								["values", "cpu0"],
								["values", "cpu1"]
							]
						}
					]
				}
			]
		}
		`

		query := &Query{}
		query.RawQuery = "Test raw query"
		result := parser.Parse(prepare(response), addQueryToQueries(*query))

		frame := result.Responses["A"]
		assert.Equal(t, frame.Frames[0].Meta.ExecutedQueryString, "Test raw query")
	})

	t.Run("Influxdb response parser with invalid value-format", func(t *testing.T) {
		parser := &ResponseParser{}

		response := `
		{
			"results": [
				{
					"series": [
						{
							"name": "cpu",
							"columns": ["time","mean"],
							"values": [
								[100,50],
								[101,"hello"],
								[102,52]
							]
						}
					]
				}
			]
		}
		`

		query := &Query{}

		newField := data.NewField("value", nil, []*float64{
			pointer.Float64(50), nil, pointer.Float64(52),
		})
		newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"}
		testFrame := data.NewFrame("cpu.mean",
			data.NewField("time", nil,
				[]time.Time{
					time.Date(1970, 1, 1, 0, 0, 0, 100000000, time.UTC),
					time.Date(1970, 1, 1, 0, 0, 0, 101000000, time.UTC),
					time.Date(1970, 1, 1, 0, 0, 0, 102000000, time.UTC),
				}),
			newField,
		)
		testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}

		result := parser.Parse(prepare(response), addQueryToQueries(*query))

		frame := result.Responses["A"]
		if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
			t.Errorf("Result mismatch (-want +got):\n%s", diff)
		}
	})

	t.Run("Influxdb response parser with invalid timestamp-format", func(t *testing.T) {
		parser := &ResponseParser{}

		response := `
		{
			"results": [
				{
					"series": [
						{
							"name": "cpu",
							"columns": ["time","mean"],
							"values": [
								[100,50],
								["hello",51],
								["hello","hello"],
								[102,52]
							]
						}
					]
				}
			]
		}
		`

		query := &Query{}

		newField := data.NewField("value", nil, []*float64{
			pointer.Float64(50), pointer.Float64(52),
		})
		newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"}
		testFrame := data.NewFrame("cpu.mean",
			data.NewField("time", nil,
				[]time.Time{
					time.Date(1970, 1, 1, 0, 0, 0, 100000000, time.UTC),
					time.Date(1970, 1, 1, 0, 0, 0, 102000000, time.UTC),
				}),
			newField,
		)
		testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}

		result := parser.Parse(prepare(response), addQueryToQueries(*query))

		frame := result.Responses["A"]
		if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
			t.Errorf("Result mismatch (-want +got):\n%s", diff)
		}
	})

	t.Run("Influxdb response parser with alias", func(t *testing.T) {
		parser := &ResponseParser{}

		response := `
		{
			"results": [
				{
					"series": [
						{
							"name": "cpu.upc",
							"columns": ["time","mean","sum"],
							"tags": {
								"datacenter": "America",
								"dc.region.name": "Northeast",
								"cluster-name":   "Cluster",
								"/cluster/name/": "Cluster/",
								"@cluster@name@": "Cluster@"
							},
							"values": [
								[111,222,333]
							]
						}
					]
				}
			]
		}
		`

		query := &Query{Alias: "series alias"}
		labels, err := data.LabelsFromString("/cluster/name/=Cluster/, @cluster@name@=Cluster@, cluster-name=Cluster, datacenter=America, dc.region.name=Northeast")
		require.Nil(t, err)
		newField := data.NewField("value", labels, []*float64{
			pointer.Float64(222),
		})
		newField.Config = &data.FieldConfig{DisplayNameFromDS: "series alias"}
		testFrame := data.NewFrame("series alias",
			data.NewField("time", nil,
				[]time.Time{
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
				}),
			newField,
		)
		testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
		result := parser.Parse(prepare(response), addQueryToQueries(*query))
		t.Run("should parse aliases", func(t *testing.T) {
			frame := result.Responses["A"]
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias $m $measurement", Measurement: "10m"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))

			frame = result.Responses["A"]
			name := "alias 10m 10m"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias $col", Measurement: "10m"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias mean"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}
			name = "alias sum"
			testFrame.Name = name
			newField = data.NewField("value", labels, []*float64{
				pointer.Float64(333),
			})
			testFrame.Fields[1] = newField
			testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name}
			if diff := cmp.Diff(testFrame, frame.Frames[1], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias $tag_datacenter"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias America"
			testFrame.Name = name
			newField = data.NewField("value", labels, []*float64{
				pointer.Float64(222),
			})
			testFrame.Fields[1] = newField
			testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name}
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias $tag_datacenter/$tag_datacenter"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias America/America"
			testFrame.Name = name
			newField = data.NewField("value", labels, []*float64{
				pointer.Float64(222),
			})
			testFrame.Fields[1] = newField
			testFrame.Fields[1].Config = &data.FieldConfig{DisplayNameFromDS: name}
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias [[col]]", Measurement: "10m"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias mean"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias $0 $1 $2 $3 $4"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias cpu upc $2 $3 $4"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias $0, $1 - $2 - $3, $4: something"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias cpu, upc - $2 - $3, $4: something"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias $1"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias upc"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias $5"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias $5"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "series alias"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "series alias"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias [[m]] [[measurement]]", Measurement: "10m"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias 10m 10m"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias [[tag_datacenter]]"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias America"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias [[tag_dc.region.name]]"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias Northeast"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias [[tag_cluster-name]]"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias Cluster"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias [[tag_/cluster/name/]]"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias Cluster/"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias [[tag_@cluster@name@]]"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias Cluster@"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}
		})
		t.Run("shouldn't parse aliases", func(t *testing.T) {
			query = &Query{Alias: "alias words with no brackets"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame := result.Responses["A"]
			name := "alias words with no brackets"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias Test 1.5"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias Test 1.5"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}

			query = &Query{Alias: "alias Test -1"}
			result = parser.Parse(prepare(response), addQueryToQueries(*query))
			frame = result.Responses["A"]
			name = "alias Test -1"
			testFrame.Name = name
			testFrame.Fields[1].Config.DisplayNameFromDS = name
			if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
				t.Errorf("Result mismatch (-want +got):\n%s", diff)
			}
		})
	})

	t.Run("Influxdb response parser with errors", func(t *testing.T) {
		parser := &ResponseParser{}

		response := `
		{
			"results": [
				{
					"series": [
						{
							"name": "cpu",
							"columns": ["time","mean","sum"],
							"tags": {"datacenter": "America"},
							"values": [
								[111,222,333],
								[111,222,333],
								[111,null,333]
							]
						}
					]
				},
				{
					"error": "query-timeout limit exceeded"
				}
			]
		}
		`

		query := &Query{}
		var queries = addQueryToQueries(*query)
		queryB := &Query{}
		queryB.RefID = "B"
		queries = append(queries, *queryB)
		labels, err := data.LabelsFromString("datacenter=America")
		require.Nil(t, err)
		newField := data.NewField("value", labels, []*float64{
			pointer.Float64(222), pointer.Float64(222), nil,
		})
		newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean { datacenter: America }"}
		testFrame := data.NewFrame("cpu.mean { datacenter: America }",
			data.NewField("time", nil,
				[]time.Time{
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
					time.Date(1970, 1, 1, 0, 0, 0, 111000000, time.UTC),
				}),
			newField,
		)
		testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
		result := parser.Parse(prepare(response), queries)

		frame := result.Responses["A"]
		if diff := cmp.Diff(testFrame, frame.Frames[0], data.FrameTestCompareOptions()...); diff != "" {
			t.Errorf("Result mismatch (-want +got):\n%s", diff)
		}

		require.EqualError(t, result.Responses["B"].Error, "query-timeout limit exceeded")
	})

	t.Run("Influxdb response parser with top-level error", func(t *testing.T) {
		parser := &ResponseParser{}

		response := `
		{
			"error": "error parsing query: found THING"
		}
		`

		query := &Query{}

		result := parser.Parse(prepare(response), addQueryToQueries(*query))

		require.Nil(t, result.Responses["A"].Frames)

		require.EqualError(t, result.Responses["A"].Error, "error parsing query: found THING")
	})

	t.Run("Influxdb response parser parseNumber nil", func(t *testing.T) {
		value := parseNumber(nil)
		require.Nil(t, value)
	})

	t.Run("Influxdb response parser parseNumber valid JSON.number", func(t *testing.T) {
		value := parseNumber(json.Number("95.4"))
		require.Equal(t, *value, 95.4)
	})

	t.Run("Influxdb response parser parseNumber invalid type", func(t *testing.T) {
		value := parseNumber("95.4")
		require.Nil(t, value)
	})

	t.Run("Influxdb response parser parseTimestamp valid JSON.number", func(t *testing.T) {
		// currently we use milliseconds-precision with influxdb, so the test works with that.
		// if we change this to for example nanoseconds-precision, the tests will have to change.
		timestamp, err := parseTimestamp(json.Number("1609556645000"))
		require.NoError(t, err)
		require.Equal(t, timestamp.Format(time.RFC3339), "2021-01-02T03:04:05Z")
	})

	t.Run("Influxdb response parser parseNumber invalid type", func(t *testing.T) {
		_, err := parseTimestamp("hello")
		require.Error(t, err)
	})
}

func TestResponseParser_Parse(t *testing.T) {
	tests := []struct {
		name  string
		input string
		f     func(t *testing.T, got *backend.QueryDataResponse)
	}{
		{
			name: "Influxdb response parser with valid value when null values returned",
			input: `{ "results": [ { "series": [ {
				"name": "cpu",
				"columns": ["time","mean"],
				"values": [
					[100,null],
					[101,null],
					[102,52]
				]
			}]}]}`,
			f: func(t *testing.T, got *backend.QueryDataResponse) {
				newField := data.NewField("value", nil, []*float64{nil, nil, pointer.Float64(52)})
				newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"}
				testFrame := data.NewFrame("cpu.mean",
					data.NewField("time", nil,
						[]time.Time{
							time.Date(1970, 1, 1, 0, 0, 0, 100000000, time.UTC),
							time.Date(1970, 1, 1, 0, 0, 0, 101000000, time.UTC),
							time.Date(1970, 1, 1, 0, 0, 0, 102000000, time.UTC),
						}),
					newField,
				)
				testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
				assert.Equal(t, testFrame, got.Responses["A"].Frames[0])
			},
		},
		{
			name: "Influxdb response parser with valid value when all values are null",
			input: `{ "results": [ { "series": [ {
				"name": "cpu",
				"columns": ["time","mean"],
				"values": [
					[100,null],
					[101,null],
					[102,null]
				]
			}]}]}`,
			f: func(t *testing.T, got *backend.QueryDataResponse) {
				newField := data.NewField("value", nil, []*float64{nil, nil, nil})
				newField.Config = &data.FieldConfig{DisplayNameFromDS: "cpu.mean"}
				testFrame := data.NewFrame("cpu.mean",
					data.NewField("time", nil,
						[]time.Time{
							time.Date(1970, 1, 1, 0, 0, 0, 100000000, time.UTC),
							time.Date(1970, 1, 1, 0, 0, 0, 101000000, time.UTC),
							time.Date(1970, 1, 1, 0, 0, 0, 102000000, time.UTC),
						}),
					newField,
				)
				testFrame.Meta = &data.FrameMeta{ExecutedQueryString: "Test raw query"}
				assert.Equal(t, testFrame, got.Responses["A"].Frames[0])
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			parser := &ResponseParser{}
			got := parser.Parse(prepare(tt.input), addQueryToQueries(Query{}))
			require.NotNil(t, got)
			if tt.f != nil {
				tt.f(t, got)
			}
		})
	}
}
