package loki

import (
	"context"
	"net/http"
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/grafana/grafana-plugin-sdk-go/backend"
	"github.com/grafana/grafana-plugin-sdk-go/experimental"
	"github.com/stretchr/testify/require"
)

// NOTE: in these tests there several different json-content-types.
// different versions of Loki use different json-content-types.
// this is probably not important when we will parse them,
// but i wanted to test for all of them, to be sure.

func TestSuccessResponse(t *testing.T) {
	matrixQuery := lokiQuery{Expr: "up(ALERTS)", Step: time.Second * 42, QueryType: QueryTypeRange, Direction: DirectionBackward, RefID: "mq"}
	vectorQuery := lokiQuery{Expr: "query1", QueryType: QueryTypeInstant, Direction: DirectionBackward, RefID: "vq"}
	streamsQuery := lokiQuery{Expr: "query1", QueryType: QueryTypeRange, Direction: DirectionBackward, RefID: "sq"}

	tt := []struct {
		name     string
		filepath string
		query    lokiQuery
	}{
		{name: "parse a simple matrix response", filepath: "matrix_simple", query: matrixQuery},
		{name: "parse a matrix response with a time-gap in the middle", filepath: "matrix_gap", query: matrixQuery},
		// you can produce NaN by having a metric query and add ` / 0` to the end
		{name: "parse a matrix response with NaN", filepath: "matrix_nan", query: matrixQuery},
		// you can produce Infinity by using `quantile_over_time(42,` (value larger than 1)
		{name: "parse a matrix response with Infinity", filepath: "matrix_inf", query: matrixQuery},
		{name: "parse a matrix response with very small step value", filepath: "matrix_small_step", query: matrixQuery},

		// Prometheus handles the `__name__` label in a special way, but Loki should not.
		{name: "parse a matrix response with __name__ label normally", filepath: "matrix_name", query: matrixQuery},

		// loki adds stats to matrix-responses too
		{name: "parse a matrix response with stats", filepath: "matrix_with_stats", query: matrixQuery},

		{name: "parse a simple vector response", filepath: "vector_simple", query: vectorQuery},
		{name: "parse a vector response with special values", filepath: "vector_special_values", query: vectorQuery},

		{name: "parse a simple streams response", filepath: "streams_simple", query: streamsQuery},

		{name: "parse a streams response with parse errors", filepath: "streams_parse_errors", query: streamsQuery},

		{name: "parse an empty response", filepath: "empty", query: matrixQuery},
	}

	for _, test := range tt {
		t.Run(test.name, func(t *testing.T) {
			responseFileName := filepath.Join("testdata", test.filepath+".json")
			goldenFileName := test.filepath + ".golden"

			bytes, err := os.ReadFile(responseFileName)
			require.NoError(t, err)

			frames, err := runQuery(context.Background(), makeMockedAPI(http.StatusOK, "application/json", bytes, nil), &test.query)
			require.NoError(t, err)

			dr := &backend.DataResponse{
				Frames: frames,
				Error:  err,
			}
			experimental.CheckGoldenJSONResponse(t, "testdata", goldenFileName, dr, true)
		})
	}
}

func TestErrorResponse(t *testing.T) {
	// NOTE: when there is an error-response, it comes with
	// HTTP code 400, and the format seems to change between versions:
	// 2.3.x: content-type=text/plain, content is plaintext
	// 2.4.x+: content-type=application/json, content is plaintext: https://github.com/grafana/loki/issues/4844
	// we should always be able to to return some kind of error message
	tt := []struct {
		name         string
		body         []byte
		contentType  string
		errorMessage string
	}{
		{
			name: "parse an error response in JSON",
			body: []byte(`
			{
				"status": "error",
				"code": 400,
				"message": "parse error at line 1, col 8: something is wrong"
			}`),
			contentType:  "application/json; charset=utf-8",
			errorMessage: "parse error at line 1, col 8: something is wrong",
		},
		{
			name:         "parse a non-json error body with json content type",
			body:         []byte("parse error at line 1, col 8: something is wrong"),
			contentType:  "application/json; charset=UTF-8",
			errorMessage: "parse error at line 1, col 8: something is wrong",
		},
		{
			name:         "parse an error response in plain text",
			body:         []byte("parse error at line 1, col 8: something is wrong"),
			contentType:  "text/plain; charset=utf-8",
			errorMessage: "parse error at line 1, col 8: something is wrong",
		},
		{
			name:         "parse an error response that is broken JSON",
			body:         []byte(`{"message":"error message but the JSON is not finished`),
			contentType:  "text/plain; charset=utf-8",
			errorMessage: `{"message":"error message but the JSON is not finished`,
		},
	}

	for _, test := range tt {
		t.Run(test.name, func(t *testing.T) {
			frames, err := runQuery(context.Background(), makeMockedAPI(400, test.contentType, test.body, nil), &lokiQuery{QueryType: QueryTypeRange, Direction: DirectionBackward})

			require.Len(t, frames, 0)
			require.Error(t, err)
			require.EqualError(t, err, test.errorMessage)
		})
	}
}
