package main

import (
	"context"
	"fmt"
	"strings"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface"
	"github.com/pkg/errors"
	"go.uber.org/multierr"
)

const (
	anomalyDetectionMetricName  = "NumberOfMessagesPublished"
	anomalyDetectionNamespace   = "AWS/SNS"
	anomalyDetectionDimension   = "TopicName"
	anomalyDetectionStatistic   = "Sum"
	anomalyDetectionTopicPrefix = "eventbus-production"
)

func main() {
	fmt.Println("Finding AWS SNS Topic missing anomaly detection")
	ctx := context.Background()
	sess := session.Must(session.NewSession(&aws.Config{Region: aws.String("us-west-2")}))
	c := cloudwatch.New(sess)
	runner := &anomalyDetectionRunner{
		client: c,
	}
	err := runner.Run(ctx)
	if err != nil {
		panic(err)
	}
}

type anomalyDetectionRunner struct {
	client cloudwatchiface.CloudWatchAPI
	topics []string
}

func (a *anomalyDetectionRunner) Run(ctx context.Context) error {
	var err error
	if err = a.loadTopics(ctx); err != nil {
		return errors.Wrap(err, "could not load topics to apply anomaly detection to")
	}
	err = a.putAnomalyDetectors(ctx)
	return errors.Wrap(err, "could not create anomaly detectors")
}

func (a *anomalyDetectionRunner) loadTopics(ctx context.Context) error {
	a.topics = make([]string, 0)
	input := &cloudwatch.ListMetricsInput{
		Namespace:  aws.String(anomalyDetectionNamespace),
		MetricName: aws.String(anomalyDetectionMetricName),
	}
	var listMetricsErr error
	err := a.client.ListMetricsPagesWithContext(ctx, input, func(res *cloudwatch.ListMetricsOutput, lastPage bool) bool {
		for _, metric := range res.Metrics {
			topicName, err := findDimensionValue(metric.Dimensions, anomalyDetectionDimension)
			if err != nil {
				listMetricsErr = multierr.Append(listMetricsErr, err)
				continue
			}
			if strings.HasPrefix(topicName, anomalyDetectionTopicPrefix) {
				a.topics = append(a.topics, topicName)
			}
		}
		return !lastPage
	})
	if err != nil {
		return err
	}
	return listMetricsErr
}

func (a *anomalyDetectionRunner) putAnomalyDetectors(ctx context.Context) error {
	var result error
	for _, topicName := range a.topics {
		fmt.Printf("Creating anomaly detection for EventStream: %s\n", topicName)
		_, err := a.client.PutAnomalyDetectorWithContext(ctx, &cloudwatch.PutAnomalyDetectorInput{
			Namespace:  aws.String(anomalyDetectionNamespace),
			MetricName: aws.String(anomalyDetectionMetricName),
			Stat:       aws.String(anomalyDetectionStatistic),
			Dimensions: []*cloudwatch.Dimension{
				&cloudwatch.Dimension{
					Name:  aws.String(anomalyDetectionDimension),
					Value: aws.String(topicName),
				},
			},
		})
		if err != nil {
			result = multierr.Append(result, err)
		}
	}
	return result
}

func findDimensionValue(dimensions []*cloudwatch.Dimension, name string) (string, error) {
	for _, d := range dimensions {
		if aws.StringValue(d.Name) == name {
			return aws.StringValue(d.Value), nil
		}
	}
	return "", fmt.Errorf("dimension '%s' not found", name)
}
