package middleware

import (
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
)

func TestRateLimiter_AllowsNormalTraffic(t *testing.T) {
	gin.SetMode(gin.TestMode)
	rl := NewRateLimiter(100, 200)

	r := gin.New()
	r.Use(rl.Middleware())
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req := httptest.NewRequest("GET", "/test", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

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

func TestRateLimiter_BlocksExcessiveTraffic(t *testing.T) {
	gin.SetMode(gin.TestMode)
	rl := NewRateLimiter(1, 1)

	r := gin.New()
	r.Use(rl.Middleware())
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req1 := httptest.NewRequest("GET", "/test", nil)
	req1.RemoteAddr = "192.168.1.1:1234"
	w1 := httptest.NewRecorder()
	r.ServeHTTP(w1, req1)
	assert.Equal(t, http.StatusOK, w1.Code)

	req2 := httptest.NewRequest("GET", "/test", nil)
	req2.RemoteAddr = "192.168.1.1:1234"
	w2 := httptest.NewRecorder()
	r.ServeHTTP(w2, req2)
	assert.Equal(t, http.StatusTooManyRequests, w2.Code)
	assert.Contains(t, w2.Body.String(), "Rate limit exceeded")
}

func TestRateLimiter_BypassesHealthEndpoint(t *testing.T) {
	gin.SetMode(gin.TestMode)
	rl := NewRateLimiter(0.001, 1)

	r := gin.New()
	r.Use(rl.Middleware())
	r.GET("/health", func(c *gin.Context) {
		c.String(http.StatusOK, "healthy")
	})

	req := httptest.NewRequest("GET", "/health", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	assert.Equal(t, http.StatusOK, w.Code, "Path /health should bypass rate limit")
}

func TestRateLimiter_IsolatesPerIP(t *testing.T) {
	gin.SetMode(gin.TestMode)
	rl := NewRateLimiter(1, 1)

	r := gin.New()
	r.Use(rl.Middleware())
	r.GET("/test", func(c *gin.Context) {
		c.String(http.StatusOK, "ok")
	})

	req1 := httptest.NewRequest("GET", "/test", nil)
	req1.RemoteAddr = "192.168.1.1:1234"
	w1 := httptest.NewRecorder()
	r.ServeHTTP(w1, req1)
	assert.Equal(t, http.StatusOK, w1.Code)

	req2 := httptest.NewRequest("GET", "/test", nil)
	req2.RemoteAddr = "192.168.1.2:1234"
	w2 := httptest.NewRecorder()
	r.ServeHTTP(w2, req2)
	assert.Equal(t, http.StatusOK, w2.Code)

	req3 := httptest.NewRequest("GET", "/test", nil)
	req3.RemoteAddr = "192.168.1.1:1234"
	w3 := httptest.NewRecorder()
	r.ServeHTTP(w3, req3)
	assert.Equal(t, http.StatusTooManyRequests, w3.Code)
}

func TestDefaultRateLimiter_UsesDefaults(t *testing.T) {
	rl := DefaultRateLimiter()
	assert.NotNil(t, rl)
	assert.NotNil(t, rl.limiters)
}

func TestDefaultRateLimiter_UsesEnvValues(t *testing.T) {
	t.Setenv("RATE_LIMIT_RPS", "50")
	t.Setenv("RATE_LIMIT_BURST", "100")

	rl := DefaultRateLimiter()
	assert.NotNil(t, rl)
}

func TestGetEnvFloat_ReturnsEnvValue(t *testing.T) {
	t.Setenv("TEST_FLOAT_VAR", "42.5")
	result := getEnvFloat("TEST_FLOAT_VAR", 10.0)
	assert.Equal(t, 42.5, result)
}

func TestGetEnvFloat_ReturnsFallbackForMissing(t *testing.T) {
	result := getEnvFloat("NONEXISTENT_FLOAT_VAR_XYZ", 99.9)
	assert.Equal(t, 99.9, result)
}

func TestGetEnvFloat_ReturnsFallbackForInvalid(t *testing.T) {
	t.Setenv("TEST_FLOAT_INVALID", "not-a-number")
	result := getEnvFloat("TEST_FLOAT_INVALID", 77.7)
	assert.Equal(t, 77.7, result)
}

func TestGetEnvInt_ReturnsEnvValue(t *testing.T) {
	t.Setenv("TEST_INT_VAR", "42")
	result := getEnvInt("TEST_INT_VAR", 10)
	assert.Equal(t, 42, result)
}

func TestGetEnvInt_ReturnsFallbackForMissing(t *testing.T) {
	result := getEnvInt("NONEXISTENT_INT_VAR_XYZ", 99)
	assert.Equal(t, 99, result)
}

func TestGetEnvInt_ReturnsFallbackForInvalid(t *testing.T) {
	t.Setenv("TEST_INT_INVALID", "not-a-number")
	result := getEnvInt("TEST_INT_INVALID", 77)
	assert.Equal(t, 77, result)
}

func TestRateLimiter_GetLimiter_DoubleCheck(t *testing.T) {
	rl := NewRateLimiter(10, 20)

	l1 := rl.getLimiter("test-ip")
	assert.NotNil(t, l1)

	l2 := rl.getLimiter("test-ip")
	assert.True(t, l1 == l2, "Same key should return same limiter instance")

	l3 := rl.getLimiter("other-ip")
	assert.NotNil(t, l3)
	assert.True(t, l1 != l3, "Different key should return different limiter")
}
