package ml

import (
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/stretchr/testify/require"
)

func TestUnmarshalCommand(t *testing.T) {
	appURL := "https://grafana.com"

	updateJson := func(cmd string, f func(m map[string]interface{})) func(t *testing.T) []byte {
		return func(t *testing.T) []byte {
			var d map[string]interface{}
			require.NoError(t, json.UnmarshalFromString(cmd, &d))
			f(d)
			data, err := json.Marshal(d)
			require.NoError(t, err)
			return data
		}
	}
	t.Run("should parse outlier command", func(t *testing.T) {
		cmd, err := UnmarshalCommand([]byte(outlierQuery), appURL)
		require.NoError(t, err)
		require.IsType(t, &OutlierCommand{}, cmd)
		outlier := cmd.(*OutlierCommand)
		require.Equal(t, 1234*time.Millisecond, outlier.interval)
		require.Equal(t, appURL, outlier.appURL)
		require.Equal(t, OutlierCommandConfiguration{
			DatasourceType: "prometheus",
			DatasourceUID:  "a4ce599c-4c93-44b9-be5b-76385b8c01be",
			QueryParams: map[string]interface{}{
				"expr":  "go_goroutines{}",
				"range": true,
				"refId": "A",
			},
			Algorithm: map[string]interface{}{
				"name": "dbscan",
				"config": map[string]interface{}{
					"epsilon": 7.667,
				},
				"sensitivity": 0.83,
			},
			ResponseType: "binary",
		}, outlier.config)
	})
	t.Run("should fallback to default if 'intervalMs' is not specified", func(t *testing.T) {
		data := updateJson(outlierQuery, func(m map[string]interface{}) {
			delete(m, "intervalMs")
		})(t)
		cmd, err := UnmarshalCommand(data, appURL)
		require.NoError(t, err)
		outlier := cmd.(*OutlierCommand)
		require.Equal(t, defaultInterval, outlier.interval)
	})
	t.Run("fails when", func(t *testing.T) {
		testCases := []struct {
			name   string
			config func(t *testing.T) []byte
			err    string
		}{
			{
				name: "field 'type' is missing",
				config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
					delete(cmd, "type")
				}),
				err: "required field 'type' is not specified or empty.  Should be one of [outlier]",
			},
			{
				name: "field 'type' is not known",
				config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
					cmd["type"] = uuid.NewString()
				}),
				err: "unsupported command type. Should be one of [outlier]",
			},
			{
				name: "field 'type' is not string",
				config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
					cmd["type"] = map[string]interface{}{
						"data": 1,
					}
				}),
				err: "failed to unmarshal Machine learning command",
			},
			{
				name: "field 'config' is missing",
				config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
					delete(cmd, "config")
				}),
				err: "required field 'config' is not specified",
			},
			{
				name: "field 'intervalMs' is not number",
				config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
					cmd["intervalMs"] = "test"
				}),
				err: "failed to unmarshal Machine learning command",
			},
			{
				name: "field 'config.datasource_uid' is not specified",
				config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
					cfg := cmd["config"].(map[string]interface{})
					delete(cfg, "datasource_uid")
				}),
				err: "required field `config.datasource_uid` is not specified",
			},
			{
				name: "field 'config.algorithm' is not specified",
				config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
					cfg := cmd["config"].(map[string]interface{})
					delete(cfg, "algorithm")
				}),
				err: "required field `config.algorithm` is not specified",
			},
			{
				name: "field 'config.response_type' is not specified",
				config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
					cfg := cmd["config"].(map[string]interface{})
					delete(cfg, "response_type")
				}),
				err: "required field `config.response_type` is not specified",
			},
			{
				name: "fields 'config.query' and 'config.query_params' are not specified",
				config: updateJson(outlierQuery, func(cmd map[string]interface{}) {
					cfg := cmd["config"].(map[string]interface{})
					delete(cfg, "query")
					delete(cfg, "query_params")
				}),
				err: "neither of required fields `config.query_params` or `config.query` are specified",
			},
		}

		for _, testCase := range testCases {
			t.Run(testCase.name, func(t *testing.T) {
				_, err := UnmarshalCommand(testCase.config(t), appURL)
				require.ErrorContains(t, err, testCase.err)
			})
		}
	})
}

const outlierQuery = `
{
	"type": "outlier",
	"intervalMs": 1234,
	"config": {
		"datasource_uid": "a4ce599c-4c93-44b9-be5b-76385b8c01be",
		"datasource_type": "prometheus",
		"query_params": {
			"expr": "go_goroutines{}",
			"range": true,
			"refId": "A"
		},
		"response_type": "binary",
		"algorithm": {
			"name": "dbscan",
			"config": {
				"epsilon": 7.667
			},
			"sensitivity": 0.83
		}
	}
}
`
