//go:build integration
// +build integration

package middleware

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

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

// setupRateLimitingIntegrationTestRouter crée un router de test avec rate limiting
func setupRateLimitingIntegrationTestRouter(t *testing.T) (*gin.Engine, func()) {
	gin.SetMode(gin.TestMode)

	// Setup SimpleRateLimiter with low limits for testing
	// Note: We use SimpleRateLimiter for integration tests as it doesn't require Redis
	simpleLimiter := NewSimpleRateLimiter(5, 1*time.Minute) // 5 requests per minute

	// Setup EndpointLimiter (without Redis for tests)
	endpointLimiterConfig := &EndpointLimiterConfig{
		RedisClient: nil, // No Redis for integration tests
		KeyPrefix:   "test:endpoint_limit",
	}
	endpointLimits := &EndpointLimits{
		LoginAttempts:    3, // 3 login attempts per window
		LoginWindow:      1 * time.Minute,
		RegisterAttempts: 2, // 2 register attempts per window
		RegisterWindow:   1 * time.Minute,
	}
	endpointLimiter := NewEndpointLimiter(endpointLimiterConfig, endpointLimits)

	// Create router
	router := gin.New()

	// Apply global rate limiting
	router.Use(simpleLimiter.Middleware())

	// Mock authentication middleware - set user_id from header
	router.Use(func(c *gin.Context) {
		userIDStr := c.GetHeader("X-User-ID")
		if userIDStr != "" {
			uid, err := uuid.Parse(userIDStr)
			if err == nil {
				c.Set("user_id", uid)
			}
		}
		c.Next()
	})

	// Setup test endpoints
	v1 := router.Group("/api/v1")
	{
		// Public endpoints with endpoint-specific rate limiting
		auth := v1.Group("/auth")
		{
			auth.POST("/login", endpointLimiter.LoginRateLimit(), func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{"message": "login successful"})
			})
			auth.POST("/register", endpointLimiter.RegisterRateLimit(), func(c *gin.Context) {
				c.JSON(http.StatusCreated, gin.H{"message": "registration successful"})
			})
		}

		// Protected endpoints (require authentication)
		protected := v1.Group("/protected")
		protected.Use(func(c *gin.Context) {
			if _, exists := c.Get("user_id"); !exists {
				c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
				c.Abort()
				return
			}
			c.Next()
		})
		{
			protected.GET("/test", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{"message": "success"})
			})
		}
	}

	cleanup := func() {
		// No specific cleanup needed
	}

	return router, cleanup
}

// TestRateLimitingIntegration_GlobalRateLimit teste le rate limiting global
func TestRateLimitingIntegration_GlobalRateLimit(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, cleanup := setupRateLimitingIntegrationTestRouter(t)
	defer cleanup()

	// Make requests within limit (5 requests)
	for i := 0; i < 5; i++ {
		req := httptest.NewRequest("GET", "/api/v1/protected/test", nil)
		req.Header.Set("X-User-ID", uuid.New().String())
		req.RemoteAddr = "127.0.0.1:12345"
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code, "Request %d should succeed", i+1)
		assert.Equal(t, "5", w.Header().Get("X-RateLimit-Limit"))
		remaining := w.Header().Get("X-RateLimit-Remaining")
		assert.NotEmpty(t, remaining)
	}

	// 6th request should be rate limited
	req := httptest.NewRequest("GET", "/api/v1/protected/test", nil)
	req.Header.Set("X-User-ID", uuid.New().String())
	req.RemoteAddr = "127.0.0.1:12345"
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

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

