package metrics

import (
	"os"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws/endpoints"

	identifier "code.justin.tv/amzn/TwitchProcessIdentifier"
	telemetry "code.justin.tv/amzn/TwitchTelemetry"
	cloudWatchMetricsSender "code.justin.tv/amzn/TwitchTelemetryCloudWatchMetricsSender"
	"code.justin.tv/creator-collab/log"
	"code.justin.tv/creator-collab/log/errors"
	"code.justin.tv/live/autohost/internal/logging"
	"github.com/aws/aws-sdk-go/aws/ec2metadata"
	"github.com/aws/aws-sdk-go/aws/session"

	"github.com/aws/aws-sdk-go/aws"
	uuid "github.com/satori/go.uuid"
)

type SampleReporterConfig struct {
	Environment string
	Logger      log.Logger
	Region      string
	// Whether the service is running on an EC2 instance, or on a developer's laptop.
	RunningInEC2 bool
	// If true, metrics are send to CloudWatch; otherwise metrics are dropped.
	SendMetricsToCloudwatch bool
	ServiceName             string
	// Optional: A build identifier for the service, like the Git SHA.
	Version string

	// Optional - use this sender instead of the default.
	OverrideSender telemetry.SampleObserver
}

// NewSampleReporter creates a SampleReporter.
// A SampleReporter allows our business logic to report their metrics. It passes these metrics to
// the rest of the Twitch telemetry stack that will send the metrics to CloudWatch.
func NewSampleReporter(conf *SampleReporterConfig) *telemetry.SampleReporter {
	twitchLogger := &logging.TwitchLogger{
		Logger: conf.Logger,
	}

	processID := getProcessID(conf)
	sampleBuilder := telemetry.SampleBuilder{
		ProcessIdentifier: *processID,
	}

	sampleObserver := conf.OverrideSender
	if sampleObserver == nil {
		var sender telemetry.SampleUnbufferedObserver
		if conf.SendMetricsToCloudwatch {
			sender = cloudWatchMetricsSender.NewUnbuffered(processID, twitchLogger)
		} else {
			sender = NewNoopSampleUnbufferedObserver()
		}

		sampleObserver = telemetry.NewBufferedAggregator(
			30*time.Second, 100000, time.Minute, sender, twitchLogger)
	}

	return &telemetry.SampleReporter{
		SampleBuilder:  sampleBuilder,
		SampleObserver: sampleObserver,
		Logger:         twitchLogger,
	}
}

func NewNoopSampleReporter() *telemetry.SampleReporter {
	return NewSampleReporter(&SampleReporterConfig{
		Environment:             "development",
		Logger:                  log.NewDevelopmentLogger(),
		Region:                  "us-west-2",
		RunningInEC2:            false,
		SendMetricsToCloudwatch: false,
		ServiceName:             "Autohost",
	})
}

func getProcessID(conf *SampleReporterConfig) *identifier.ProcessIdentifier {
	machine := getMachine(conf)
	stage := strings.ToLower(conf.Environment)

	// substage is always primary because we currently don't have a canary environment.
	substage := "primary"

	return &identifier.ProcessIdentifier{
		Service:  conf.ServiceName,
		Region:   conf.Region,
		Stage:    stage,
		Substage: substage,
		Version:  conf.Version,
		Machine:  machine,
		LaunchID: uuid.NewV4().String(),
	}
}

// getMachine returns the identity of the thing running the service.
// Autohost is run on EC2 instances and on our laptops.
// When it is running on EC2, the machine identifier should be the instance ID.
// When it is running on our laptops, the machine identifier should be our machine's FQDN.
// https://docs.google.com/document/d/1Oc0-Ut6mt1EKw46Iha2Tupe2rHJo3DsoJ7kw9dEvOUE/edit#heading=h.wq5ojao4b5wa
func getMachine(conf *SampleReporterConfig) string {
	if conf.RunningInEC2 {
		awsSession, err := session.NewSession(&aws.Config{
			Region: aws.String(conf.Region),
			// Use the regional STS endpoint when assuming roles instead of the global endpoint.
			// This reduces latency, and prevents our AWS account from getting flagged with an
			// STSGlobalEndpointDeprecation policy engine violation.
			STSRegionalEndpoint: endpoints.RegionalSTSEndpoint,
		})
		if err != nil {
			conf.Logger.Error(errors.Wrap(err, "creating AWS session failed"))
			return ""
		}

		metadataClient := ec2metadata.New(awsSession)
		instanceID, err := metadataClient.GetMetadata("instance-id")
		if err != nil {
			conf.Logger.Error(errors.Wrap(err, "fetching EC2 metadata failed"))
			return ""
		}

		return instanceID
	}

	hostName, err := os.Hostname()
	if err != nil {
		conf.Logger.Error(errors.Wrap(err, "loading host name failed"))
		return ""
	}

	return hostName
}
