package loganalytics

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"testing"

	"github.com/grafana/grafana-plugin-sdk-go/data"
	"github.com/grafana/grafana/pkg/tsdb/azuremonitor/kinds/dataquery"
	"github.com/grafana/grafana/pkg/tsdb/azuremonitor/testdata"
	"github.com/grafana/grafana/pkg/tsdb/azuremonitor/types"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestLogTableToFrame(t *testing.T) {
	tests := []struct {
		name          string
		testFile      string
		expectedFrame func() *data.Frame
	}{
		{
			name:     "single series",
			testFile: "loganalytics/1-log-analytics-response-metrics-single-series.json",
		},
		{
			name:     "response table",
			testFile: "loganalytics/6-log-analytics-response-table.json",
		},
		{
			name:     "all supported field types",
			testFile: "loganalytics/7-log-analytics-all-types-table.json",
		},
		{
			name:     "nan and infinity in real response",
			testFile: "loganalytics/8-log-analytics-response-nan-inf.json",
		},
		{
			name:     "data and error in real response",
			testFile: "loganalytics/9-log-analytics-response-error.json",
		},
		{
			name:     "data and warning in real response",
			testFile: "loganalytics/10-log-analytics-response-warning.json",
		},
		{
			name:     "empty data response",
			testFile: "loganalytics/11-log-analytics-response-empty.json",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			res := loadTestFileWithNumber(t, tt.testFile)
			frame, err := ResponseTableToFrame(&res.Tables[0], "A", "query", dataquery.AzureQueryTypeLogAnalytics, dataquery.ResultFormatTable, false)
			appendErrorNotice(frame, res.Error)
			require.NoError(t, err)

			testdata.CheckGoldenFrame(t, "../testdata", tt.testFile, frame)
		})
	}
}

func TestTraceTableToFrame(t *testing.T) {
	tests := []struct {
		name          string
		testFile      string
		expectedFrame func() *data.Frame
		resultFormat  dataquery.ResultFormat
		queryType     dataquery.AzureQueryType
	}{
		{
			name:         "multi trace",
			testFile:     "traces/1-traces-multiple-table.json",
			resultFormat: dataquery.ResultFormatTable,
			queryType:    dataquery.AzureQueryTypeAzureTraces,
		},
		{
			name:         "multi trace as trace format",
			testFile:     "traces/1-traces-multiple-table.json",
			resultFormat: dataquery.ResultFormatTrace,
			queryType:    dataquery.AzureQueryTypeAzureTraces,
		},
		{
			name:         "single trace",
			testFile:     "traces/2-traces-single-table.json",
			resultFormat: dataquery.ResultFormatTable,
			queryType:    dataquery.AzureQueryTypeAzureTraces,
		},
		{
			name:         "single trace as trace format",
			testFile:     "traces/2-traces-single-table.json",
			resultFormat: dataquery.ResultFormatTrace,
			queryType:    dataquery.AzureQueryTypeAzureTraces,
		},
		{
			name:         "single trace with empty serviceTags and tags",
			testFile:     "traces/3-traces-empty-dynamics.json",
			resultFormat: dataquery.ResultFormatTrace,
			queryType:    dataquery.AzureQueryTypeAzureTraces,
		},
		{
			name:         "single trace as trace format from exemplars query",
			testFile:     "traces/2-traces-single-table.json",
			resultFormat: dataquery.ResultFormatTrace,
			queryType:    dataquery.AzureQueryTypeTraceExemplar,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			res := loadTestFileWithNumber(t, tt.testFile)
			frame, err := ResponseTableToFrame(&res.Tables[0], "A", "query", tt.queryType, tt.resultFormat, false)
			appendErrorNotice(frame, res.Error)
			require.NoError(t, err)

			testdata.CheckGoldenFrame(t, "../testdata", fmt.Sprintf("%s.%s", tt.testFile, strings.ReplaceAll(tt.name, " ", "-")), frame)
		})
	}
}

func TestLargeLogsResponse(t *testing.T) {
	t.Run("large logs response with limit enabled", func(t *testing.T) {
		res := AzureLogAnalyticsResponse{
			Tables: []types.AzureResponseTable{
				{Name: "PrimaryResult",
					Columns: []struct {
						Name string `json:"name"`
						Type string `json:"type"`
					}{
						{Name: "value", Type: "int"},
					}},
			},
		}
		rows := [][]any{}
		for i := 0; i <= 30000; i++ {
			rows = append(rows, []any{json.Number(strconv.Itoa(i))})
		}
		res.Tables[0].Rows = rows
		resultFormat := dataquery.ResultFormatLogs
		frame, err := ResponseTableToFrame(&res.Tables[0], "A", "query", dataquery.AzureQueryTypeLogAnalytics, resultFormat, false)
		appendErrorNotice(frame, res.Error)
		require.NoError(t, err)
		require.Equal(t, frame.Rows(), 30000)
		require.Len(t, frame.Meta.Notices, 1)
		require.Equal(t, frame.Meta.Notices[0], data.Notice{
			Severity: data.NoticeSeverityWarning,
			Text:     "The number of results in the result set has been limited to 30,000.",
		})
	})

	t.Run("large logs response with limit disabled", func(t *testing.T) {
		res := AzureLogAnalyticsResponse{
			Tables: []types.AzureResponseTable{
				{Name: "PrimaryResult",
					Columns: []struct {
						Name string `json:"name"`
						Type string `json:"type"`
					}{
						{Name: "value", Type: "int"},
					}},
			},
		}
		rows := [][]any{}
		for i := 0; i < 40000; i++ {
			rows = append(rows, []any{json.Number(strconv.Itoa(i))})
		}
		res.Tables[0].Rows = rows
		resultFormat := dataquery.ResultFormatLogs
		frame, err := ResponseTableToFrame(&res.Tables[0], "A", "query", dataquery.AzureQueryTypeLogAnalytics, resultFormat, true)
		appendErrorNotice(frame, res.Error)
		require.NoError(t, err)
		require.Equal(t, frame.Rows(), 40000)
		require.Nil(t, frame.Meta.Notices)
	})
}

func loadTestFileWithNumber(t *testing.T, name string) AzureLogAnalyticsResponse {
	t.Helper()
	path := filepath.Join("../testdata", name)
	// Ignore gosec warning G304 since it's a test
	// nolint:gosec
	f, err := os.Open(path)
	require.NoError(t, err)
	defer func() {
		err := f.Close()
		assert.NoError(t, err)
	}()

	d := json.NewDecoder(f)
	d.UseNumber()
	var data AzureLogAnalyticsResponse
	err = d.Decode(&data)
	require.NoError(t, err)
	return data
}
