package middleware

import (
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"

	"veza-backend-api/internal/tracing"
)

const (
	// TraceIDHeader est le nom du header HTTP pour propager le trace ID (legacy)
	TraceIDHeader = "X-Trace-ID"
	// TraceIDKey est la clé utilisée pour stocker le trace ID dans le contexte Gin
	TraceIDKey = "trace_id"
	// SpanIDHeader est le nom du header HTTP pour propager le span ID (legacy)
	SpanIDHeader = "X-Span-ID"
	// SpanIDKey est la clé utilisée pour stocker le span ID dans le contexte Gin
	SpanIDKey = "span_id"
)

// Tracing middleware pour générer et propager trace ID (W3C Trace Context compatible)
// BE-SVC-018: Amélioré pour supporter le format W3C Trace Context standard
// Le trace ID permet de tracer une requête à travers plusieurs services
// Si un trace ID est déjà présent dans le header, il est réutilisé (propagation)
// Sinon, un nouveau trace ID UUID v4 est généré
func Tracing() gin.HandlerFunc {
	return func(c *gin.Context) {
		// Extraire le contexte de tracing depuis les headers HTTP
		traceCtx := tracing.ExtractTraceContext(c.Request)

		// Stocker dans le contexte Gin pour utilisation dans les handlers et logs
		c.Set(TraceIDKey, traceCtx.TraceID)
		c.Set(SpanIDKey, traceCtx.SpanID)

		// Stocker aussi dans le contexte Go pour propagation
		ctx := tracing.WithTraceContext(c.Request.Context(), traceCtx)
		c.Request = c.Request.WithContext(ctx)

		// Propager via les headers de réponse (format W3C + legacy pour compatibilité)
		c.Header(tracing.TraceParentHeader, traceCtx.ToW3CTraceParent())
		c.Header(TraceIDHeader, traceCtx.TraceID)
		c.Header(SpanIDHeader, traceCtx.SpanID)

		c.Next()
	}
}

// TracingWithLogger est une version améliorée qui log aussi le contexte de tracing
func TracingWithLogger(logger *zap.Logger) gin.HandlerFunc {
	return func(c *gin.Context) {
		// Extraire le contexte de tracing depuis les headers HTTP
		traceCtx := tracing.ExtractTraceContext(c.Request)

		// Stocker dans le contexte Gin
		c.Set(TraceIDKey, traceCtx.TraceID)
		c.Set(SpanIDKey, traceCtx.SpanID)

		// Stocker dans le contexte Go
		ctx := tracing.WithTraceContext(c.Request.Context(), traceCtx)
		c.Request = c.Request.WithContext(ctx)

		// Propager via les headers de réponse
		c.Header(tracing.TraceParentHeader, traceCtx.ToW3CTraceParent())
		c.Header(TraceIDHeader, traceCtx.TraceID)
		c.Header(SpanIDHeader, traceCtx.SpanID)

		// Logger le début de la requête avec le contexte de tracing
		if logger != nil {
			logger.Debug("Request started",
				zap.String("trace_id", traceCtx.TraceID),
				zap.String("span_id", traceCtx.SpanID),
				zap.String("method", c.Request.Method),
				zap.String("path", c.Request.URL.Path),
			)
		}

		c.Next()
	}
}

// GetTraceID retourne le trace ID du contexte, ou une chaîne vide si non défini
func GetTraceID(c *gin.Context) string {
	// Essayer d'abord depuis le contexte Go (nouveau système)
	if traceCtx := tracing.FromContext(c.Request.Context()); traceCtx != nil {
		return traceCtx.TraceID
	}
	// Fallback sur le contexte Gin (legacy)
	if traceID, exists := c.Get(TraceIDKey); exists {
		if id, ok := traceID.(string); ok {
			return id
		}
	}
	return ""
}

// GetSpanID retourne le span ID du contexte, ou une chaîne vide si non défini
func GetSpanID(c *gin.Context) string {
	// Essayer d'abord depuis le contexte Go (nouveau système)
	if traceCtx := tracing.FromContext(c.Request.Context()); traceCtx != nil {
		return traceCtx.SpanID
	}
	// Fallback sur le contexte Gin (legacy)
	if spanID, exists := c.Get(SpanIDKey); exists {
		if id, ok := spanID.(string); ok {
			return id
		}
	}
	return ""
}

// GetTraceContext retourne le contexte de tracing complet depuis le contexte Gin
func GetTraceContext(c *gin.Context) *tracing.TraceContext {
	// Essayer d'abord depuis le contexte Go
	if traceCtx := tracing.FromContext(c.Request.Context()); traceCtx != nil {
		return traceCtx
	}
	// Fallback: créer depuis les valeurs du contexte Gin
	traceID := GetTraceID(c)
	spanID := GetSpanID(c)
	if traceID == "" && spanID == "" {
		return nil
	}
	return &tracing.TraceContext{
		TraceID: traceID,
		SpanID:  spanID,
		Sampled: true,
	}
}

// NewChildSpan crée un nouveau span enfant depuis le contexte Gin
func NewChildSpan(c *gin.Context) *tracing.TraceContext {
	parentCtx := GetTraceContext(c)
	if parentCtx == nil {
		return tracing.NewTraceContext()
	}
	return parentCtx.NewChildSpan()
}
