package monitoring

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

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

type customGatherer struct {
}

func (c customGatherer) Gather() ([]*dto.MetricFamily, error) {
	return nil, nil
}

type customRegisterer struct {
}

func (c customRegisterer) Register(collector prometheus.Collector) error {
	return nil
}

func (c customRegisterer) MustRegister(collector ...prometheus.Collector) {
}

func (c customRegisterer) Unregister(collector prometheus.Collector) bool {
	return false
}

func checkDefaultGatherer(t *testing.T, g prometheus.Gatherer) {
	assert.Equal(t, prometheus.DefaultGatherer, g)
}

func checkCustomGatherer(t *testing.T, g prometheus.Gatherer) {
	assert.Equal(t, customGatherer{}, g)
}

func checkDefaultRegisterer(t *testing.T, g prometheus.Registerer) {
	assert.Equal(t, prometheus.DefaultRegisterer, g)
}

func checkCustomRegisterer(t *testing.T, g prometheus.Registerer) {
	assert.Equal(t, customRegisterer{}, g)
}
func checkDefaultListener(t *testing.T, f listenerFactory) {
	gotListener, err := f()
	require.Exactly(t, nil, gotListener)
	require.NoError(t, err)
}

func isTCPListener(t *testing.T, f listenerFactory) {
	gotListener, err := f()
	require.NoError(t, err)
	require.IsType(t, &net.TCPListener{}, gotListener)
}

