package middleware

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestTracing_GeneratesTraceID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Tracing())
	router.GET("/test", func(c *gin.Context) {
		traceID, exists := c.Get(TraceIDKey)
		require.True(t, exists)
		c.JSON(200, gin.H{"trace_id": traceID})
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	// Vérifier que le header X-Trace-ID est présent dans la réponse
	traceIDHeader := w.Header().Get(TraceIDHeader)
	assert.NotEmpty(t, traceIDHeader, "X-Trace-ID header should be present")

	// Vérifier que c'est un UUID valide
	_, err := uuid.Parse(traceIDHeader)
	assert.NoError(t, err, "Trace ID should be a valid UUID")

	// Vérifier dans la réponse JSON
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.Equal(t, traceIDHeader, response["trace_id"])
}

func TestTracing_PropagatesTraceID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Tracing())
	router.GET("/test", func(c *gin.Context) {
		traceID := GetTraceID(c)
		c.JSON(200, gin.H{"trace_id": traceID})
	})

	// Générer un trace ID existant
	existingTraceID := uuid.New().String()

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	req.Header.Set(TraceIDHeader, existingTraceID)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	// Vérifier que le trace ID propagé est réutilisé
	traceIDHeader := w.Header().Get(TraceIDHeader)
	assert.Equal(t, existingTraceID, traceIDHeader, "Trace ID should be propagated")

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.Equal(t, existingTraceID, response["trace_id"])
}

func TestTracing_GeneratesSpanID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Tracing())
	router.GET("/test", func(c *gin.Context) {
		spanID := GetSpanID(c)
		c.JSON(200, gin.H{"span_id": spanID})
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	// Vérifier que le header X-Span-ID est présent
	spanIDHeader := w.Header().Get(SpanIDHeader)
	assert.NotEmpty(t, spanIDHeader, "X-Span-ID header should be present")

	// Vérifier que c'est un UUID valide
	_, err := uuid.Parse(spanIDHeader)
	assert.NoError(t, err, "Span ID should be a valid UUID")

	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.Equal(t, spanIDHeader, response["span_id"])
}

func TestTracing_PropagatesSpanID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Tracing())
	router.GET("/test", func(c *gin.Context) {
		spanID := GetSpanID(c)
		c.JSON(200, gin.H{"span_id": spanID})
	})

	// Générer un span ID existant
	existingSpanID := uuid.New().String()

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	req.Header.Set(SpanIDHeader, existingSpanID)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)

	// Vérifier que le span ID propagé est réutilisé
	spanIDHeader := w.Header().Get(SpanIDHeader)
	assert.Equal(t, existingSpanID, spanIDHeader, "Span ID should be propagated")

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.Equal(t, existingSpanID, response["span_id"])
}

func TestTracing_UniqueTraceIDs(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Tracing())
	router.GET("/test", func(c *gin.Context) {
		traceID := GetTraceID(c)
		c.JSON(200, gin.H{"trace_id": traceID})
	})

	// Générer plusieurs requêtes et vérifier que chaque trace ID est unique
	traceIDs := make(map[string]bool)
	for i := 0; i < 10; i++ {
		w := httptest.NewRecorder()
		req := httptest.NewRequest("GET", "/test", nil)
		router.ServeHTTP(w, req)

		traceIDHeader := w.Header().Get(TraceIDHeader)
		assert.False(t, traceIDs[traceIDHeader], "Trace ID should be unique")
		traceIDs[traceIDHeader] = true
	}

	assert.Equal(t, 10, len(traceIDs), "Should have 10 unique trace IDs")
}

func TestTracing_ContextKeys(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Tracing())
	router.GET("/test", func(c *gin.Context) {
		traceID, traceExists := c.Get(TraceIDKey)
		spanID, spanExists := c.Get(SpanIDKey)

		assert.True(t, traceExists, "Trace ID should be in context")
		assert.True(t, spanExists, "Span ID should be in context")
		assert.NotEmpty(t, traceID)
		assert.NotEmpty(t, spanID)

		c.JSON(200, gin.H{
			"trace_id": traceID,
			"span_id":  spanID,
		})
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestGetTraceID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Tracing())
	router.GET("/test", func(c *gin.Context) {
		traceID := GetTraceID(c)
		assert.NotEmpty(t, traceID)

		// Tester avec un contexte vide (devrait retourner chaîne vide)
		// Ne pas créer un Context vide car Request serait nil et causerait un panic
		// À la place, tester avec un contexte qui n'a pas de trace ID
		w2 := httptest.NewRecorder()
		emptyCtx, _ := gin.CreateTestContext(w2)
		emptyCtx.Request = httptest.NewRequest("GET", "/", nil)
		emptyTraceID := GetTraceID(emptyCtx)
		assert.Empty(t, emptyTraceID)

		c.JSON(200, gin.H{"trace_id": traceID})
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestGetSpanID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Tracing())
	router.GET("/test", func(c *gin.Context) {
		spanID := GetSpanID(c)
		assert.NotEmpty(t, spanID)

		// Tester avec un contexte vide (devrait retourner chaîne vide)
		// Ne pas créer un Context vide car Request serait nil et causerait un panic
		// À la place, tester avec un contexte qui n'a pas de span ID
		w2 := httptest.NewRecorder()
		emptyCtx, _ := gin.CreateTestContext(w2)
		emptyCtx.Request = httptest.NewRequest("GET", "/", nil)
		emptySpanID := GetSpanID(emptyCtx)
		assert.Empty(t, emptySpanID)

		c.JSON(200, gin.H{"span_id": spanID})
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
}

func TestTracing_W3CTraceContextCompatible(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Tracing())
	router.GET("/test", func(c *gin.Context) {
		traceID := GetTraceID(c)
		c.JSON(200, gin.H{"trace_id": traceID})
	})

	// Tester avec un trace ID W3C Trace Context format (16 hex digits)
	// Le format W3C permet traceparent: 00-{trace_id}-{span_id}-01
	// Ici on teste juste que notre UUID est compatible (peut être utilisé dans traceparent)
	w3cTraceID := "4bf92f3577b34da6a3ce929d0e0e4736" // 32 hex digits (128 bits, comme UUID)

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	req.Header.Set(TraceIDHeader, w3cTraceID)
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	traceIDHeader := w.Header().Get(TraceIDHeader)
	assert.Equal(t, w3cTraceID, traceIDHeader, "Should accept W3C-compatible trace ID")
}
