package xray

import (
	"context"
	"crypto/rand"
	"fmt"
	"os"
	"time"

	"code.justin.tv/foundation/xray/internal/plugins"
)

// The max size of a segment as defined by AWS is 64kB
// http://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html
// This is to fit within the max UDP packet size of 65,507 bytes
const maxSegmentSize = 64000

func newTraceID() string {
	var r [12]byte
	rand.Read(r[:])
	return fmt.Sprintf("1-%08x-%02x", time.Now().Unix(), r)
}

func newSegmentID() string {
	var r [8]byte
	rand.Read(r[:])
	return fmt.Sprintf("%02x", r)
}

func newSegment(ctx context.Context, name string) (context.Context, *segment) {
	if len(name) > 200 {
		name = name[:200]
	}

	parent := getSegment(ctx)
	seg := &segment{parent: parent}
	seg.Lock()
	defer seg.Unlock()

	if parent == nil {
		seg.TraceID = newTraceID()
		seg.sampled = sample(seg)
		seg.addPlugin(plugins.InstancePluginMetadata)
		if svcVersion := config.ServiceVersion(); svcVersion != "" {
			seg.service().Version = svcVersion
		}
	} else {
		parent.Lock()
		parent.rawSubsegments = append(parent.rawSubsegments, seg)
		parent.openSegments++
		parent.Unlock()
	}

	seg.ID = newSegmentID()
	seg.Name = name
	seg.StartTime = float64(time.Now().UnixNano()) / float64(time.Second)
	seg.InProgress = true

	return context.WithValue(ctx, contextkey, seg), seg
}

func (seg *segment) close(err error) {
	seg.Lock()

	seg.EndTime = float64(time.Now().UnixNano()) / float64(time.Second)
	seg.InProgress = false

	if err != nil {
		seg.Fault = true
		seg.cause().WorkingDirectory, _ = os.Getwd()
		seg.cause().Exceptions = append(seg.cause().Exceptions, exceptionFromError(err))
	}

	seg.Unlock()
	seg.flush(false)
}

func (seg *segment) flush(decrement bool) {
	seg.Lock()
	if decrement {
		seg.openSegments--
	}
	shouldFlush := seg.openSegments == 0 && seg.EndTime > 0
	seg.Unlock()

	if shouldFlush {
		if seg.parent == nil {
			emit(seg)
		} else {
			seg.parent.flush(true)
		}
	}
}

func (seg *segment) addPlugin(metadata *plugins.PluginMetadata) {
	//Only called within a seg locked code block
	if metadata == nil {
		return
	}

	if metadata.IdentityDocument != nil {
		seg.aws().AccountID = metadata.IdentityDocument.AccountID
		seg.aws().ec2().InstanceID = metadata.IdentityDocument.InstanceID
		seg.aws().ec2().AvailabilityZone = metadata.IdentityDocument.AvailabilityZone
	}

	if metadata.ECSContainerName != "" {
		seg.aws().ecs().Container = metadata.ECSContainerName
	}

	if metadata.BeanstalkMetadata != nil {
		seg.aws().elasticBeanstalk().Environment = metadata.BeanstalkMetadata.Environment
		seg.aws().elasticBeanstalk().VersionLabel = metadata.BeanstalkMetadata.VersionLabel
		seg.aws().elasticBeanstalk().DeploymentID = metadata.BeanstalkMetadata.DeploymentID
	}
}

func (seg *segment) addAnnotation(key string, value interface{}) error {
	switch value.(type) {
	case bool, int, uint, float32, float64, string:
	default:
		return fmt.Errorf("Failed to add annotation key: %q value: %q to subsegment %q. Value must be of type string, number or boolean.", key, value, seg.Name)
	}

	seg.Lock()
	defer seg.Unlock()

	if seg.Annotations == nil {
		seg.Annotations = map[string]interface{}{}
	}
	seg.Annotations[key] = value
	return nil
}

func (seg *segment) addMetadata(key string, value interface{}) error {
	seg.Lock()
	defer seg.Unlock()

	if seg.Metadata == nil {
		seg.Metadata = map[string]map[string]interface{}{}
	}
	if seg.Metadata["default"] == nil {
		seg.Metadata["default"] = map[string]interface{}{}
	}
	seg.Metadata["default"][key] = value
	return nil
}

func (seg *segment) root() *segment {
	if seg.parent == nil {
		return seg
	}
	return seg.parent.root()
}
