package tracing

import (
	"os"
	"strings"

	octrace "go.opencensus.io/trace"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/bridge/opencensus"
	"go.opentelemetry.io/otel/exporters/jaeger"
	"go.opentelemetry.io/otel/sdk/resource"
	tracesdk "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
	"go.uber.org/zap"

	logging "github.com/ipfs/go-log/v2"
)

var log = logging.Logger("tracing")

const (
	// environment variable names
	envCollectorEndpoint = "LOTUS_JAEGER_COLLECTOR_ENDPOINT"
	envAgentHost         = "LOTUS_JAEGER_AGENT_HOST"
	envAgentPort         = "LOTUS_JAEGER_AGENT_PORT"
	envJaegerUser        = "LOTUS_JAEGER_USERNAME"
	envJaegerCred        = "LOTUS_JAEGER_PASSWORD"
)

// When sending directly to the collector, agent options are ignored.
// The collector endpoint is an HTTP or HTTPs URL.
// The agent endpoint is a thrift/udp protocol and should be given
// as a string like "hostname:port". The agent can also be configured
// with separate host and port variables.
func jaegerOptsFromEnv() jaeger.EndpointOption {
	var e string
	var ok bool

	if e, ok = os.LookupEnv(envCollectorEndpoint); ok {
		options := []jaeger.CollectorEndpointOption{jaeger.WithEndpoint(e)}
		if u, ok := os.LookupEnv(envJaegerUser); ok {
			if p, ok := os.LookupEnv(envJaegerCred); ok {
				options = append(options, jaeger.WithUsername(u))
				options = append(options, jaeger.WithPassword(p))
			} else {
				log.Warn("jaeger username supplied with no password. authentication will not be used.")
			}
		}
		log.Infof("jaeger tracess will send to collector %s", e)
		return jaeger.WithCollectorEndpoint(options...)
	}

	if e, ok = os.LookupEnv(envAgentHost); ok {
		options := []jaeger.AgentEndpointOption{jaeger.WithAgentHost(e), jaeger.WithLogger(zap.NewStdLog(log.Desugar()))}
		var ep string
		if p, ok := os.LookupEnv(envAgentPort); ok {
			options = append(options, jaeger.WithAgentPort(p))
			ep = strings.Join([]string{e, p}, ":")
		} else {
			ep = strings.Join([]string{e, "6831"}, ":")
		}
		log.Infof("jaeger traces will be sent to agent %s", ep)
		return jaeger.WithAgentEndpoint(options...)
	}
	return nil
}

func SetupJaegerTracing(serviceName string) *tracesdk.TracerProvider {
	jaegerEndpoint := jaegerOptsFromEnv()
	if jaegerEndpoint == nil {
		return nil
	}
	je, err := jaeger.New(jaegerEndpoint)
	if err != nil {
		log.Errorw("failed to create the jaeger exporter", "error", err)
		return nil
	}
	tp := tracesdk.NewTracerProvider(
		// Always be sure to batch in production.
		tracesdk.WithBatcher(je),
		// Record information about this application in an Resource.
		tracesdk.WithResource(resource.NewWithAttributes(
			semconv.SchemaURL,
			semconv.ServiceNameKey.String(serviceName),
		)),
		tracesdk.WithSampler(tracesdk.AlwaysSample()),
	)
	otel.SetTracerProvider(tp)
	tracer := tp.Tracer(serviceName)
	octrace.DefaultTracer = opencensus.NewTracer(tracer)
	return tp
}