// TestRateLimitingIntegration_EndpointSpecificRateLimit teste le rate limiting spécifique par endpoint
func TestRateLimitingIntegration_EndpointSpecificRateLimit(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, cleanup := setupRateLimitingIntegrationTestRouter(t)
	defer cleanup()

	// Make login requests within limit (3 requests)
	loginReq := map[string]interface{}{
		"email":    "test@example.com",
		"password": "password123",
	}
	loginBody, _ := json.Marshal(loginReq)

	for i := 0; i < 3; i++ {
		req := httptest.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer(loginBody))
		req.Header.Set("Content-Type", "application/json")
		req.RemoteAddr = "127.0.0.1:12345"
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code, "Login request %d should succeed", i+1)
	}

	// 4th login request should be rate limited
	req := httptest.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer(loginBody))
	req.Header.Set("Content-Type", "application/json")
	req.RemoteAddr = "127.0.0.1:12345"
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.Contains(t, response, "error")
	assert.Contains(t, response["error"].(string), "Too many login attempts")
}

// TestRateLimitingIntegration_RegisterRateLimit teste le rate limiting pour l'inscription
func TestRateLimitingIntegration_RegisterRateLimit(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, cleanup := setupRateLimitingIntegrationTestRouter(t)
	defer cleanup()

	// Make register requests within limit (2 requests)
	registerReq := map[string]interface{}{
		"email":            "test@example.com",
		"username":         "testuser",
		"password":         "SecurePassword123!",
		"password_confirm": "SecurePassword123!",
	}
	registerBody, _ := json.Marshal(registerReq)

	for i := 0; i < 2; i++ {
		req := httptest.NewRequest("POST", "/api/v1/auth/register", bytes.NewBuffer(registerBody))
		req.Header.Set("Content-Type", "application/json")
		req.RemoteAddr = "127.0.0.1:12345"
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		assert.Equal(t, http.StatusCreated, w.Code, "Register request %d should succeed", i+1)
	}

	// 3rd register request should be rate limited
	req := httptest.NewRequest("POST", "/api/v1/auth/register", bytes.NewBuffer(registerBody))
	req.Header.Set("Content-Type", "application/json")
	req.RemoteAddr = "127.0.0.1:12345"
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.Contains(t, response, "error")
	assert.Contains(t, response["error"].(string), "Too many registration attempts")
}

// TestRateLimitingIntegration_DifferentIPs teste que différentes IPs ont des limites séparées
func TestRateLimitingIntegration_DifferentIPs(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, cleanup := setupRateLimitingIntegrationTestRouter(t)
	defer cleanup()

	// IP 1: Make 5 requests (limit reached)
	for i := 0; i < 5; i++ {
		req := httptest.NewRequest("GET", "/api/v1/protected/test", nil)
		req.Header.Set("X-User-ID", uuid.New().String())
		req.RemoteAddr = "127.0.0.1:12345"
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)
		assert.Equal(t, http.StatusOK, w.Code)
	}

	// IP 1: 6th request should be rate limited
	req1 := httptest.NewRequest("GET", "/api/v1/protected/test", nil)
	req1.Header.Set("X-User-ID", uuid.New().String())
	req1.RemoteAddr = "127.0.0.1:12345"
	w1 := httptest.NewRecorder()
	router.ServeHTTP(w1, req1)
	assert.Equal(t, http.StatusTooManyRequests, w1.Code)

	// IP 2: Should be able to make 5 requests (different IP)
	for i := 0; i < 5; i++ {
		req := httptest.NewRequest("GET", "/api/v1/protected/test", nil)
		req.Header.Set("X-User-ID", uuid.New().String())
		req.RemoteAddr = "192.168.1.1:12345"
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)
		assert.Equal(t, http.StatusOK, w.Code, "IP 2 request %d should succeed", i+1)
	}
}

// TestRateLimitingIntegration_RateLimitHeaders teste que les headers de rate limiting sont présents
func TestRateLimitingIntegration_RateLimitHeaders(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, cleanup := setupRateLimitingIntegrationTestRouter(t)
	defer cleanup()

	req := httptest.NewRequest("GET", "/api/v1/protected/test", nil)
	req.Header.Set("X-User-ID", uuid.New().String())
	req.RemoteAddr = "127.0.0.1:12345"
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	// Check rate limit headers
	assert.Equal(t, "5", w.Header().Get("X-RateLimit-Limit"))
	assert.NotEmpty(t, w.Header().Get("X-RateLimit-Remaining"))
	assert.NotEmpty(t, w.Header().Get("X-RateLimit-Reset"))
}

