package oc

import (
	"github.com/sirupsen/logrus"
	"go.opencensus.io/trace"
	"google.golang.org/grpc/codes"

	"github.com/Microsoft/hcsshim/internal/log"
	"github.com/Microsoft/hcsshim/internal/logfields"
)

const spanMessage = "Span"

var _errorCodeKey = logrus.ErrorKey + "Code"

// LogrusExporter is an OpenCensus `trace.Exporter` that exports
// `trace.SpanData` to logrus output.
type LogrusExporter struct{}

var _ trace.Exporter = &LogrusExporter{}

// ExportSpan exports `s` based on the the following rules:
//
// 1. All output will contain `s.Attributes`, `s.SpanKind`, `s.TraceID`,
// `s.SpanID`, and `s.ParentSpanID` for correlation
//
// 2. Any calls to .Annotate will not be supported.
//
// 3. The span itself will be written at `logrus.InfoLevel` unless
// `s.Status.Code != 0` in which case it will be written at `logrus.ErrorLevel`
// providing `s.Status.Message` as the error value.
func (le *LogrusExporter) ExportSpan(s *trace.SpanData) {
	if s.DroppedAnnotationCount > 0 {
		logrus.WithFields(logrus.Fields{
			"name":            s.Name,
			logfields.TraceID: s.TraceID.String(),
			logfields.SpanID:  s.SpanID.String(),
			"dropped":         s.DroppedAttributeCount,
			"maxAttributes":   len(s.Attributes),
		}).Warning("span had dropped attributes")
	}

	entry := log.L.Dup()
	// Combine all span annotations with span data (eg, trace ID, span ID, parent span ID,
	// error, status code)
	// (OC) Span attributes are guaranteed to be  strings, bools, or int64s, so we can
	// can skip overhead in entry.WithFields() and add them directly to entry.Data.
	// Preallocate ahead of time, since we should add, at most, 10 additional entries
	data := make(logrus.Fields, len(entry.Data)+len(s.Attributes)+10)

	// Default log entry may have prexisting/application-wide data
	for k, v := range entry.Data {
		data[k] = v
	}
	for k, v := range s.Attributes {
		data[k] = v
	}

	data[logfields.Name] = s.Name
	data[logfields.TraceID] = s.TraceID.String()
	data[logfields.SpanID] = s.SpanID.String()
	data[logfields.ParentSpanID] = s.ParentSpanID.String()
	data[logfields.StartTime] = s.StartTime
	data[logfields.EndTime] = s.EndTime
	data[logfields.Duration] = s.EndTime.Sub(s.StartTime)
	if sk := spanKindToString(s.SpanKind); sk != "" {
		data["spanKind"] = sk
	}

	level := logrus.InfoLevel
	if s.Status.Code != 0 {
		level = logrus.ErrorLevel

		// don't overwrite an existing "error" or "errorCode" attributes
		if _, ok := data[logrus.ErrorKey]; !ok {
			data[logrus.ErrorKey] = s.Status.Message
		}
		if _, ok := data[_errorCodeKey]; !ok {
			data[_errorCodeKey] = codes.Code(s.Status.Code).String()
		}
	}

	entry.Data = data
	entry.Time = s.StartTime
	entry.Log(level, spanMessage)
}
