package middleware

import (
	"net/http"
	"net/http/httptest"
	"sync/atomic"
	"testing"
	"time"

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

// newRateLimitRouter 构造一个使用 RateLimit 中间件、所有请求都返回 200 的测试 router。
// 返回 router 与下游被调用次数计数器，便于断言请求是否被放行。
func newRateLimitRouter(rps float64, burst int) (*gin.Engine, *int32) {
	gin.SetMode(gin.TestMode)
	var count int32
	r := gin.New()
	r.Use(RateLimit(rps, burst))
	r.GET("/test", func(c *gin.Context) {
		atomic.AddInt32(&count, 1)
		c.Status(http.StatusOK)
	})
	return r, &count
}

// doRequest 发起一次测试请求，返回响应状态码。
func doRequest(r *gin.Engine, remoteAddr string) int {
	w := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/test", nil)
	req.RemoteAddr = remoteAddr
	r.ServeHTTP(w, req)
	return w.Code
}

// TestRateLimit_AllowUnderBurst 验证突发请求 ≤ burst 时全部放行。
func TestRateLimit_AllowUnderBurst(t *testing.T) {
	r, count := newRateLimitRouter(0.1 /*rps*/, 5 /*burst*/)

	// 5 个请求应全部放行（初始满桶 5 令牌）
	for i := 0; i < 5; i++ {
		if code := doRequest(r, "1.1.1.1:1234"); code != http.StatusOK {
			t.Fatalf("请求 %d 应放行（200），实际 %d", i+1, code)
		}
	}
	if got := atomic.LoadInt32(count); got != 5 {
		t.Fatalf("下游应被调用 5 次，实际 %d", got)
	}
}

// TestRateLimit_RejectOverBurst 验证突发请求 > burst 时返回 429。
func TestRateLimit_RejectOverBurst(t *testing.T) {
	r, count := newRateLimitRouter(0.1 /*rps*/, 3 /*burst*/)

	// 前 3 个放行
	for i := 0; i < 3; i++ {
		if code := doRequest(r, "2.2.2.2:1234"); code != http.StatusOK {
			t.Fatalf("请求 %d 应放行（200），实际 %d", i+1, code)
		}
	}
	// 第 4 个应被限流（429）
	if code := doRequest(r, "2.2.2.2:1234"); code != http.StatusTooManyRequests {
		t.Fatalf("第 4 个请求应被限流（429），实际 %d", code)
	}
	if got := atomic.LoadInt32(count); got != 3 {
		t.Fatalf("下游应被调用 3 次，实际 %d", got)
	}
}

// TestRateLimit_RefillTokens 验证令牌按 rps 速率补充。
// rps=10 表示每秒补 10 个令牌，即每 100ms 补 1 个；消耗 1 个后等待 ~150ms 应补回 1+ 个。
func TestRateLimit_RefillTokens(t *testing.T) {
	r, count := newRateLimitRouter(10 /*rps*/, 1 /*burst*/)

	// 第 1 个放行（消耗唯一令牌）
	if code := doRequest(r, "3.3.3.3:1234"); code != http.StatusOK {
		t.Fatalf("第 1 个请求应放行（200），实际 %d", code)
	}
	// 立即第 2 个应被限流（无令牌）
	if code := doRequest(r, "3.3.3.3:1234"); code != http.StatusTooManyRequests {
		t.Fatalf("第 2 个请求应被限流（429），实际 %d", code)
	}
	// 等待 150ms，应补充 1.5 个令牌（capped 至 burst=1）
	time.Sleep(150 * time.Millisecond)
	// 第 3 个应再次放行
	if code := doRequest(r, "3.3.3.3:1234"); code != http.StatusOK {
		t.Fatalf("等待补充后请求应放行（200），实际 %d", code)
	}
	if got := atomic.LoadInt32(count); got != 2 {
		t.Fatalf("下游应被调用 2 次，实际 %d", got)
	}
}

// TestRateLimit_PerIPIsolation 验证不同 IP 的桶相互独立。
func TestRateLimit_PerIPIsolation(t *testing.T) {
	r, count := newRateLimitRouter(0.1 /*rps*/, 2 /*burst*/)

	// IP A 消耗 2 个令牌（满桶）
	for i := 0; i < 2; i++ {
		if code := doRequest(r, "10.0.0.1:1234"); code != http.StatusOK {
			t.Fatalf("IP A 请求 %d 应放行，实际 %d", i+1, code)
		}
	}
	// IP A 第 3 个应被限流
	if code := doRequest(r, "10.0.0.1:1234"); code != http.StatusTooManyRequests {
		t.Fatalf("IP A 第 3 个请求应被限流，实际 %d", code)
	}
	// IP B 仍有独立满桶，应放行
	if code := doRequest(r, "10.0.0.2:1234"); code != http.StatusOK {
		t.Fatalf("IP B 第 1 个请求应放行（独立桶），实际 %d", code)
	}
	if got := atomic.LoadInt32(count); got != 3 {
		t.Fatalf("下游应被调用 3 次（A×2 + B×1），实际 %d", got)
	}
}

// TestRateLimit_CleanupExpiredBuckets 验证 10 分钟无访问的桶被清理。
// 使用短周期参数（50ms 间隔、500ms 过期）加速测试；直接构造 rateLimiter 而不经过 RateLimit
// （后者会启动 5min/10min 的清理 goroutine，无法在测试中观察）。
// 时序：创建旧桶（1h 前）→ 启动 cleanup → 等 100ms（旧桶被清，无新桶）→ 创建新桶 → 等 100ms（新桶未过 500ms，保留）。
func TestRateLimit_CleanupExpiredBuckets(t *testing.T) {
	rl := &rateLimiter{rps: 1, burst: 1}

	// 创建旧桶并人为把 lastTime 调到 1 小时前
	b := rl.getBucket("9.9.9.9:1234")
	b.mu.Lock()
	b.lastTime = time.Now().Add(-1 * time.Hour)
	b.mu.Unlock()

	// 启动 cleanup，50ms 间隔、500ms 过期
	go rl.cleanup(50*time.Millisecond, 500*time.Millisecond)

	// 等 100ms 让 cleanup 执行（旧桶 lastTime=1h 前，远超 500ms，必被清）
	time.Sleep(100 * time.Millisecond)

	// 旧桶应已清理
	if _, ok := rl.buckets.Load("9.9.9.9:1234"); ok {
		t.Fatal("1 小时无访问的旧桶应被清理")
	}

	// 此时创建新鲜桶（lastTime = now）
	rl.getBucket("8.8.8.8:1234")

	// 再等 100ms 让 cleanup 再执行
	time.Sleep(100 * time.Millisecond)

	// 新鲜桶 lastTime 仅 ~100ms 前，未超 500ms maxIdle，应保留
	if _, ok := rl.buckets.Load("8.8.8.8:1234"); !ok {
		t.Fatal("新鲜桶（<500ms）不应被清理")
	}
}
