package observability

import (
	"context"
	"fmt"
	"net/http"

	"github.com/traefik/traefik/v3/pkg/config/dynamic"
	"go.opentelemetry.io/otel/codes"
	"go.opentelemetry.io/otel/trace"
)

type contextKey int

const observabilityKey contextKey = iota

type Observability struct {
	AccessLogsEnabled      bool
	MetricsEnabled         bool
	SemConvMetricsEnabled  bool
	TracingEnabled         bool
	DetailedTracingEnabled bool
	Metadata               *dynamic.ObservabilityMetadata
}

// WithObservabilityHandler sets the observability state in the context for the next handler.
// This is also used for testing purposes to control whether access logs are enabled or not.
func WithObservabilityHandler(next http.Handler, obs Observability) http.Handler {
	return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
		next.ServeHTTP(rw, req.WithContext(WithObservability(req.Context(), obs)))
	})
}

// WithObservability injects the observability state into the context.
func WithObservability(ctx context.Context, obs Observability) context.Context {
	return context.WithValue(ctx, observabilityKey, obs)
}

// AccessLogsEnabled returns whether access-logs are enabled.
func AccessLogsEnabled(ctx context.Context) bool {
	obs, ok := ctx.Value(observabilityKey).(Observability)
	return ok && obs.AccessLogsEnabled
}

// MetricsEnabled returns whether metrics are enabled.
func MetricsEnabled(ctx context.Context) bool {
	obs, ok := ctx.Value(observabilityKey).(Observability)
	return ok && obs.MetricsEnabled
}

// SemConvMetricsEnabled returns whether metrics are enabled.
func SemConvMetricsEnabled(ctx context.Context) bool {
	obs, ok := ctx.Value(observabilityKey).(Observability)
	return ok && obs.SemConvMetricsEnabled
}

// TracingEnabled returns whether tracing is enabled.
func TracingEnabled(ctx context.Context) bool {
	obs, ok := ctx.Value(observabilityKey).(Observability)
	return ok && obs.TracingEnabled
}

// DetailedTracingEnabled returns whether detailed tracing is enabled.
func DetailedTracingEnabled(ctx context.Context) bool {
	obs, ok := ctx.Value(observabilityKey).(Observability)
	return ok && obs.DetailedTracingEnabled
}

// GetObservabilityMetadata returns the observability metadata.
func GetObservabilityMetadata(ctx context.Context) *dynamic.ObservabilityMetadata {
	obs, ok := ctx.Value(observabilityKey).(Observability)
	if ok {
		return obs.Metadata
	}
	return nil
}

// SetStatusErrorf flags the span as in error and log an event.
func SetStatusErrorf(ctx context.Context, format string, args ...any) {
	if span := trace.SpanFromContext(ctx); span != nil {
		span.SetStatus(codes.Error, fmt.Sprintf(format, args...))
	}
}

func Proto(proto string) string {
	switch proto {
	case "HTTP/1.0":
		return "1.0"
	case "HTTP/1.1":
		return "1.1"
	case "HTTP/2":
		return "2"
	case "HTTP/3":
		return "3"
	default:
		return proto
	}
}
