package middleware

import (
	"fmt"
	"net/http"
	"os"
	"strconv"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
)

// SimpleRateLimiter est un rate limiter simple basé sur une sliding window en mémoire
// Utilisé pour le rate limiting basique par IP sans dépendance Redis
type SimpleRateLimiter struct {
	requests map[string][]time.Time
	limit    int
	window   time.Duration
	mu       sync.Mutex
	stop     chan struct{} // Channel to signal cleanup goroutine to stop
}

// NewSimpleRateLimiter crée un nouveau rate limiter simple
// limit: nombre maximum de requêtes
// window: fenêtre de temps (ex: 1 * time.Minute pour 100 req/min)
func NewSimpleRateLimiter(limit int, window time.Duration) *SimpleRateLimiter {
	rl := &SimpleRateLimiter{
		requests: make(map[string][]time.Time),
		limit:    limit,
		window:   window,
		stop:     make(chan struct{}), // Initialize the stop channel
	}

	// Démarrer la goroutine de nettoyage
	go rl.cleanup()
	return rl
}

// Routes exclues du rate limiting (routes critiques)
var excludedRateLimitPaths = []string{
	"/health",
	"/healthz",
	"/readyz",
	"/api/v1/health",
	"/api/v1/healthz",
	"/api/v1/readyz",
	"/api/v1/csrf-token",
	"/api/v1/auth/register",
	"/api/v1/auth/login",
	"/api/v1/auth/refresh",
	"/api/v1/auth/verify-email",
	"/api/v1/auth/resend-verification",
	"/api/v1/auth/check-username",
	"/swagger",
	"/docs",
}

// isExcludedPath vérifie si un chemin est exclu du rate limiting
func isExcludedPath(path string) bool {
	for _, excluded := range excludedRateLimitPaths {
		if path == excluded || (len(path) > len(excluded) && path[:len(excluded)] == excluded) {
			return true
		}
	}
	return false
}

// Middleware retourne le middleware Gin pour le rate limiting
func (rl *SimpleRateLimiter) Middleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// Exclure les routes critiques du rate limiting
		if isExcludedPath(c.Request.URL.Path) {
			c.Next()
			return
		}

		// DÉSACTIVER le rate limiting en mode test/e2e/development pour les tests E2E et développement
		if c.GetHeader("X-Test-Mode") == "true" ||
			c.GetHeader("X-E2E-Test") == "true" ||
			os.Getenv("NODE_ENV") == "test" ||
			os.Getenv("NODE_ENV") == "e2e" ||
			os.Getenv("NODE_ENV") == "development" ||
			os.Getenv("APP_ENV") == "test" ||
			os.Getenv("APP_ENV") == "e2e" ||
			os.Getenv("APP_ENV") == "development" {
			c.Next()
			return
		}

		ip := c.ClientIP()

		rl.mu.Lock()
		now := time.Now()
		cutoff := now.Add(-rl.window)

		// Nettoyer les anciennes requêtes
		valid := []time.Time{}
		for _, t := range rl.requests[ip] {
			if t.After(cutoff) {
				valid = append(valid, t)
			}
		}

		// Vérifier si la limite est atteinte
		if len(valid) >= rl.limit {
			rl.mu.Unlock()
			resetTime := now.Add(rl.window).Unix()
			retryAfter := int(rl.window.Seconds())

			// INT-013: Standardize rate limit response format
			c.Header("X-RateLimit-Limit", strconv.Itoa(rl.limit))
			c.Header("X-RateLimit-Remaining", "0")
			c.Header("X-RateLimit-Reset", strconv.FormatInt(resetTime, 10))
			c.Header("Retry-After", strconv.Itoa(retryAfter))
			c.JSON(http.StatusTooManyRequests, gin.H{
				"success": false,
				"error": gin.H{
					"code":    429,
					"message": "Rate limit exceeded. Please try again later.",
					"details": []gin.H{
						{
							"field":   "rate_limit",
							"message": fmt.Sprintf("You have exceeded the rate limit of %d requests per %v", rl.limit, rl.window),
						},
					},
					"retry_after": retryAfter,
					"limit":       rl.limit,
					"remaining":   0,
					"reset":       resetTime,
				},
			})
			c.Abort()
			return
		}

		// Ajouter la nouvelle requête
		valid = append(valid, now)
		rl.requests[ip] = valid
		remaining := rl.limit - len(valid)
		rl.mu.Unlock()

		// Ajouter les headers de rate limiting
		c.Header("X-RateLimit-Limit", strconv.Itoa(rl.limit))
		c.Header("X-RateLimit-Remaining", strconv.Itoa(remaining))
		c.Header("X-RateLimit-Reset", strconv.FormatInt(now.Add(rl.window).Unix(), 10))

		c.Next()
	}
}

// UpdateLimits met à jour les limites de rate limiting (T0034)
// Permet le rechargement à chaud des limites sans redémarrer l'application
func (rl *SimpleRateLimiter) UpdateLimits(limit int, window time.Duration) {
	rl.mu.Lock()
	defer rl.mu.Unlock()
	rl.limit = limit
	rl.window = window
}

// cleanup nettoie périodiquement les anciennes requêtes
func (rl *SimpleRateLimiter) cleanup() {
	ticker := time.NewTicker(1 * time.Minute)
	defer ticker.Stop() // Ensure ticker is stopped

	for {
		select {
		case <-ticker.C:
			rl.mu.Lock()
			cutoff := time.Now().Add(-rl.window)
			for ip, times := range rl.requests {
				valid := []time.Time{}
				for _, t := range times {
					if t.After(cutoff) {
						valid = append(valid, t)
					}
				}
				if len(valid) == 0 {
					delete(rl.requests, ip)
				} else {
					rl.requests[ip] = valid
				}
			}
			rl.mu.Unlock()
		case <-rl.stop: // Listen for stop signal
			return // Exit goroutine
		}
	}
}

// Stop signale au goroutine de nettoyage de s'arrêter
func (rl *SimpleRateLimiter) Stop() {
	close(rl.stop)
}
