package alerting

import (
	"fmt"
	"testing"
	"time"

	"github.com/grafana/grafana/pkg/components/simplejson"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/plugins"
	"github.com/grafana/grafana/pkg/services/sqlstore"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type FakeCondition struct{}

func (f *FakeCondition) Eval(context *EvalContext, reqHandler plugins.DataRequestHandler) (*ConditionResult, error) {
	return &ConditionResult{}, nil
}

func TestAlertRuleFrequencyParsing(t *testing.T) {
	tcs := []struct {
		input  string
		err    error
		result int64
	}{
		{input: "10s", result: 10},
		{input: "10m", result: 600},
		{input: "1h", result: 3600},
		{input: "1d", result: 86400},
		{input: "1o", err: ErrWrongUnitFormat},
		{input: "0s", err: ErrFrequencyCannotBeZeroOrLess},
		{input: "0m", err: ErrFrequencyCannotBeZeroOrLess},
		{input: "0h", err: ErrFrequencyCannotBeZeroOrLess},
		{input: "0", err: ErrFrequencyCouldNotBeParsed},
		{input: "", err: ErrFrequencyCouldNotBeParsed},
		{input: "-1s", err: ErrFrequencyCouldNotBeParsed},
	}

	for _, tc := range tcs {
		t.Run(tc.input, func(t *testing.T) {
			r, err := getTimeDurationStringToSeconds(tc.input)
			if tc.err == nil {
				require.NoError(t, err)
			} else {
				require.EqualError(t, err, tc.err.Error())
			}
			assert.Equal(t, tc.result, r)
		})
	}
}

func TestAlertRuleForParsing(t *testing.T) {
	tcs := []struct {
		input  string
		err    error
		result time.Duration
	}{
		{input: "10s", result: time.Duration(10000000000)},
		{input: "10m", result: time.Duration(600000000000)},
		{input: "1h", result: time.Duration(3600000000000)},
		{input: "1o", err: fmt.Errorf("alert validation error: could not parse for field, error: %s", ErrWrongUnitFormat)},
		{input: "1", err: fmt.Errorf("alert validation error: no specified unit, error: %s", ErrWrongUnitFormat)},
		{input: "0s", result: time.Duration(0)},
		{input: "0m", result: time.Duration(0)},
		{input: "0h", result: time.Duration(0)},
		{input: "0", result: time.Duration(0)},
		{input: "", result: time.Duration(0)},
	}

	for _, tc := range tcs {
		t.Run(tc.input, func(t *testing.T) {
			r, err := getForValue(tc.input)
			if tc.err == nil {
				require.NoError(t, err)
			} else {
				require.EqualError(t, err, tc.err.Error())
			}
			assert.Equal(t, tc.result, r)
		})
	}
}

func TestAlertRuleModel(t *testing.T) {
	sqlstore.InitTestDB(t)
	RegisterCondition("test", func(model *simplejson.Json, index int) (Condition, error) {
		return &FakeCondition{}, nil
	})

	firstNotification := models.CreateAlertNotificationCommand{Uid: "notifier1", OrgId: 1, Name: "1"}
	err := sqlstore.CreateAlertNotificationCommand(&firstNotification)
	require.Nil(t, err)
	secondNotification := models.CreateAlertNotificationCommand{Uid: "notifier2", OrgId: 1, Name: "2"}
	err = sqlstore.CreateAlertNotificationCommand(&secondNotification)
	require.Nil(t, err)

	t.Run("Testing alert rule with notification id and uid", func(t *testing.T) {
		json := `
				{
					"name": "name2",
					"description": "desc2",
					"handler": 0,
					"noDataMode": "critical",
					"enabled": true,
					"frequency": "60s",
					"conditions": [
						{
							"type": "test",
							"prop": 123
						}
					],
					"notifications": [
						{"id": 1},
						{"uid": "notifier2"}
					]
				}
				`

		alertJSON, jsonErr := simplejson.NewJson([]byte(json))
		require.Nil(t, jsonErr)

		alert := &models.Alert{
			Id:          1,
			OrgId:       1,
			DashboardId: 1,
			PanelId:     1,

			Settings: alertJSON,
		}

		alertRule, err := NewRuleFromDBAlert(alert, false)
		require.Nil(t, err)

		require.Len(t, alertRule.Conditions, 1)
		require.Len(t, alertRule.Notifications, 2)

		require.Contains(t, alertRule.Notifications, "notifier2")
		require.Contains(t, alertRule.Notifications, "notifier1")
	})

	t.Run("Testing alert rule with non existing notification id", func(t *testing.T) {
		json := `
				{
					"name": "name3",
					"description": "desc3",
					"handler": 0,
					"noDataMode": "critical",
					"enabled": true,
					"frequency": "60s",
					"conditions": [{"type": "test", "prop": 123 }],
					"notifications": [
						{"id": 999},
						{"uid": "notifier2"}
					]
				}
				`

		alertJSON, jsonErr := simplejson.NewJson([]byte(json))
		require.Nil(t, jsonErr)

		alert := &models.Alert{
			Id:          1,
			OrgId:       1,
			DashboardId: 1,
			PanelId:     1,

			Settings: alertJSON,
		}

		alertRule, err := NewRuleFromDBAlert(alert, false)
		require.Nil(t, err)
		require.NotContains(t, alertRule.Notifications, "999")
		require.Contains(t, alertRule.Notifications, "notifier2")
	})

	t.Run("Testing alert rule which can construct alert rule model with invalid frequency", func(t *testing.T) {
		json := `
			{
				"name": "name2",
				"description": "desc2",
				"noDataMode": "critical",
				"enabled": true,
				"frequency": "0s",
				"conditions": [ { "type": "test", "prop": 123 } ],
				"notifications": []
			}`

		alertJSON, jsonErr := simplejson.NewJson([]byte(json))
		require.Nil(t, jsonErr)

		alert := &models.Alert{
			Id:          1,
			OrgId:       1,
			DashboardId: 1,
			PanelId:     1,
			Frequency:   0,

			Settings: alertJSON,
		}

		alertRule, err := NewRuleFromDBAlert(alert, false)
		require.Nil(t, err)
		require.EqualValues(t, alertRule.Frequency, 60)
	})

	t.Run("Testing alert rule which will raise error in case of missing notification id and uid", func(t *testing.T) {
		json := `
			{
				"name": "name2",
				"description": "desc2",
				"noDataMode": "critical",
				"enabled": true,
				"frequency": "60s",
				"conditions": [
					{
						"type": "test",
						"prop": 123
					}
				],
				"notifications": [
					{"not_id_uid": "1134"}
				]
			}
			`

		alertJSON, jsonErr := simplejson.NewJson([]byte(json))
		require.Nil(t, jsonErr)

		alert := &models.Alert{
			Id:          1,
			OrgId:       1,
			DashboardId: 1,
			PanelId:     1,
			Frequency:   0,

			Settings: alertJSON,
		}

		_, err := NewRuleFromDBAlert(alert, false)
		require.NotNil(t, err)
		require.EqualValues(t, err.Error(), "alert validation error: Neither id nor uid is specified in 'notifications' block, type assertion to string failed AlertId: 1 PanelId: 1 DashboardId: 1")
	})
}
