package cloudwatch

import (
	"context"
	"testing"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
	"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi"
	"github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi/resourcegroupstaggingapiiface"
	"github.com/grafana/grafana/pkg/components/securejsondata"
	"github.com/grafana/grafana/pkg/components/simplejson"
	"github.com/grafana/grafana/pkg/models"
	"github.com/grafana/grafana/pkg/tsdb"
	. "github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/assert"
)

type mockedEc2 struct {
	ec2iface.EC2API
	Resp        ec2.DescribeInstancesOutput
	RespRegions ec2.DescribeRegionsOutput
}

type mockedRGTA struct {
	resourcegroupstaggingapiiface.ResourceGroupsTaggingAPIAPI
	Resp resourcegroupstaggingapi.GetResourcesOutput
}

func (m mockedEc2) DescribeInstancesPages(in *ec2.DescribeInstancesInput, fn func(*ec2.DescribeInstancesOutput, bool) bool) error {
	fn(&m.Resp, true)
	return nil
}
func (m mockedEc2) DescribeRegions(in *ec2.DescribeRegionsInput) (*ec2.DescribeRegionsOutput, error) {
	return &m.RespRegions, nil
}

func (m mockedRGTA) GetResourcesPages(in *resourcegroupstaggingapi.GetResourcesInput, fn func(*resourcegroupstaggingapi.GetResourcesOutput, bool) bool) error {
	fn(&m.Resp, true)
	return nil
}

