package middleware

import (
	"net/http"
	"time"

	"github.com/TaruLab/keluargamart-backend/pkg/httpresponse"
	redislib "github.com/TaruLab/keluargamart-backend/pkg/redis"
	"github.com/redis/go-redis/v9"
)

// RateLimiter limits requests per key (usually client IP) across a window.
// rdb may be nil → limit disabled (local dev without Redis).
type RateLimiter struct {
	rdb        *redis.Client
	key        func(r *http.Request) string
	limit      int64
	window     time.Duration
	failClosed bool
}

// RateLimitOption configures a RateLimiter.
type RateLimitOption func(*RateLimiter)

// WithFailClosed makes the limiter reject requests when the backing store
// errors (safe default for abuse-prone endpoints such as login). The default
// is fail-open so a Redis outage never blocks normal traffic.
func WithFailClosed(v bool) RateLimitOption {
	return func(rl *RateLimiter) { rl.failClosed = v }
}

func NewRateLimiter(rdb *redis.Client, keyFn func(*http.Request) string, limit int64, window time.Duration, opts ...RateLimitOption) *RateLimiter {
	rl := &RateLimiter{rdb: rdb, key: keyFn, limit: limit, window: window}
	for _, o := range opts {
		o(rl)
	}
	return rl
}

// Handler returns the middleware. 429 uses RATE_LIMITED per api-design §4.
func (rl *RateLimiter) Handler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if rl.rdb != nil && rl.limit > 0 {
			ok, err := redislib.Allow(r.Context(), rl.rdb, rl.key(r), rl.limit, rl.window)
			if err != nil {
				if rl.failClosed {
					httpresponse.Error(w, http.StatusServiceUnavailable, RequestIDFrom(r.Context()),
						httpresponse.CodeInternal, "Rate limiter unavailable, try again", nil)
					return
				}
				// fail-open on infra error so Redis outage never blocks traffic
				next.ServeHTTP(w, r)
				return
			}
			if !ok {
				httpresponse.Error(w, http.StatusTooManyRequests, RequestIDFrom(r.Context()),
					httpresponse.CodeRateLimited, "Too many requests", nil)
				return
			}
		}
		next.ServeHTTP(w, r)
	})
}
