package metrics

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

	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"github.com/stretchr/testify/require"
)

func TestNewHandlerFactory(t *testing.T) {
	tests := []struct {
		name        string
		opts        []HandlerFactoryOption
		handlerOpts []HandlerOption
	}{
		{
			name: "basic",
			opts: []HandlerFactoryOption{
				WithNamespace("basic"),
			},
		},
		{
			name: "labels",
			opts: []HandlerFactoryOption{
				WithNamespace("labels"),
				WithLabels("label1", "label2"),
			},
			handlerOpts: []HandlerOption{
				WithLabelValues(map[string]string{"label1": "1", "label2": "1"}),
			},
		},
		{
			name: "request_duration_buckets",
			opts: []HandlerFactoryOption{
				WithNamespace("request_duration_buckets"),
				WithRequestDurationBuckets([]float64{1, 2, 3}),
			},
		},
		{
			name: "time_to_write_header_duration_buckets",
			opts: []HandlerFactoryOption{
				WithNamespace("time_to_write_header_duration_buckets"),
				WithTimeToWriteHeaderDurationBuckets([]float64{1, 2, 3}),
			},
		},
		{
			name: "byte_bucket_size_buckets",
			opts: []HandlerFactoryOption{
				WithNamespace("byte_bucket_size_buckets"),
				WithByteSizeBuckets([]float64{1, 2, 3}),
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := NewHandlerFactory(tt.opts...)
			require.NotNil(t, got)

			var invoked bool
			handler := got(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				invoked = true
				w.WriteHeader(200)
				fmt.Fprint(w, "OK")
			}), tt.handlerOpts...)

			r := httptest.NewRequest("GET", "http://example.com", nil)

			w := httptest.NewRecorder()
			handler.ServeHTTP(w, r)

			metrics, err := prometheus.DefaultGatherer.Gather()
			require.NoError(t, err)
			require.NotEmpty(t, metrics)

			keyedMetrics := make(map[string]*dto.MetricFamily, len(metrics))
			for _, v := range metrics {
				keyedMetrics[*v.Name] = v
			}

			httpInFlightRequestsMetric := keyedMetrics[tt.name+"_http_"+httpInFlightRequestsMetricName]
			require.NotNil(t, httpInFlightRequestsMetric, "No metric named %v", tt.name+"_http_"+httpInFlightRequestsMetricName)
			require.Equal(t, 0.0, *httpInFlightRequestsMetric.Metric[0].Gauge.Value)

			httpRequestsTotalMetric := keyedMetrics[tt.name+"_http_"+httpRequestsTotalMetricName]
			require.NotNil(t, httpRequestsTotalMetric, "No metric named %v", tt.name+"_http_"+httpRequestsTotalMetricName)
			require.Equal(t, 1.0, *httpRequestsTotalMetric.Metric[0].Counter.Value)

			httpRequestDurationSecondsMetric := keyedMetrics[tt.name+"_http_"+httpRequestDurationSecondsMetricName]
			require.NotNil(t, httpRequestDurationSecondsMetric, "No metric named %v", tt.name+"_http_"+httpRequestDurationSecondsMetricName)
			require.Equal(t, uint64(1), *httpRequestDurationSecondsMetric.Metric[0].Histogram.SampleCount)

			httpRequestSizeBytesMetric := keyedMetrics[tt.name+"_http_"+httpRequestSizeBytesMetricName]
			require.NotNil(t, httpRequestSizeBytesMetric, "No metric named %v", tt.name+"_http_"+httpRequestSizeBytesMetricName)
			require.Equal(t, uint64(1), *httpRequestSizeBytesMetric.Metric[0].Histogram.SampleCount)

			httpResponseSizeBytesMetric := keyedMetrics[tt.name+"_http_"+httpResponseSizeBytesMetricName]
			require.NotNil(t, httpResponseSizeBytesMetric, "No metric named %v", tt.name+"_http_"+httpResponseSizeBytesMetricName)
			require.Equal(t, uint64(1), *httpResponseSizeBytesMetric.Metric[0].Histogram.SampleCount)

			httpTimeToWriteHeaderSecondsMetric := keyedMetrics[tt.name+"_http_"+httpTimeToWriteHeaderSecondsMetricName]
			require.NotNil(t, httpTimeToWriteHeaderSecondsMetric, "No metric named %v", tt.name+"_http_"+httpTimeToWriteHeaderSecondsMetricName)
			require.Equal(t, uint64(1), *httpTimeToWriteHeaderSecondsMetric.Metric[0].Histogram.SampleCount)

			require.True(t, invoked, "handler not executed")
		})
	}
}
