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 TestRequestID_GeneratesUUID(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(RequestID())

	router.GET("/test", func(c *gin.Context) {
		requestID, exists := c.Get("request_id")
		require.True(t, exists, "request_id should be set in context")

		c.JSON(http.StatusOK, gin.H{"request_id": requestID})
	})

	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-Request-ID est présent
	headerValue := w.Header().Get("X-Request-ID")
	assert.NotEmpty(t, headerValue, "X-Request-ID header should be present")

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

	// Vérifier que le request ID est dans la réponse JSON
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	responseRequestID, ok := response["request_id"].(string)
	require.True(t, ok, "request_id should be a string in response")
	assert.Equal(t, headerValue, responseRequestID, "request_id in response should match header")
}

func TestRequestID_UsesExistingHeader(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(RequestID())

	customRequestID := "custom-request-id-12345"

	router.GET("/test", func(c *gin.Context) {
		requestID, exists := c.Get("request_id")
		require.True(t, exists, "request_id should be set in context")

		c.JSON(http.StatusOK, gin.H{"request_id": requestID})
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	req.Header.Set("X-Request-ID", customRequestID)
	router.ServeHTTP(w, req)

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

	// Vérifier que le header X-Request-ID utilise la valeur fournie
	headerValue := w.Header().Get("X-Request-ID")
	assert.Equal(t, customRequestID, headerValue, "X-Request-ID should use provided header value")

	// Vérifier que le request ID est dans la réponse JSON
	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	responseRequestID, ok := response["request_id"].(string)
	require.True(t, ok, "request_id should be a string in response")
	assert.Equal(t, customRequestID, responseRequestID, "request_id in response should match provided header")
}

func TestRequestID_UniquePerRequest(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(RequestID())

	router.GET("/test", func(c *gin.Context) {
		requestID, _ := c.Get("request_id")
		c.JSON(http.StatusOK, gin.H{"request_id": requestID})
	})

	// Faire plusieurs requêtes et vérifier qu'elles ont des IDs différents
	requestIDs := make(map[string]bool)

	for i := 0; i < 10; i++ {
		w := httptest.NewRecorder()
		req := httptest.NewRequest("GET", "/test", nil)
		router.ServeHTTP(w, req)

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

		requestID, ok := response["request_id"].(string)
		require.True(t, ok, "request_id should be a string")

		// Vérifier que chaque ID est unique
		assert.False(t, requestIDs[requestID], "Each request should have a unique ID")
		requestIDs[requestID] = true
	}
}

func TestRequestID_EmptyHeaderGeneratesNew(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(RequestID())

	router.GET("/test", func(c *gin.Context) {
		requestID, _ := c.Get("request_id")
		c.JSON(http.StatusOK, gin.H{"request_id": requestID})
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/test", nil)
	// Ne pas définir de header X-Request-ID
	router.ServeHTTP(w, req)

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

	headerValue := w.Header().Get("X-Request-ID")
	assert.NotEmpty(t, headerValue, "X-Request-ID should be generated even if not provided")

	// Vérifier que c'est un UUID valide
	_, err := uuid.Parse(headerValue)
	assert.NoError(t, err, "Generated X-Request-ID should be a valid UUID")
}

func TestRequestID_AvailableInLogger(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(RequestID())

	var capturedRequestID string

	router.GET("/test", func(c *gin.Context) {
		// Simuler l'utilisation dans un logger
		if requestID, exists := c.Get("request_id"); exists {
			capturedRequestID = requestID.(string)
		}
		c.JSON(http.StatusOK, gin.H{"ok": true})
	})

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

	assert.Equal(t, http.StatusOK, w.Code)
	assert.NotEmpty(t, capturedRequestID, "request_id should be available for logger")

	// Vérifier que le request ID capturé correspond au header
	headerValue := w.Header().Get("X-Request-ID")
	assert.Equal(t, headerValue, capturedRequestID, "captured request_id should match header")
}

func TestRequestID_MultipleRequests(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(RequestID())

	// Utiliser router.Any() pour accepter toutes les méthodes HTTP testées
	router.Any("/test", func(c *gin.Context) {
		requestID, _ := c.Get("request_id")
		c.JSON(http.StatusOK, gin.H{"request_id": requestID})
	})

	// Tester avec différentes méthodes HTTP
	methods := []string{"GET", "POST", "PUT", "DELETE"}

	for _, method := range methods {
		w := httptest.NewRecorder()
		req := httptest.NewRequest(method, "/test", nil)
		router.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code, "Request with method %s should return 200", method)
		headerValue := w.Header().Get("X-Request-ID")
		assert.NotEmpty(t, headerValue, "X-Request-ID should be present for %s requests", method)
	}
}
