//go:build integration
// +build integration

package middleware

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap/zaptest"
)

// setupTestRedisClient crée un client Redis de test
func setupTestRedisClient(t *testing.T) *redis.Client {
	redisAddr := os.Getenv("REDIS_ADDR")
	if redisAddr == "" {
		redisAddr = "localhost:6379"
	}

	client := redis.NewClient(&redis.Options{
		Addr: redisAddr,
		DB:   15, // Use DB 15 for tests
	})

	// Test connection
	ctx := context.Background()
	_, err := client.Ping(ctx).Result()
	if err != nil {
		t.Skipf("Skipping integration test: Redis not available at %s: %v", redisAddr, err)
		return nil
	}

	// Cleanup: Flush DB after tests
	t.Cleanup(func() {
		client.FlushDB(ctx)
		client.Close()
	})

	// Flush DB before tests
	client.FlushDB(ctx)

	return client
}

// setupCSRFIntegrationTestRouter crée un router de test avec protection CSRF
func setupCSRFIntegrationTestRouter(t *testing.T) (*gin.Engine, *CSRFMiddleware, *redis.Client, func()) {
	gin.SetMode(gin.TestMode)
	logger := zaptest.NewLogger(t)

	// Setup Redis
	redisClient := setupTestRedisClient(t)
	if redisClient == nil {
		t.Skip("Redis not available")
	}

	// Setup CSRF middleware
	csrfMiddleware := NewCSRFMiddleware(redisClient, logger)

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

	// 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()
	})

	// Apply CSRF middleware
	router.Use(csrfMiddleware.Middleware())

	// Setup test endpoints
	v1 := router.Group("/api/v1")
	{
		// Public endpoint (no auth required)
		v1.GET("/public", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"message": "public endpoint"})
		})

		// Protected endpoints (require auth and CSRF for state-changing methods)
		protected := v1.Group("/protected")
		{
			protected.GET("/test", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{"message": "get successful"})
			})
			protected.POST("/test", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{"message": "post successful"})
			})
			protected.PUT("/test", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{"message": "put successful"})
			})
			protected.DELETE("/test", func(c *gin.Context) {
				c.JSON(http.StatusOK, gin.H{"message": "delete successful"})
			})
			protected.HEAD("/test", func(c *gin.Context) {
				c.Status(http.StatusOK)
			})
			protected.OPTIONS("/test", func(c *gin.Context) {
				c.Status(http.StatusOK)
			})
		}

		// CSRF token endpoint
		v1.GET("/csrf-token", func(c *gin.Context) {
			userIDInterface, exists := c.Get("user_id")
			if !exists {
				c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
				return
			}
			userID := userIDInterface.(uuid.UUID)
			token, err := csrfMiddleware.GetToken(c.Request.Context(), userID)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"})
				return
			}
			c.JSON(http.StatusOK, gin.H{"csrf_token": token})
		})
	}

	cleanup := func() {
		// Cleanup handled by t.Cleanup in setupTestRedisClient
	}

	return router, csrfMiddleware, redisClient, cleanup
}

// TestCSRFProtectionIntegration_GETPassesWithoutToken teste que GET passe sans token CSRF
func TestCSRFProtectionIntegration_GETPassesWithoutToken(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, _, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

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

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.Equal(t, "get successful", response["message"])
}

// TestCSRFProtectionIntegration_HEADPassesWithoutToken teste que HEAD passe sans token CSRF
func TestCSRFProtectionIntegration_HEADPassesWithoutToken(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, _, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

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

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

// TestCSRFProtectionIntegration_OPTIONSPassesWithoutToken teste que OPTIONS passe sans token CSRF
func TestCSRFProtectionIntegration_OPTIONSPassesWithoutToken(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, _, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

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

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

// TestCSRFProtectionIntegration_POSTRequiresToken teste que POST nécessite un token CSRF
func TestCSRFProtectionIntegration_POSTRequiresToken(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, _, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

	userID := uuid.New()
	req := httptest.NewRequest("POST", "/api/v1/protected/test", bytes.NewBuffer([]byte(`{}`)))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	// No X-CSRF-Token header
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.False(t, response["success"].(bool))
	errorObj := response["error"].(map[string]interface{})
	assert.Equal(t, "CSRF token required", errorObj["message"])
}

// TestCSRFProtectionIntegration_PUTRequiresToken teste que PUT nécessite un token CSRF
func TestCSRFProtectionIntegration_PUTRequiresToken(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, _, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

	userID := uuid.New()
	req := httptest.NewRequest("PUT", "/api/v1/protected/test", bytes.NewBuffer([]byte(`{}`)))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	// No X-CSRF-Token header
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

// TestCSRFProtectionIntegration_DELETERequiresToken teste que DELETE nécessite un token CSRF
func TestCSRFProtectionIntegration_DELETERequiresToken(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, _, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

	userID := uuid.New()
	req := httptest.NewRequest("DELETE", "/api/v1/protected/test", nil)
	req.Header.Set("X-User-ID", userID.String())
	// No X-CSRF-Token header
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

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

// TestCSRFProtectionIntegration_InvalidTokenRejected teste qu'un token invalide est rejeté
func TestCSRFProtectionIntegration_InvalidTokenRejected(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, _, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

	userID := uuid.New()
	req := httptest.NewRequest("POST", "/api/v1/protected/test", bytes.NewBuffer([]byte(`{}`)))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	req.Header.Set("X-CSRF-Token", "invalid_token_12345")
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code)
	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.False(t, response["success"].(bool))
	errorObj := response["error"].(map[string]interface{})
	assert.Contains(t, errorObj["message"].(string), "Invalid")
}

// TestCSRFProtectionIntegration_ValidTokenPasses teste qu'un token valide permet la requête
func TestCSRFProtectionIntegration_ValidTokenPasses(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, csrfMiddleware, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

	userID := uuid.New()

	// Generate a valid CSRF token
	ctx := context.Background()
	token, err := csrfMiddleware.GenerateToken(ctx, userID)
	require.NoError(t, err)
	require.NotEmpty(t, token)

	// Make POST request with valid token
	req := httptest.NewRequest("POST", "/api/v1/protected/test", bytes.NewBuffer([]byte(`{}`)))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID.String())
	req.Header.Set("X-CSRF-Token", token)
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.Equal(t, "post successful", response["message"])
}

// TestCSRFProtectionIntegration_GetTokenEndpoint teste l'endpoint de génération de token
func TestCSRFProtectionIntegration_GetTokenEndpoint(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, _, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

	userID := uuid.New()

	// Request CSRF token
	req := httptest.NewRequest("GET", "/api/v1/csrf-token", nil)
	req.Header.Set("X-User-ID", userID.String())
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusOK, w.Code)
	var response map[string]interface{}
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)
	assert.Contains(t, response, "csrf_token")
	token := response["csrf_token"].(string)
	assert.NotEmpty(t, token)
	assert.Len(t, token, 64) // 32 bytes = 64 hex characters
}

