package tracer

import (
	"os"

	"github.com/go-logr/logr"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	"go.opentelemetry.io/otel/trace"

	v1 "github.com/tidbcloud/infra-api/api/v1"

	"github.com/tidbcloud/infra-api/controllers/utils"
	"github.com/tidbcloud/infra-api/pkg/consts"
)

// InitTracer initializes OpenTelemetry tracing settings.
// It will check the env variables `ENABLE_TRACING` and `LS_ACCESS_TOKEN`
// to determine if enable tracing and send traces to where.
func InitTracer(logger logr.Logger) {
	var tp *sdktrace.TracerProvider

	// Disable the tracing feature by default, the trace provider will not sample traces.
	tp = sdktrace.NewTracerProvider(
		sdktrace.WithSampler(sdktrace.NeverSample()),
	)

	if os.Getenv("ENABLE_TRACING") == "true" {
		// Enable tracing, check if lightstep's access token is given.
		token := os.Getenv("LS_ACCESS_TOKEN")

		if token != "" {
			if lsTraceProvider, err := initLightstepTracerProvider(token); err != nil {
				logger.Error(err, "failed to init Lightstep TracerProvider, will disable tracing")
			} else {
				// Set up lightstep launcher and traces will be sent to lightstep.
				logger.Info("Traces will be sent to Lightstep")
				tp = lsTraceProvider
			}
		} else {
			// Lightstep's access token is not specified,
			// traces will be sent to stdout in JSON format.
			if exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint()); err != nil {
				logger.Error(err, "failed to create OTEL stdout exporter")
			} else {
				logger.Info("Traces will be sent to stdout")
				tp = sdktrace.NewTracerProvider(
					sdktrace.WithBatcher(exporter),
				)
			}
		}
	}

	otel.SetTracerProvider(tp)
}

// EndSpan wraps the error handling and ends the spans to reduce repetition.
// It should be called in a defer function to get the final error,
// like `defer func() { EndSpan(span, err) }()`.
func EndSpan(span trace.Span, err error) {
	if utils.IsRealError(err) {
		span.RecordError(err)
		span.SetStatus(codes.Error, err.Error())
	}
	span.End()
}

// SetCommonAttributes sets some common business-related attributes for the given span,
// so that we can easily query all related objects with them.
func SetCommonAttributes(span trace.Span, c *v1.Cluster) {
	if c != nil {
		span.SetAttributes(
			attribute.String("project_id", c.Labels[consts.ProjectTagKey]),
			attribute.String("cluster_id", c.Labels[consts.ClusterTagKey]),
			attribute.String("tenant_id", c.Labels[consts.TenantTagKey]),
		)
	}
}
