package observ

import (
	"context"
	"fmt"

	"contrib.go.opencensus.io/exporter/jaeger"
	"contrib.go.opencensus.io/exporter/stackdriver"
	datadog "github.com/DataDog/opencensus-go-exporter-datadog"
	"github.com/gomods/athens/pkg/errors"
	"go.opencensus.io/trace"
)

// RegisterExporter determines the type of TraceExporter service for exporting traces from opencensus
// User can choose from multiple tracing services (datadog, jaegar)
// RegisterExporter returns the 'Flush' function for that particular tracing service.
func RegisterExporter(traceExporter, url, service, env string) (func(), error) {
	const op errors.Op = "observ.RegisterExporter"
	switch traceExporter {
	case "jaeger":
		return registerJaegerExporter(url, service, env)
	case "datadog":
		return registerDatadogExporter(url, service, env)
	case "stackdriver":
		return registerStackdriverExporter(url, env)
	case "":
		return nil, errors.E(op, "Exporter not specified. Traces won't be exported")
	default:
		return nil, errors.E(op, fmt.Sprintf("Exporter %s not supported. Please open PR or an issue at github.com/gomods/athens", traceExporter))
	}
}

// registerJaegerExporter creates a jaeger exporter for exporting traces to opencensus.
// Currently uses the 'TraceExporter' variable in the config file.
// It should in the future have a nice sampling rate defined.
func registerJaegerExporter(url, service, env string) (func(), error) {
	const op errors.Op = "observ.registerJaegarExporter"
	if url == "" {
		return nil, errors.E(op, "Exporter URL is empty. Traces won't be exported")
	}
	ex, err := jaeger.NewExporter(jaeger.Options{
		Endpoint: url,
		Process: jaeger.Process{
			ServiceName: service,
			Tags: []jaeger.Tag{
				// IP Tag ensures Jaeger's clock isn't skewed.
				// If/when we have traces across different servers,
				// we should make this IP dynamic.
				jaeger.StringTag("ip", "127.0.0.1"),
			},
		},
	})
	if err != nil {
		return nil, errors.E(op, err)
	}
	traceRegisterExporter(ex, env)
	return ex.Flush, nil
}

func traceRegisterExporter(exporter trace.Exporter, env string) {
	trace.RegisterExporter(exporter)
	if env == "development" {
		trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
	}
}

// registerDatadogTracerExporter creates a datadog exporter.
// Currently uses the 'TraceExporter' variable in the config file.
func registerDatadogExporter(url, service, env string) (func(), error) {
	ex := datadog.NewExporter(
		datadog.Options{
			TraceAddr: url,
			Service:   service,
		})
	traceRegisterExporter(ex, env)
	return ex.Stop, nil
}

func registerStackdriverExporter(projectID, env string) (func(), error) {
	const op errors.Op = "observ.registerStackdriverExporter"
	ex, err := stackdriver.NewExporter(stackdriver.Options{ProjectID: projectID})
	if err != nil {
		return nil, errors.E(op, err)
	}
	traceRegisterExporter(ex, env)
	return ex.Flush, nil
}

// StartSpan takes in a Context Interface and opName and starts a span. It returns the new attached ObserverContext
// and span.
func StartSpan(ctx context.Context, op string) (context.Context, *trace.Span) {
	return trace.StartSpan(ctx, op)
}