func Test_applyOptions(t *testing.T) {
	testListener, err := net.Listen("tcp", ":0")
	require.NoError(t, err)
	defer testListener.Close()

	isTestListener := func(t *testing.T, f listenerFactory) {
		gotListener, err := f()
		require.NoError(t, err)
		require.Exactly(t, testListener, gotListener)
	}

	testMux := http.NewServeMux()
	testMux.HandleFunc("/debug/foo", func(writer http.ResponseWriter, r *http.Request) {})

	tests := []struct {
		name                            string
		opts                            []Option
		wantListenerCheck               func(t *testing.T, f listenerFactory)
		wantGathererCheck               func(t *testing.T, g prometheus.Gatherer)
		wantRegistererCheck             func(t *testing.T, g prometheus.Registerer)
		wantbuildInfoGaugeLabels        prometheus.Labels
		wantVersion                     string
		wantContinuousProfilingDisabled bool
		wantMetricsDisabled             bool
		wantPprofDisabled               bool
		wantMetricsHandlerPattern       string
		wantProfilerCredentialsFile     string
		wantServeMux                    *http.ServeMux
	}{
		{
			name:                      "empty",
			opts:                      nil,
			wantListenerCheck:         checkDefaultListener,
			wantGathererCheck:         checkDefaultGatherer,
			wantRegistererCheck:       checkDefaultRegisterer,
			wantMetricsHandlerPattern: "/metrics",
			wantServeMux:              http.NewServeMux(),
		},
		{
			name:                      "with_listener",
			opts:                      []Option{WithListener(testListener)},
			wantListenerCheck:         isTestListener,
			wantGathererCheck:         checkDefaultGatherer,
			wantRegistererCheck:       checkDefaultRegisterer,
			wantMetricsHandlerPattern: "/metrics",
			wantServeMux:              http.NewServeMux(),
		},
		{
			name:                      "with_listen_address",
			opts:                      []Option{WithListenerAddress(":0")},
			wantListenerCheck:         isTCPListener,
			wantGathererCheck:         checkDefaultGatherer,
			wantRegistererCheck:       checkDefaultRegisterer,
			wantMetricsHandlerPattern: "/metrics",
			wantServeMux:              http.NewServeMux(),
		},
		{
			name:                      "with_build_information",
			opts:                      []Option{WithBuildInformation("1.0.0", "2018-01-02T00:00:00Z")},
			wantListenerCheck:         checkDefaultListener,
			wantGathererCheck:         checkDefaultGatherer,
			wantRegistererCheck:       checkDefaultRegisterer,
			wantbuildInfoGaugeLabels:  prometheus.Labels{"built": "2018-01-02T00:00:00Z", "version": "1.0.0"},
			wantVersion:               "1.0.0",
			wantMetricsHandlerPattern: "/metrics",
			wantServeMux:              http.NewServeMux(),
		},
		{
			name:                      "with_build_extra_labels",
			opts:                      []Option{WithBuildExtraLabels(map[string]string{"git_version": "2.0.0"})},
			wantListenerCheck:         checkDefaultListener,
			wantGathererCheck:         checkDefaultGatherer,
			wantRegistererCheck:       checkDefaultRegisterer,
			wantbuildInfoGaugeLabels:  prometheus.Labels{"git_version": "2.0.0"},
			wantMetricsHandlerPattern: "/metrics",
			wantServeMux:              http.NewServeMux(),
		},
		{
			name: "with_build_information_and_extra_labels",
			opts: []Option{
				WithBuildInformation("1.0.0", "2018-01-02T00:00:00Z"),
				WithBuildExtraLabels(map[string]string{"git_version": "2.0.0"}),
			},
			wantListenerCheck:   checkDefaultListener,
			wantGathererCheck:   checkDefaultGatherer,
			wantRegistererCheck: checkDefaultRegisterer,
			wantbuildInfoGaugeLabels: prometheus.Labels{
				"built":       "2018-01-02T00:00:00Z",
				"version":     "1.0.0",
				"git_version": "2.0.0",
			},
			wantVersion:               "1.0.0",
			wantMetricsHandlerPattern: "/metrics",
			wantServeMux:              http.NewServeMux(),
		},
		{
			name: "combined",
			opts: []Option{
				WithListenerAddress(":0"),
				WithBuildInformation("1.0.0", "2018-01-02T00:00:00Z"),
				WithBuildExtraLabels(map[string]string{"git_version": "2.0.0"}),
			},
			wantListenerCheck:   isTCPListener,
			wantGathererCheck:   checkDefaultGatherer,
			wantRegistererCheck: checkDefaultRegisterer,
			wantbuildInfoGaugeLabels: prometheus.Labels{
				"built":       "2018-01-02T00:00:00Z",
				"version":     "1.0.0",
				"git_version": "2.0.0",
			},
			wantVersion:               "1.0.0",
			wantMetricsHandlerPattern: "/metrics",
			wantServeMux:              http.NewServeMux(),
		},
		{
			name:                            "without continuous profiling",
			opts:                            []Option{WithoutContinuousProfiling()},
			wantListenerCheck:               checkDefaultListener,
			wantGathererCheck:               checkDefaultGatherer,
			wantRegistererCheck:             checkDefaultRegisterer,
			wantContinuousProfilingDisabled: true,
			wantMetricsHandlerPattern:       "/metrics",
			wantServeMux:                    http.NewServeMux(),
		},
		{
			name:                      "without metrics",
			opts:                      []Option{WithoutMetrics()},
			wantListenerCheck:         checkDefaultListener,
			wantGathererCheck:         checkDefaultGatherer,
			wantRegistererCheck:       checkDefaultRegisterer,
			wantMetricsDisabled:       true,
			wantMetricsHandlerPattern: "/metrics",
			wantServeMux:              http.NewServeMux(),
		},
		{
			name:                      "without pprof",
			opts:                      []Option{WithoutPprof()},
			wantListenerCheck:         checkDefaultListener,
			wantGathererCheck:         checkDefaultGatherer,
			wantRegistererCheck:       checkDefaultRegisterer,
			wantPprofDisabled:         true,
			wantMetricsHandlerPattern: "/metrics",
			wantServeMux:              http.NewServeMux(),
		},
		{
			name:                      "with custom metrics handler pattern",
			opts:                      []Option{WithMetricsHandlerPattern("/foo")},
			wantListenerCheck:         checkDefaultListener,
			wantGathererCheck:         checkDefaultGatherer,
			wantRegistererCheck:       checkDefaultRegisterer,
			wantMetricsHandlerPattern: "/foo",
			wantServeMux:              http.NewServeMux(),
		},
		{
			name:                        "with profiler credentials file",
			opts:                        []Option{WithProfilerCredentialsFile("/path/to/credentials.json")},
			wantListenerCheck:           checkDefaultListener,
			wantGathererCheck:           checkDefaultGatherer,
			wantRegistererCheck:         checkDefaultRegisterer,
			wantMetricsHandlerPattern:   "/metrics",
			wantProfilerCredentialsFile: "/path/to/credentials.json",
			wantServeMux:                http.NewServeMux(),
		},
		{
			name:                      "with custom gatherer",
			opts:                      []Option{WithPrometheusGatherer(customGatherer{})},
			wantListenerCheck:         checkDefaultListener,
			wantGathererCheck:         checkCustomGatherer,
			wantRegistererCheck:       checkDefaultRegisterer,
			wantMetricsHandlerPattern: "/metrics",
			wantServeMux:              http.NewServeMux(),
		},
		{
			name:                      "with custom registerer",
			opts:                      []Option{WithPrometheusRegisterer(customRegisterer{})},
			wantListenerCheck:         checkDefaultListener,
			wantGathererCheck:         checkDefaultGatherer,
			wantRegistererCheck:       checkCustomRegisterer,
			wantMetricsHandlerPattern: "/metrics",
			wantServeMux:              http.NewServeMux(),
		},
		{
			name:                      "with custom mux",
			opts:                      []Option{WithServeMux(testMux)},
			wantListenerCheck:         checkDefaultListener,
			wantGathererCheck:         checkDefaultGatherer,
			wantRegistererCheck:       checkDefaultRegisterer,
			wantMetricsHandlerPattern: "/metrics",
			wantServeMux:              testMux,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := applyOptions(tt.opts)
			tt.wantListenerCheck(t, got.listenerFactory)
			tt.wantGathererCheck(t, got.gatherer)
			tt.wantRegistererCheck(t, got.registerer)
			require.Equal(t, tt.wantVersion, got.version)
			require.Equal(t, tt.wantContinuousProfilingDisabled, got.continuousProfilingDisabled)
			require.Equal(t, tt.wantMetricsDisabled, got.metricsDisabled)
			require.Equal(t, tt.wantPprofDisabled, got.pprofDisabled)
			require.Equal(t, tt.wantMetricsHandlerPattern, got.metricsHandlerPattern)
			require.Equal(t, tt.wantProfilerCredentialsFile, got.profilerCredentialsFile)
			require.Equal(t, tt.wantServeMux, got.serveMux)

			if tt.wantbuildInfoGaugeLabels == nil {
				require.Equal(t, prometheus.Labels{}, got.buildInfoGaugeLabels)
			} else {
				require.Equal(t, tt.wantbuildInfoGaugeLabels, got.buildInfoGaugeLabels)
			}
		})
	}
}
