//go:build !integration
// +build !integration

package middleware_test

import (
	"net/http"
	"net/http/httptest"
	"runtime"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"

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

// TestTimeoutMiddleware_NoGoroutineLeak vérifie qu'il n'y a pas de fuite de goroutines après des timeouts
func TestTimeoutMiddleware_NoGoroutineLeak(t *testing.T) {
	gin.SetMode(gin.TestMode)

	// Capturer le nombre initial de goroutines
	initialGoroutines := runtime.NumGoroutine()

	// Créer un router avec timeout middleware
	router := gin.New()
	router.Use(middleware.Timeout(10 * time.Millisecond))

	// Handler qui dépasse le timeout
	router.GET("/slow", func(c *gin.Context) {
		// Simuler un travail qui dépasse le timeout
		time.Sleep(100 * time.Millisecond)
		// Vérifier que le contexte est annulé
		select {
		case <-c.Request.Context().Done():
			// Contexte annulé, ne pas écrire de réponse
			return
		case <-time.After(10 * time.Millisecond):
			// Si on arrive ici, le contexte n'a pas été annulé (ne devrait pas arriver)
			c.JSON(http.StatusOK, gin.H{"status": "should not reach here"})
		}
	})

	// Exécuter plusieurs requêtes qui vont timeout
	const numRequests = 10
	for i := 0; i < numRequests; i++ {
		w := httptest.NewRecorder()
		req := httptest.NewRequest("GET", "/slow", nil)
		router.ServeHTTP(w, req)

		// Vérifier que le timeout est respecté
		assert.Equal(t, http.StatusGatewayTimeout, w.Code)
	}

	// Attendre un peu pour que les goroutines se nettoient
	time.Sleep(200 * time.Millisecond)

	// Forcer un GC pour nettoyer les ressources
	runtime.GC()
	time.Sleep(50 * time.Millisecond)

	// Vérifier que le nombre de goroutines n'a pas augmenté de manière significative
	// On accepte une marge de 2 goroutines pour les goroutines système
	finalGoroutines := runtime.NumGoroutine()
	goroutineIncrease := finalGoroutines - initialGoroutines

	// Le nombre de goroutines ne devrait pas augmenter de plus de 2 (marge pour les goroutines système)
	assert.LessOrEqual(t, goroutineIncrease, 2,
		"Goroutine leak detected: initial=%d, final=%d, increase=%d",
		initialGoroutines, finalGoroutines, goroutineIncrease)
}

// TestTimeoutMiddleware_HandlerRespectsContext vérifie que le handler respecte l'annulation du contexte
func TestTimeoutMiddleware_HandlerRespectsContext(t *testing.T) {
	gin.SetMode(gin.TestMode)

	router := gin.New()
	router.Use(middleware.Timeout(10 * time.Millisecond))

	handlerExecuted := false

	router.GET("/test", func(c *gin.Context) {
		handlerExecuted = true
		// Attendre que le contexte soit annulé
		select {
		case <-c.Request.Context().Done():
			// Contexte annulé, ne pas écrire de réponse
			return
		case <-time.After(100 * time.Millisecond):
			// Timeout du test lui-même
		}
	})

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

	// Vérifier que le handler a été exécuté
	assert.True(t, handlerExecuted, "Handler should have been executed")

	// Vérifier que le contexte a été annulé
	// Note: Le handler peut ne pas avoir eu le temps de détecter l'annulation
	// mais le middleware doit avoir retourné 504
	assert.Equal(t, http.StatusGatewayTimeout, w.Code)

	// Attendre un peu pour que le handler détecte l'annulation
	time.Sleep(50 * time.Millisecond)
	// Le contexte devrait être annulé (mais le handler peut ne pas avoir eu le temps de le détecter)
	// L'important est que le middleware retourne 504 et que les ressources soient nettoyées
}

// TestTimeoutMiddleware_MultipleConcurrentRequests vérifie qu'il n'y a pas de fuite avec plusieurs requêtes concurrentes
func TestTimeoutMiddleware_MultipleConcurrentRequests(t *testing.T) {
	gin.SetMode(gin.TestMode)

	initialGoroutines := runtime.NumGoroutine()

	router := gin.New()
	router.Use(middleware.Timeout(10 * time.Millisecond))

	router.GET("/slow", func(c *gin.Context) {
		time.Sleep(100 * time.Millisecond)
		select {
		case <-c.Request.Context().Done():
			return
		default:
			c.JSON(http.StatusOK, gin.H{"status": "ok"})
		}
	})

	// Lancer plusieurs requêtes concurrentes
	const numConcurrent = 5
	done := make(chan bool, numConcurrent)

	for i := 0; i < numConcurrent; i++ {
		go func() {
			w := httptest.NewRecorder()
			req := httptest.NewRequest("GET", "/slow", nil)
			router.ServeHTTP(w, req)
			assert.Equal(t, http.StatusGatewayTimeout, w.Code)
			done <- true
		}()
	}

	// Attendre que toutes les requêtes soient terminées
	for i := 0; i < numConcurrent; i++ {
		<-done
	}

	// Attendre que les goroutines se nettoient
	time.Sleep(200 * time.Millisecond)
	runtime.GC()
	time.Sleep(50 * time.Millisecond)

	finalGoroutines := runtime.NumGoroutine()
	goroutineIncrease := finalGoroutines - initialGoroutines

	// Le nombre de goroutines ne devrait pas augmenter de plus de 2
	assert.LessOrEqual(t, goroutineIncrease, 2,
		"Goroutine leak detected with concurrent requests: initial=%d, final=%d, increase=%d",
		initialGoroutines, finalGoroutines, goroutineIncrease)
}

// TestTimeoutMiddleware_FastHandler_NoTimeout vérifie qu'un handler rapide ne déclenche pas de timeout
func TestTimeoutMiddleware_FastHandler_NoTimeout(t *testing.T) {
	gin.SetMode(gin.TestMode)

	router := gin.New()
	router.Use(middleware.Timeout(100 * time.Millisecond))

	router.GET("/fast", func(c *gin.Context) {
		time.Sleep(10 * time.Millisecond)
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})

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

	// Vérifier que le handler a réussi (pas de timeout)
	assert.Equal(t, http.StatusOK, w.Code)
}
