// Package otelsetup initialises the OpenTelemetry SDK for the LibNovel backend.
//
// It reads two environment variables:
//
//	OTEL_EXPORTER_OTLP_ENDPOINT  — OTLP/HTTP endpoint; accepts either a full
//	                               URL ("https://otel.example.com") or a bare
//	                               host[:port] ("otel-collector:4318").
//	                               TLS is used when the value starts with "https://".
//	OTEL_SERVICE_NAME            — service name reported in traces (default: "backend")
//
// When OTEL_EXPORTER_OTLP_ENDPOINT is empty the function is a no-op: it
// returns a nil shutdown func and the default slog.Logger, so callers never
// need to branch on it.
//
// Usage in main.go:
//
//	shutdown, log, err := otelsetup.Init(ctx, version)
//	if err != nil { return err }
//	if shutdown != nil { defer shutdown() }
package otelsetup

import (
	"context"
	"fmt"
	"log/slog"
	"os"
	"strings"
	"time"

	"go.opentelemetry.io/contrib/bridges/otelslog"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	otellog "go.opentelemetry.io/otel/log/global"
	"go.opentelemetry.io/otel/sdk/log"
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)

// Init sets up TracerProvider and LoggerProvider that export via OTLP/HTTP.
//
// Returns:
//   - shutdown: flushes and stops both providers (nil when OTel is disabled).
//   - logger:   an slog.Logger bridged to OTel logs (falls back to default when disabled).
//   - err:      non-nil only on SDK initialisation failure.
func Init(ctx context.Context, version string) (shutdown func(), logger *slog.Logger, err error) {
	rawEndpoint := os.Getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
	if rawEndpoint == "" {
		return nil, slog.Default(), nil // OTel disabled — not an error
	}

	// WithEndpoint expects a host[:port] value — no scheme.
	// Support both "https://otel.example.com" and "otel-collector:4318".
	useTLS := strings.HasPrefix(rawEndpoint, "https://")
	endpoint := strings.TrimPrefix(rawEndpoint, "https://")
	endpoint = strings.TrimPrefix(endpoint, "http://")

	serviceName := os.Getenv("OTEL_SERVICE_NAME")
	if serviceName == "" {
		serviceName = "backend"
	}

	// ── Shared resource ───────────────────────────────────────────────────────
	res, err := resource.New(ctx,
		resource.WithAttributes(
			semconv.ServiceName(serviceName),
			semconv.ServiceVersion(version),
		),
	)
	if err != nil {
		return nil, slog.Default(), fmt.Errorf("otelsetup: create resource: %w", err)
	}

	// ── Trace provider ────────────────────────────────────────────────────────
	traceOpts := []otlptracehttp.Option{otlptracehttp.WithEndpoint(endpoint)}
	if !useTLS {
		traceOpts = append(traceOpts, otlptracehttp.WithInsecure())
	}
	traceExp, err := otlptracehttp.New(ctx, traceOpts...)
	if err != nil {
		return nil, slog.Default(), fmt.Errorf("otelsetup: create OTLP trace exporter: %w", err)
	}

	tp := sdktrace.NewTracerProvider(
		sdktrace.WithBatcher(traceExp),
		sdktrace.WithResource(res),
		sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.2))),
	)
	otel.SetTracerProvider(tp)

	// ── Log provider ──────────────────────────────────────────────────────────
	logOpts := []otlploghttp.Option{otlploghttp.WithEndpoint(endpoint)}
	if !useTLS {
		logOpts = append(logOpts, otlploghttp.WithInsecure())
	}
	logExp, err := otlploghttp.New(ctx, logOpts...)
	if err != nil {
		return nil, slog.Default(), fmt.Errorf("otelsetup: create OTLP log exporter: %w", err)
	}

	lp := log.NewLoggerProvider(
		log.WithProcessor(log.NewBatchProcessor(logExp)),
		log.WithResource(res),
	)
	otellog.SetLoggerProvider(lp)

	// Bridge slog → OTel logs. Structured fields and trace IDs are forwarded
	// automatically; Grafana can correlate log lines with Tempo traces.
	otelLogger := otelslog.NewLogger(serviceName)

	shutdown = func() {
		shutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		_ = tp.Shutdown(shutCtx)
		_ = lp.Shutdown(shutCtx)
	}

	return shutdown, otelLogger, nil
}
