package middleware

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

	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestMetricsMiddleware(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Metrics())
	router.GET("/test", func(c *gin.Context) {
		c.JSON(200, gin.H{"ok": true})
	})

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

	assert.Equal(t, 200, w.Code)

	// Vérifier que les métriques ont été enregistrées
	// On vérifie via le registry Prometheus par défaut
	registry := prometheus.DefaultRegisterer.(*prometheus.Registry)
	metricFamilies, err := registry.Gather()
	require.NoError(t, err)

	foundRequestsTotal := false
	foundDuration := false

	for _, mf := range metricFamilies {
		if *mf.Name == "veza_gin_http_requests_total" {
			foundRequestsTotal = true
			assert.Greater(t, len(mf.Metric), 0)
		}
		if *mf.Name == "veza_gin_http_request_duration_seconds" {
			foundDuration = true
			assert.Greater(t, len(mf.Metric), 0)
		}
	}

	assert.True(t, foundRequestsTotal, "veza_gin_http_requests_total metric should exist")
	assert.True(t, foundDuration, "veza_gin_http_request_duration_seconds metric should exist")
}

func TestMetricsMiddleware_DifferentStatusCodes(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Metrics())

	router.GET("/ok", func(c *gin.Context) {
		c.JSON(200, gin.H{"ok": true})
	})
	router.GET("/notfound", func(c *gin.Context) {
		c.JSON(404, gin.H{"error": "not found"})
	})
	router.GET("/error", func(c *gin.Context) {
		c.JSON(500, gin.H{"error": "internal error"})
	})

	// Tester différents codes de status
	w1 := httptest.NewRecorder()
	req1 := httptest.NewRequest("GET", "/ok", nil)
	router.ServeHTTP(w1, req1)
	assert.Equal(t, 200, w1.Code)

	w2 := httptest.NewRecorder()
	req2 := httptest.NewRequest("GET", "/notfound", nil)
	router.ServeHTTP(w2, req2)
	assert.Equal(t, 404, w2.Code)

	w3 := httptest.NewRecorder()
	req3 := httptest.NewRequest("GET", "/error", nil)
	router.ServeHTTP(w3, req3)
	assert.Equal(t, 500, w3.Code)
}

func TestMetricsMiddleware_DifferentMethods(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Metrics())

	router.GET("/resource", func(c *gin.Context) {
		c.JSON(200, gin.H{"method": "GET"})
	})
	router.POST("/resource", func(c *gin.Context) {
		c.JSON(201, gin.H{"method": "POST"})
	})
	router.PUT("/resource", func(c *gin.Context) {
		c.JSON(200, gin.H{"method": "PUT"})
	})
	router.DELETE("/resource", func(c *gin.Context) {
		c.JSON(204, gin.H{"method": "DELETE"})
	})

	// Tester différentes méthodes HTTP
	methods := []struct {
		method string
		path   string
		status int
	}{
		{"GET", "/resource", 200},
		{"POST", "/resource", 201},
		{"PUT", "/resource", 200},
		{"DELETE", "/resource", 204},
	}

	for _, m := range methods {
		w := httptest.NewRecorder()
		req := httptest.NewRequest(m.method, m.path, nil)
		router.ServeHTTP(w, req)
		assert.Equal(t, m.status, w.Code)
	}
}

func TestMetricsMiddleware_DurationMeasurement(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Metrics())

	router.GET("/slow", func(c *gin.Context) {
		time.Sleep(50 * time.Millisecond)
		c.JSON(200, gin.H{"ok": true})
	})

	start := time.Now()
	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/slow", nil)
	router.ServeHTTP(w, req)
	duration := time.Since(start)

	assert.Equal(t, 200, w.Code)
	assert.GreaterOrEqual(t, duration, 50*time.Millisecond, "Should measure at least the sleep duration")
}

func TestMetricsMiddleware_EmptyPath(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Metrics())

	// Route sans nom de route défini
	router.Any("/unknown/*path", func(c *gin.Context) {
		c.JSON(200, gin.H{"ok": true})
	})

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

	assert.Equal(t, 200, w.Code)
	// Le path devrait être l'URL path si FullPath est vide
}

func TestMetricsMiddleware_MultipleRequests(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Metrics())
	router.GET("/test", func(c *gin.Context) {
		c.JSON(200, gin.H{"ok": true})
	})

	// Faire plusieurs requêtes
	for i := 0; i < 5; i++ {
		w := httptest.NewRecorder()
		req := httptest.NewRequest("GET", "/test", nil)
		router.ServeHTTP(w, req)
		assert.Equal(t, 200, w.Code)
	}

	// Vérifier que les métriques sont accumulées
	registry := prometheus.DefaultRegisterer.(*prometheus.Registry)
	metricFamilies, err := registry.Gather()
	require.NoError(t, err)

	totalRequests := 0.0
	for _, mf := range metricFamilies {
		if *mf.Name == "veza_gin_http_requests_total" {
			for _, metric := range mf.Metric {
				if metric.Counter != nil {
					// Somme toutes les valeurs de counter pour cette métrique
					totalRequests += *metric.Counter.Value
				}
			}
		}
	}

	// Au moins 5 requêtes devraient être comptées au total
	// (les métriques sont groupées par labels, donc on somme toutes les valeurs)
	assert.GreaterOrEqual(t, totalRequests, float64(5), "Should have recorded at least 5 requests")
}

func TestMetricsMiddleware_LabelsCorrectness(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Metrics())

	router.GET("/api/v1/users/:id", func(c *gin.Context) {
		c.JSON(200, gin.H{"id": c.Param("id")})
	})

	w := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/api/v1/users/123", nil)
	router.ServeHTTP(w, req)

	assert.Equal(t, 200, w.Code)

	// Vérifier que les labels sont corrects
	registry := prometheus.DefaultRegisterer.(*prometheus.Registry)
	metricFamilies, err := registry.Gather()
	require.NoError(t, err)

	for _, mf := range metricFamilies {
		if *mf.Name == "veza_http_requests_total" {
			for _, metric := range mf.Metric {
				method := ""
				path := ""
				status := ""

				for _, label := range metric.Label {
					switch *label.Name {
					case "method":
						method = *label.Value
					case "path":
						path = *label.Value
					case "status":
						status = *label.Value
					}
				}

				if method == "GET" && path == "/api/v1/users/:id" {
					assert.Equal(t, "200", status)
				}
			}
		}
	}
}

func TestMetricsMiddleware_HistogramBuckets(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(Metrics())

	router.GET("/test", func(c *gin.Context) {
		c.JSON(200, gin.H{"ok": true})
	})

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

	assert.Equal(t, 200, w.Code)

	// Vérifier que l'histogramme est correctement configuré
	registry := prometheus.DefaultRegisterer.(*prometheus.Registry)
	metricFamilies, err := registry.Gather()
	require.NoError(t, err)

	for _, mf := range metricFamilies {
		if *mf.Name == "veza_http_request_duration_seconds" {
			assert.Equal(t, dto.MetricType_HISTOGRAM, *mf.Type)
			assert.Greater(t, len(mf.Metric), 0)
		}
	}
}
