package tracing

import (
	"context"
	"net/http"
	"strings"
	"sync"

	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
	"go.opentelemetry.io/contrib/propagators/b3"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/trace"
)

const TraceIDHeader = "X-Trace-ID"

var instrumentDefaultHTTPTransportOnce sync.Once

// NewPropagator accepts the legacy headers used by Clinic and NGM, while
// preferring the W3C traceparent emitted by Kong when multiple formats exist.
func NewPropagator() propagation.TextMapPropagator {
	return propagation.NewCompositeTextMapPropagator(
		traceIDPropagator{},
		b3.New(),
		propagation.TraceContext{},
		propagation.Baggage{},
	)
}

// ConfigurePropagation installs the shared propagation contract. It must run
// even when trace exporting is disabled.
func ConfigurePropagation() {
	otel.SetTextMapPropagator(NewPropagator())
}

// InstrumentDefaultHTTPTransport propagates traces through clients and reverse
// proxies that use http.DefaultTransport.
func InstrumentDefaultHTTPTransport() {
	instrumentDefaultHTTPTransportOnce.Do(func() {
		http.DefaultTransport = otelhttp.NewTransport(http.DefaultTransport)
	})
}

type traceIDPropagator struct{}

func (traceIDPropagator) Inject(ctx context.Context, carrier propagation.TextMapCarrier) {
	spanContext := trace.SpanContextFromContext(ctx)
	if spanContext.HasTraceID() {
		carrier.Set(TraceIDHeader, spanContext.TraceID().String())
	}
}

func (traceIDPropagator) Extract(ctx context.Context, carrier propagation.TextMapCarrier) context.Context {
	traceID, err := trace.TraceIDFromHex(strings.TrimSpace(carrier.Get(TraceIDHeader)))
	if err != nil || !traceID.IsValid() {
		return ctx
	}

	spanContext := trace.NewSpanContext(trace.SpanContextConfig{
		TraceID:    traceID,
		SpanID:     trace.SpanID{1},
		TraceFlags: trace.FlagsSampled,
		Remote:     true,
	})
	return trace.ContextWithRemoteSpanContext(ctx, spanContext)
}

func (traceIDPropagator) Fields() []string {
	return []string{TraceIDHeader}
}