// TestCSRFProtectionIntegration_UnauthenticatedNotBlocked teste que les utilisateurs non authentifiés ne sont pas bloqués
func TestCSRFProtectionIntegration_UnauthenticatedNotBlocked(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, _, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

	// Make POST request without authentication (no X-User-ID header)
	req := httptest.NewRequest("POST", "/api/v1/protected/test", bytes.NewBuffer([]byte(`{}`)))
	req.Header.Set("Content-Type", "application/json")
	// No X-User-ID header, so no CSRF check
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Should pass CSRF check (no user_id), but fail auth check in handler
	// Actually, the protected endpoint might require auth, but CSRF middleware won't block it
	// Let's check what happens - the endpoint handler might return 401
	// But CSRF middleware should not block it
	assert.NotEqual(t, http.StatusForbidden, w.Code, "CSRF should not block unauthenticated requests")
}

// TestCSRFProtectionIntegration_PublicEndpointNotBlocked teste que les endpoints publics ne sont pas bloqués
func TestCSRFProtectionIntegration_PublicEndpointNotBlocked(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, _, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

	// Make POST request to public endpoint
	req := httptest.NewRequest("POST", "/api/v1/public", bytes.NewBuffer([]byte(`{}`)))
	req.Header.Set("Content-Type", "application/json")
	// No X-User-ID header
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	// Public endpoint should work (though it's GET in our setup, let's test GET)
	req2 := httptest.NewRequest("GET", "/api/v1/public", nil)
	w2 := httptest.NewRecorder()
	router.ServeHTTP(w2, req2)

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

// TestCSRFProtectionIntegration_TokenPerUser teste que chaque utilisateur a son propre token
func TestCSRFProtectionIntegration_TokenPerUser(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, csrfMiddleware, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

	userID1 := uuid.New()
	userID2 := uuid.New()

	// Generate tokens for both users
	ctx := context.Background()
	token1, err := csrfMiddleware.GenerateToken(ctx, userID1)
	require.NoError(t, err)
	token2, err := csrfMiddleware.GenerateToken(ctx, userID2)
	require.NoError(t, err)

	// User 1's token should not work for User 2
	req := httptest.NewRequest("POST", "/api/v1/protected/test", bytes.NewBuffer([]byte(`{}`)))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-User-ID", userID2.String())
	req.Header.Set("X-CSRF-Token", token1) // User 1's token
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	assert.Equal(t, http.StatusForbidden, w.Code, "User 1's token should not work for User 2")

	// User 2's token should work for User 2
	req2 := httptest.NewRequest("POST", "/api/v1/protected/test", bytes.NewBuffer([]byte(`{}`)))
	req2.Header.Set("Content-Type", "application/json")
	req2.Header.Set("X-User-ID", userID2.String())
	req2.Header.Set("X-CSRF-Token", token2) // User 2's token
	w2 := httptest.NewRecorder()
	router.ServeHTTP(w2, req2)

	assert.Equal(t, http.StatusOK, w2.Code, "User 2's token should work for User 2")
}

// TestCSRFProtectionIntegration_MultipleRequestsSameToken teste que le même token peut être utilisé plusieurs fois
func TestCSRFProtectionIntegration_MultipleRequestsSameToken(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	router, csrfMiddleware, _, cleanup := setupCSRFIntegrationTestRouter(t)
	defer cleanup()

	userID := uuid.New()

	// Generate a token
	ctx := context.Background()
	token, err := csrfMiddleware.GenerateToken(ctx, userID)
	require.NoError(t, err)

	// Make multiple requests with the same token
	for i := 0; i < 3; i++ {
		req := httptest.NewRequest("POST", "/api/v1/protected/test", bytes.NewBuffer([]byte(`{}`)))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("X-User-ID", userID.String())
		req.Header.Set("X-CSRF-Token", token)
		w := httptest.NewRecorder()
		router.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code, "Request %d should succeed with same token", i+1)
	}
}
