package api

import (
	"net"
	"net/http"
	"sync"
	"time"
)

// rateLimiter — in-memory token bucket rate limiter.
type rateLimiter struct {
	mu              sync.Mutex
	buckets         map[string]*tokenBucket
	rate            float64 // токенов в секунду
	burst           int     // максимальный размер бакета
	cleanupInterval time.Duration
}

type tokenBucket struct {
	tokens   float64
	lastSeen time.Time
}

// newRateLimiter создаёт rate limiter с заданными параметрами.
// Запускает фоновую горутину очистки устаревших бакетов.
func newRateLimiter(ratePerSec, burst int) *rateLimiter {
	if burst <= 0 {
		burst = ratePerSec
	}
	rl := &rateLimiter{
		buckets:         make(map[string]*tokenBucket),
		rate:            float64(ratePerSec),
		burst:           burst,
		cleanupInterval: 60 * time.Second,
	}

	// Фоновая горутина очистки — не блокирует hot path
	go rl.cleanupLoop()

	return rl
}

// allow проверяет, разрешён ли запрос для данного ключа.
// Возвращает true если разрешён, false если превышен лимит.
func (rl *rateLimiter) allow(key string) bool {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	now := time.Now()

	b, exists := rl.buckets[key]
	if !exists {
		b = &tokenBucket{tokens: float64(rl.burst), lastSeen: now}
		rl.buckets[key] = b
		b.tokens--
		return true
	}

	// Пополняем токены
	elapsed := now.Sub(b.lastSeen).Seconds()
	b.tokens += elapsed * rl.rate
	if b.tokens > float64(rl.burst) {
		b.tokens = float64(rl.burst)
	}
	b.lastSeen = now

	if b.tokens >= 1.0 {
		b.tokens--
		return true
	}
	return false
}

// cleanupLoop — фоновая очистка устаревших бакетов.
func (rl *rateLimiter) cleanupLoop() {
	ticker := time.NewTicker(rl.cleanupInterval)
	defer ticker.Stop()
	for range ticker.C {
		rl.mu.Lock()
		for k, b := range rl.buckets {
			if time.Since(b.lastSeen) > rl.cleanupInterval {
				delete(rl.buckets, k)
			}
		}
		rl.mu.Unlock()
	}
}

// rateLimitMiddleware создаёт middleware с rate limiting по IP.
// Ключ — RemoteAddr (или X-Forwarded-For, если за проксей).
func rateLimitMiddleware(limiter *rateLimiter) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			key := stripPort(r.RemoteAddr)
			if fwd := r.Header.Get("X-Forwarded-For"); fwd != "" {
				key = fwd
			}

			if !limiter.allow(key) {
				w.Header().Set("Retry-After", "1")
				writeError(w, http.StatusTooManyRequests, "слишком много запросов")
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

// stripPort убирает порт из адреса (напр. "[::1]:58544" → "::1", "127.0.0.1:1234" → "127.0.0.1").
func stripPort(addr string) string {
	host, _, err := net.SplitHostPort(addr)
	if err != nil {
		return addr // нет порта
	}
	return host
}
