package alerting

import (
	"testing"

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

type FakeCondition struct{}

func (f *FakeCondition) Eval(context *EvalContext) (*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", result: 1},
		{input: "0s", err: ErrFrequencyCannotBeZeroOrLess},
		{input: "0m", err: ErrFrequencyCannotBeZeroOrLess},
		{input: "0h", err: ErrFrequencyCannotBeZeroOrLess},
		{input: "0", err: ErrFrequencyCannotBeZeroOrLess},
		{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 TestAlertRuleModel(t *testing.T) {
	sqlstore.InitTestDB(t)
	Convey("Testing alert rule", t, func() {
		RegisterCondition("test", func(model *simplejson.Json, index int) (Condition, error) {
			return &FakeCondition{}, nil
		})

		Convey("should return err for empty string", func() {
			_, err := getTimeDurationStringToSeconds("")
			So(err, ShouldNotBeNil)
		})

		Convey("can construct alert rule model", func() {
			firstNotification := models.CreateAlertNotificationCommand{Uid: "notifier1", OrgId: 1, Name: "1"}
			err := sqlstore.CreateAlertNotificationCommand(&firstNotification)
			So(err, ShouldBeNil)
			secondNotification := models.CreateAlertNotificationCommand{Uid: "notifier2", OrgId: 1, Name: "2"}
			err = sqlstore.CreateAlertNotificationCommand(&secondNotification)
			So(err, ShouldBeNil)

			Convey("with notification id and uid", func() {
				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))
				So(jsonErr, ShouldBeNil)

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

					Settings: alertJSON,
				}

				alertRule, err := NewRuleFromDBAlert(alert, false)
				So(err, ShouldBeNil)

				So(len(alertRule.Conditions), ShouldEqual, 1)
				So(len(alertRule.Notifications), ShouldEqual, 2)

				Convey("Can read Id and Uid notifications (translate Id to Uid)", func() {
					So(alertRule.Notifications, ShouldContain, "notifier2")
					So(alertRule.Notifications, ShouldContain, "notifier1")
				})
			})
		})

		Convey("with non existing notification id", func() {
			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))
			So(jsonErr, ShouldBeNil)

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

				Settings: alertJSON,
			}

			alertRule, err := NewRuleFromDBAlert(alert, false)
			Convey("swallows the error", func() {
				So(err, ShouldBeNil)
				So(alertRule.Notifications, ShouldNotContain, "999")
				So(alertRule.Notifications, ShouldContain, "notifier2")
			})
		})

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

			alertJSON, jsonErr := simplejson.NewJson([]byte(json))
			So(jsonErr, ShouldBeNil)

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

				Settings: alertJSON,
			}

			alertRule, err := NewRuleFromDBAlert(alert, false)
			So(err, ShouldBeNil)
			So(alertRule.Frequency, ShouldEqual, 60)
		})

		Convey("raise error in case of missing notification id and uid", func() {
			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))
			So(jsonErr, ShouldBeNil)

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

				Settings: alertJSON,
			}

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