// TestRateLimitingIntegration_EndpointSpecificHeaders teste les headers spécifiques par endpoint
func TestRateLimitingIntegration_EndpointSpecificHeaders(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, cleanup := setupRateLimitingIntegrationTestRouter(t)
	defer cleanup()

	loginReq := map[string]interface{}{
		"email":    "test@example.com",
		"password": "password123",
	}
	loginBody, _ := json.Marshal(loginReq)

	req := httptest.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer(loginBody))
	req.Header.Set("Content-Type", "application/json")
	req.RemoteAddr = "127.0.0.1:12345"
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

	// Check endpoint-specific rate limit headers
	assert.Equal(t, "3", w.Header().Get("X-LoginLimit-Limit"))
	assert.NotEmpty(t, w.Header().Get("X-LoginLimit-Remaining"))
	assert.NotEmpty(t, w.Header().Get("X-LoginLimit-Reset"))
}

// TestRateLimitingIntegration_UnauthenticatedRateLimit teste le rate limiting pour les utilisateurs non authentifiés
func TestRateLimitingIntegration_UnauthenticatedRateLimit(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, cleanup := setupRateLimitingIntegrationTestRouter(t)
	defer cleanup()

	// Make requests without authentication (should still be rate limited by IP)
	for i := 0; i < 5; i++ {
		req := httptest.NewRequest("GET", "/api/v1/protected/test", nil)
		// No X-User-ID header
		req.RemoteAddr = "127.0.0.1:12345"
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		// Should return 401 (unauthorized) but not 429 (rate limited) yet
		assert.Equal(t, http.StatusUnauthorized, w.Code, "Request %d should be unauthorized", i+1)
	}

	// 6th request should still be rate limited even if unauthorized
	req := httptest.NewRequest("GET", "/api/v1/protected/test", nil)
	req.RemoteAddr = "127.0.0.1:12345"
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// The rate limiter runs before auth check, so it should return 429
	assert.Equal(t, http.StatusTooManyRequests, w.Code)
}

// TestRateLimitingIntegration_MultipleEndpoints teste le rate limiting sur plusieurs endpoints
func TestRateLimitingIntegration_MultipleEndpoints(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, cleanup := setupRateLimitingIntegrationTestRouter(t)
	defer cleanup()

	// Exhaust login rate limit
	loginReq := map[string]interface{}{
		"email":    "test@example.com",
		"password": "password123",
	}
	loginBody, _ := json.Marshal(loginReq)

	for i := 0; i < 3; i++ {
		req := httptest.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer(loginBody))
		req.Header.Set("Content-Type", "application/json")
		req.RemoteAddr = "127.0.0.1:12345"
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)
		assert.Equal(t, http.StatusOK, w.Code)
	}

	// Login should now be rate limited
	req := httptest.NewRequest("POST", "/api/v1/auth/login", bytes.NewBuffer(loginBody))
	req.Header.Set("Content-Type", "application/json")
	req.RemoteAddr = "127.0.0.1:12345"
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)
	assert.Equal(t, http.StatusTooManyRequests, w.Code)

	// But register should still work (different endpoint, different limit)
	registerReq := map[string]interface{}{
		"email":            "test2@example.com",
		"username":         "testuser2",
		"password":         "SecurePassword123!",
		"password_confirm": "SecurePassword123!",
	}
	registerBody, _ := json.Marshal(registerReq)

	req2 := httptest.NewRequest("POST", "/api/v1/auth/register", bytes.NewBuffer(registerBody))
	req2.Header.Set("Content-Type", "application/json")
	req2.RemoteAddr = "127.0.0.1:12345"
	w2 := httptest.NewRecorder()
	router.ServeHTTP(w2, req2)
	assert.Equal(t, http.StatusCreated, w2.Code, "Register should still work as it has separate rate limit")
}
