package middleware

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

	"github.com/gin-gonic/gin"
	"github.com/redis/go-redis/v9"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// requireRedis vérifie que Redis est disponible et skip le test sinon
// ÉTAPE 1.3: Skip conditionnel pour les tests dépendant de Redis
func requireRedis(t *testing.T, client *redis.Client) {
	t.Helper()
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()

	if _, err := client.Ping(ctx).Result(); err != nil {
		t.Skipf("Redis not available (connection refused); skipping rate limit tests: %v", err)
	}
}

func setupTestRedis() (*redis.Client, func()) {
	// Utiliser un client Redis de test (en mémoire via Miniredis ou un conteneur)
	// Pour simplifier, on va utiliser un client Redis réel ou mock
	// Dans un vrai test, on utiliserait un conteneur Docker ou Miniredis

	client := redis.NewClient(&redis.Options{
		Addr: "localhost:6379",
		DB:   15, // Utiliser une DB de test
	})

	// Nettoyer la DB de test (si Redis est disponible)
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	if err := client.FlushDB(ctx).Err(); err == nil {
		// Redis est disponible, on peut nettoyer
	}

	cleanup := func() {
		ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
		defer cancel()
		client.FlushDB(ctx)
		client.Close()
	}

	return client, cleanup
}

func TestUploadRateLimit_Allowed(t *testing.T) {
	redisClient, cleanup := setupTestRedis()
	defer cleanup()
	requireRedis(t, redisClient) // ÉTAPE 1.3: Skip si Redis indisponible

	// Mettre en place Gin en mode test
	gin.SetMode(gin.TestMode)
	router := gin.New()

	// Middleware de rate limiting
	router.Use(func(c *gin.Context) {
		c.Set("user_id", int64(123))
	})
	router.Use(UploadRateLimit(redisClient))

	// Route de test
	router.POST("/upload", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "upload successful"})
	})

	// Première requête - devrait être autorisée
	req, _ := http.NewRequest("POST", "/upload", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "10", w.Header().Get("X-RateLimit-Limit"))
	assert.Equal(t, "9", w.Header().Get("X-RateLimit-Remaining"))
}

func TestUploadRateLimit_Exceeded(t *testing.T) {
	redisClient, cleanup := setupTestRedis()
	defer cleanup()
	requireRedis(t, redisClient) // ÉTAPE 1.3: Skip si Redis indisponible

	// Mettre en place Gin en mode test
	gin.SetMode(gin.TestMode)
	router := gin.New()

	// Middleware de rate limiting
	router.Use(func(c *gin.Context) {
		c.Set("user_id", int64(123))
	})
	router.Use(UploadRateLimit(redisClient))

	// Route de test
	router.POST("/upload", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "upload successful"})
	})

	// Effectuer 11 requêtes (limite est 10)
	for i := 0; i < 10; i++ {
		req, _ := http.NewRequest("POST", "/upload", nil)
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)
		assert.Equal(t, http.StatusOK, w.Code, "Request %d should be allowed", i+1)
	}

	// La 11ème requête devrait être bloquée
	req, _ := http.NewRequest("POST", "/upload", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusTooManyRequests, w.Code)
	assert.Equal(t, "10", w.Header().Get("X-RateLimit-Limit"))
	assert.Equal(t, "0", w.Header().Get("X-RateLimit-Remaining"))
}

func TestUploadRateLimit_NoUserID(t *testing.T) {
	redisClient, cleanup := setupTestRedis()
	defer cleanup()
	requireRedis(t, redisClient) // ÉTAPE 1.3: Skip si Redis indisponible

	// Mettre en place Gin en mode test
	gin.SetMode(gin.TestMode)
	router := gin.New()

	// Pas de user_id dans le contexte
	router.Use(UploadRateLimit(redisClient))

	// Route de test
	router.POST("/upload", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "upload successful"})
	})

	// Requête sans user_id - devrait passer sans rate limiting
	req, _ := http.NewRequest("POST", "/upload", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	// Pas de headers de rate limit car pas d'utilisateur authentifié
}

func TestUploadRateLimit_RedisError(t *testing.T) {
	// Créer un client Redis invalide pour simuler une erreur
	invalidClient := redis.NewClient(&redis.Options{
		Addr: "localhost:9999", // Port invalide
	})

	// Mettre en place Gin en mode test
	gin.SetMode(gin.TestMode)
	router := gin.New()

	// Middleware de rate limiting avec Redis invalide
	router.Use(func(c *gin.Context) {
		c.Set("user_id", int64(123))
	})
	router.Use(UploadRateLimit(invalidClient))

	// Route de test
	router.POST("/upload", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "upload successful"})
	})

	// Requête - devrait passer en cas d'erreur Redis (fail-open)
	req, _ := http.NewRequest("POST", "/upload", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Devrait être autorisé en cas d'erreur Redis (fail-open)
	assert.Equal(t, http.StatusOK, w.Code)

	invalidClient.Close()
}

func TestUploadRateLimit_Headers(t *testing.T) {
	redisClient, cleanup := setupTestRedis()
	defer cleanup()
	requireRedis(t, redisClient) // ÉTAPE 1.3: Skip si Redis indisponible

	// Mettre en place Gin en mode test
	gin.SetMode(gin.TestMode)
	router := gin.New()

	// Middleware de rate limiting
	router.Use(func(c *gin.Context) {
		c.Set("user_id", int64(123))
	})
	router.Use(UploadRateLimit(redisClient))

	// Route de test
	router.POST("/upload", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"message": "upload successful"})
	})

	req, _ := http.NewRequest("POST", "/upload", nil)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Vérifier les headers
	assert.Equal(t, "10", w.Header().Get("X-RateLimit-Limit"))
	assert.NotEmpty(t, w.Header().Get("X-RateLimit-Remaining"))
	assert.NotEmpty(t, w.Header().Get("X-RateLimit-Reset"))

	// Vérifier que le reset timestamp est dans le futur
	resetTime, err := time.Parse(time.RFC3339, w.Header().Get("X-RateLimit-Reset"))
	if err != nil {
		// Si ce n'est pas un timestamp RFC3339, essayer un timestamp Unix
		resetTimestamp := w.Header().Get("X-RateLimit-Reset")
		require.NotEmpty(t, resetTimestamp, "X-RateLimit-Reset header should be present")
	}
	if err == nil {
		assert.True(t, resetTime.After(time.Now()), "Reset time should be in the future")
	}
}
