package apimetrics

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

	"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 TestAPIGroupFromPath(t *testing.T) {
	assert.Equal(t, "statements", apiGroupFromPath("/api/v1/statements/list"))
	assert.Equal(t, "sqls", apiGroupFromPath("/api/v1/SQLs/:name/data"))
	assert.Equal(t, "slow_query", apiGroupFromPath("/api/v1/slow_query/list"))
	assert.Equal(t, "olap", apiGroupFromPath("/api/v1/olap/:query_id/results"))
	assert.Equal(t, "continuous_profiling", apiGroupFromPath("/api/v1/continuous_profiling/download"))
	assert.Equal(t, "root", apiGroupFromPath("/api/v1"))
	assert.Equal(t, "other", apiGroupFromPath("/api/v1/other/thing"))
}

func TestMiddlewareRecordsMetrics(t *testing.T) {
	// Use isolated registry via re-init is hard; just exercise middleware on a route.
	gin.SetMode(gin.TestMode)
	Init()

	r := gin.New()
	r.Use(Middleware())
	r.GET("/api/v1/statements/list", func(c *gin.Context) {
		c.Status(http.StatusOK)
	})

	before := counterValue(t, httpRequestsTotal, "GET", "/api/v1/statements/list", "200", "statements")

	req := httptest.NewRequest(http.MethodGet, "/api/v1/statements/list", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusOK, w.Code)

	after := counterValue(t, httpRequestsTotal, "GET", "/api/v1/statements/list", "200", "statements")
	assert.Equal(t, before+1, after)
}

func TestMiddlewareSkipsUnmatched(t *testing.T) {
	gin.SetMode(gin.TestMode)
	Init()

	r := gin.New()
	r.Use(Middleware())
	r.GET("/api/v1/statements/list", func(c *gin.Context) {
		c.Status(http.StatusOK)
	})

	before := scrapeAll(t, httpRequestsTotal)

	req := httptest.NewRequest(http.MethodGet, "/api/v1/does-not-exist", nil)
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)
	require.Equal(t, http.StatusNotFound, w.Code)

	after := scrapeAll(t, httpRequestsTotal)
	assert.Equal(t, before, after)
}

func counterValue(t *testing.T, vec *prometheus.CounterVec, labels ...string) float64 {
	t.Helper()
	m, err := vec.GetMetricWithLabelValues(labels...)
	require.NoError(t, err)
	var out dto.Metric
	require.NoError(t, m.Write(&out))
	return out.GetCounter().GetValue()
}

func scrapeAll(t *testing.T, vec *prometheus.CounterVec) float64 {
	t.Helper()
	ch := make(chan prometheus.Metric, 64)
	vec.Collect(ch)
	close(ch)
	var sum float64
	for m := range ch {
		var out dto.Metric
		require.NoError(t, m.Write(&out))
		sum += out.GetCounter().GetValue()
	}
	return sum
}