func TestCloudWatchMetrics(t *testing.T) {

	Convey("When calling getMetricsForCustomMetrics", t, func() {
		dsInfo := &DatasourceInfo{
			Region:        "us-east-1",
			Namespace:     "Foo",
			Profile:       "default",
			AssumeRoleArn: "",
		}
		f := func(dsInfo *DatasourceInfo) (cloudwatch.ListMetricsOutput, error) {
			return cloudwatch.ListMetricsOutput{
				Metrics: []*cloudwatch.Metric{
					{
						MetricName: aws.String("Test_MetricName"),
						Dimensions: []*cloudwatch.Dimension{
							{
								Name: aws.String("Test_DimensionName"),
							},
						},
					},
				},
			}, nil
		}
		metrics, _ := getMetricsForCustomMetrics(dsInfo, f)

		Convey("Should contain Test_MetricName", func() {
			So(metrics, ShouldContain, "Test_MetricName")
		})
	})

	Convey("When calling getDimensionsForCustomMetrics", t, func() {
		dsInfo := &DatasourceInfo{
			Region:        "us-east-1",
			Namespace:     "Foo",
			Profile:       "default",
			AssumeRoleArn: "",
		}
		f := func(dsInfo *DatasourceInfo) (cloudwatch.ListMetricsOutput, error) {
			return cloudwatch.ListMetricsOutput{
				Metrics: []*cloudwatch.Metric{
					{
						MetricName: aws.String("Test_MetricName"),
						Dimensions: []*cloudwatch.Dimension{
							{
								Name: aws.String("Test_DimensionName"),
							},
						},
					},
				},
			}, nil
		}
		dimensionKeys, _ := getDimensionsForCustomMetrics(dsInfo, f)

		Convey("Should contain Test_DimensionName", func() {
			So(dimensionKeys, ShouldContain, "Test_DimensionName")
		})
	})

	Convey("When calling handleGetRegions", t, func() {
		executor := &CloudWatchExecutor{
			ec2Svc: mockedEc2{RespRegions: ec2.DescribeRegionsOutput{
				Regions: []*ec2.Region{
					{
						RegionName: aws.String("ap-northeast-2"),
					},
				},
			}},
		}
		jsonData := simplejson.New()
		jsonData.Set("defaultRegion", "default")
		executor.DataSource = &models.DataSource{
			JsonData:       jsonData,
			SecureJsonData: securejsondata.SecureJsonData{},
		}

		result, _ := executor.handleGetRegions(context.Background(), simplejson.New(), &tsdb.TsdbQuery{})

		Convey("Should return regions", func() {
			So(result[0].Text, ShouldEqual, "ap-northeast-1")
			So(result[1].Text, ShouldEqual, "ap-northeast-2")
		})
	})

	Convey("When calling handleGetEc2InstanceAttribute", t, func() {
		executor := &CloudWatchExecutor{
			ec2Svc: mockedEc2{Resp: ec2.DescribeInstancesOutput{
				Reservations: []*ec2.Reservation{
					{
						Instances: []*ec2.Instance{
							{
								InstanceId: aws.String("i-12345678"),
								Tags: []*ec2.Tag{
									{
										Key:   aws.String("Environment"),
										Value: aws.String("production"),
									},
								},
							},
						},
					},
				},
			}},
		}

		json := simplejson.New()
		json.Set("region", "us-east-1")
		json.Set("attributeName", "InstanceId")
		filters := make(map[string]interface{})
		filters["tag:Environment"] = []string{"production"}
		json.Set("filters", filters)
		result, _ := executor.handleGetEc2InstanceAttribute(context.Background(), json, &tsdb.TsdbQuery{})

		Convey("Should equal production InstanceId", func() {
			So(result[0].Text, ShouldEqual, "i-12345678")
		})
	})

	Convey("When calling handleGetEbsVolumeIds", t, func() {

		executor := &CloudWatchExecutor{
			ec2Svc: mockedEc2{Resp: ec2.DescribeInstancesOutput{
				Reservations: []*ec2.Reservation{
					{
						Instances: []*ec2.Instance{
							{
								InstanceId: aws.String("i-1"),
								BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
									{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-1-1")}},
									{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-1-2")}},
								},
							},
							{
								InstanceId: aws.String("i-2"),
								BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
									{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-2-1")}},
									{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-2-2")}},
								},
							},
						},
					},
					{
						Instances: []*ec2.Instance{
							{
								InstanceId: aws.String("i-3"),
								BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
									{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-3-1")}},
									{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-3-2")}},
								},
							},
							{
								InstanceId: aws.String("i-4"),
								BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
									{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-4-1")}},
									{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-4-2")}},
								},
							},
						},
					},
				},
			}},
		}

		json := simplejson.New()
		json.Set("region", "us-east-1")
		json.Set("instanceId", "{i-1, i-2, i-3, i-4}")
		result, _ := executor.handleGetEbsVolumeIds(context.Background(), json, &tsdb.TsdbQuery{})

		Convey("Should return all 8 VolumeIds", func() {
			So(len(result), ShouldEqual, 8)
			So(result[0].Text, ShouldEqual, "vol-1-1")
			So(result[1].Text, ShouldEqual, "vol-1-2")
			So(result[2].Text, ShouldEqual, "vol-2-1")
			So(result[3].Text, ShouldEqual, "vol-2-2")
			So(result[4].Text, ShouldEqual, "vol-3-1")
			So(result[5].Text, ShouldEqual, "vol-3-2")
			So(result[6].Text, ShouldEqual, "vol-4-1")
			So(result[7].Text, ShouldEqual, "vol-4-2")
		})
	})

	Convey("When calling handleGetResourceArns", t, func() {
		executor := &CloudWatchExecutor{
			rgtaSvc: mockedRGTA{
				Resp: resourcegroupstaggingapi.GetResourcesOutput{
					ResourceTagMappingList: []*resourcegroupstaggingapi.ResourceTagMapping{
						{
							ResourceARN: aws.String("arn:aws:ec2:us-east-1:123456789012:instance/i-12345678901234567"),
							Tags: []*resourcegroupstaggingapi.Tag{
								{
									Key:   aws.String("Environment"),
									Value: aws.String("production"),
								},
							},
						},
						{
							ResourceARN: aws.String("arn:aws:ec2:us-east-1:123456789012:instance/i-76543210987654321"),
							Tags: []*resourcegroupstaggingapi.Tag{
								{
									Key:   aws.String("Environment"),
									Value: aws.String("production"),
								},
							},
						},
					},
				},
			},
		}

		json := simplejson.New()
		json.Set("region", "us-east-1")
		json.Set("resourceType", "ec2:instance")
		tags := make(map[string]interface{})
		tags["Environment"] = []string{"production"}
		json.Set("tags", tags)
		result, _ := executor.handleGetResourceArns(context.Background(), json, &tsdb.TsdbQuery{})

		Convey("Should return all two instances", func() {
			So(result[0].Text, ShouldEqual, "arn:aws:ec2:us-east-1:123456789012:instance/i-12345678901234567")
			So(result[0].Value, ShouldEqual, "arn:aws:ec2:us-east-1:123456789012:instance/i-12345678901234567")
			So(result[1].Text, ShouldEqual, "arn:aws:ec2:us-east-1:123456789012:instance/i-76543210987654321")
			So(result[1].Value, ShouldEqual, "arn:aws:ec2:us-east-1:123456789012:instance/i-76543210987654321")

		})
	})
}

func TestParseMultiSelectValue(t *testing.T) {
	values := parseMultiSelectValue(" i-someInstance ")
	assert.Equal(t, []string{"i-someInstance"}, values)

	values = parseMultiSelectValue("{i-05}")
	assert.Equal(t, []string{"i-05"}, values)

	values = parseMultiSelectValue(" {i-01, i-03, i-04} ")
	assert.Equal(t, []string{"i-01", "i-03", "i-04"}, values)

	values = parseMultiSelectValue("i-{01}")
	assert.Equal(t, []string{"i-{01}"}, values)

}
