// Package tracer bootstraps the OpenTelemetry SDK for a service and exports
// traces over OTLP/HTTP (default endpoint localhost:4318, overridable via the
// OTEL_EXPORTER_OTLP_ENDPOINT environment variable).
//
// Typical usage:
//
//	if err := tracer.Init("api-gateway"); err != nil { log.Fatal(err) }
//	defer tracer.Shutdown(context.Background())
package tracer

import (
	"context"
	"fmt"
	"os"
	"time"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
	"go.opentelemetry.io/otel/trace"
)

// tp is the process-wide TracerProvider created by Init and torn down by
// Shutdown.
var tp *sdktrace.TracerProvider

// Init initializes the OpenTelemetry SDK with an OTLP HTTP exporter and
// registers it as the global TracerProvider. The service is identified by
// serviceName on all exported spans.
func Init(serviceName string) error {
	endpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
	if endpoint == "" {
		endpoint = "localhost:4318"
	}

	exp, err := otlptracehttp.New(
		context.Background(),
		otlptracehttp.WithEndpoint(endpoint),
		otlptracehttp.WithInsecure(),
	)
	if err != nil {
		return fmt.Errorf("tracer: creating OTLP exporter: %w", err)
	}

	res, err := resource.Merge(
		resource.Default(),
		resource.NewWithAttributes(
			semconv.SchemaURL,
			semconv.ServiceName(serviceName),
		),
	)
	if err != nil {
		return fmt.Errorf("tracer: building resource: %w", err)
	}

	tp = sdktrace.NewTracerProvider(
		sdktrace.WithBatcher(exp),
		sdktrace.WithResource(res),
	)
	otel.SetTracerProvider(tp)
	otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
		propagation.TraceContext{},
		propagation.Baggage{},
	))
	return nil
}

// Shutdown flushes pending spans and releases resources held by the
// TracerProvider. It is a no-op when Init was never called. The provided
// context bounds the flush to 5 seconds.
func Shutdown(ctx context.Context) error {
	if tp == nil {
		return nil
	}

	ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()
	if err := tp.Shutdown(ctx); err != nil {
		return fmt.Errorf("tracer: shutting down provider: %w", err)
	}
	return nil
}

// FromContext returns the active span from ctx. When no span is active the
// returned span is a no-op, so callers may safely record attributes or events
// without nil checks.
func FromContext(ctx context.Context) trace.Span {
	return trace.SpanFromContext(ctx)
}
