package stat

import (
	"context"
	"sync"
	"time"

	"code.justin.tv/systems/sandstorm/closer"
	"code.justin.tv/systems/sandstorm/logging"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface"
)

const cloudwatchMaxBatchSize = 20

type runner struct {
	Closer     *closer.Closer
	CloudWatch cloudwatchiface.CloudWatchAPI
	Logger     logging.Logger

	// rate to flush to cloudwatch
	FlushRate time.Duration
	// cloudwatch namespace to push metrics out to
	Namespace string

	initSync sync.Once
	batcher  *batcher
}

func (r *runner) init() {
	r.initSync.Do(func() {
		r.batcher = &batcher{
			Logger: r.Logger,
		}

		go r.run()
	})
}

func (r *runner) run() {
	cont := true
	for cont {
		select {
		case <-r.Closer.Done():
			r.Logger.Debug("exiting cloudwatch runner - flushing one last time")
			cont = false
		case <-time.NewTimer(r.FlushRate).C:
		}

		r.Flush(context.Background())
	}
}

func (r *runner) Increment(metric Metric) {
	r.init()

	r.batcher.Increment(metric)
}

func (r *runner) Measure(metric Metric, value float64) {
	r.init()

	r.batcher.Measure(metric, value)
}

func (r *runner) Flush(ctx context.Context) {
	r.init()

	batch := &metricDatumBatch{r.batcher.Pop()}

	for batch.HasMore() {
		_, err := r.CloudWatch.PutMetricDataWithContext(ctx, &cloudwatch.PutMetricDataInput{
			MetricData: batch.Pop(cloudwatchMaxBatchSize),
			Namespace:  aws.String(r.Namespace),
		})

		if err != nil {
			r.Logger.Errorf("failed to flush stats: %s", err)
		}
	}
}
