package monitoring

import (
	"net"
	"net/http"

	"github.com/prometheus/client_golang/prometheus"
)

type listenerFactory func() (net.Listener, error)

type optionsConfig struct {
	listenerFactory             listenerFactory
	buildInfoGaugeLabels        prometheus.Labels
	registerer                  prometheus.Registerer
	gatherer                    prometheus.Gatherer
	version                     string
	continuousProfilingDisabled bool
	metricsDisabled             bool
	pprofDisabled               bool
	metricsHandlerPattern       string
	profilerCredentialsFile     string
	serveMux                    *http.ServeMux
}

// Option is used to pass options to NewListener.
type Option func(*optionsConfig)

func defaultOptions() optionsConfig {
	return optionsConfig{
		listenerFactory:       defaultListenerFactory,
		buildInfoGaugeLabels:  prometheus.Labels{},
		registerer:            prometheus.DefaultRegisterer,
		gatherer:              prometheus.DefaultGatherer,
		metricsHandlerPattern: "/metrics",
		serveMux:              http.NewServeMux(),
	}
}

func applyOptions(opts []Option) optionsConfig {
	config := defaultOptions()

	for _, v := range opts {
		v(&config)
	}

	return config
}

// WithListener will configure the health check endpoint to use the provided listener.
func WithListener(listener net.Listener) Option {
	return func(config *optionsConfig) {
		config.listenerFactory = func() (net.Listener, error) {
			return listener, nil
		}
	}
}

// WithListenerAddress will configure the health check endpoint to use the provided listener.
func WithListenerAddress(addr string) Option {
	return func(config *optionsConfig) {
		config.listenerFactory = func() (net.Listener, error) {
			return net.Listen("tcp", addr)
		}
	}
}

// WithBuildInformation will configure the `gitlab_build_info` metric with appropriate labels.
func WithBuildInformation(version string, buildTime string) Option {
	return func(config *optionsConfig) {
		config.buildInfoGaugeLabels[buildInfoVersionLabel] = version
		config.buildInfoGaugeLabels[buildInfoBuildTimeLabel] = buildTime
		config.version = version
	}
}

// WithBuildExtraLabels will configure extra labels on the `gitlab_build_info` metric.
func WithBuildExtraLabels(labels map[string]string) Option {
	return func(config *optionsConfig) {
		for key, v := range labels {
			config.buildInfoGaugeLabels[key] = v
		}
	}
}

// WithMetricsHandlerPattern configures the pattern that the metrics handler is registered for (defaults to `/metrics`).
func WithMetricsHandlerPattern(pattern string) Option {
	return func(config *optionsConfig) {
		config.metricsHandlerPattern = pattern
	}
}

// WithoutContinuousProfiling disables the continuous profiler.
func WithoutContinuousProfiling() Option {
	return func(config *optionsConfig) {
		config.continuousProfilingDisabled = true
	}
}

// WithoutMetrics disables the metrics endpoint.
func WithoutMetrics() Option {
	return func(config *optionsConfig) {
		config.metricsDisabled = true
	}
}

// WithoutPprof disables the pprof endpoint.
func WithoutPprof() Option {
	return func(config *optionsConfig) {
		config.pprofDisabled = true
	}
}

// WithProfilerCredentialsFile configures the credentials file to be used for the profiler service.
func WithProfilerCredentialsFile(path string) Option {
	return func(config *optionsConfig) {
		config.profilerCredentialsFile = path
	}
}

// WithPrometheusGatherer sets the prometheus.Gatherer to expose in the metrics endpoint.
func WithPrometheusGatherer(gatherer prometheus.Gatherer) Option {
	return func(config *optionsConfig) {
		config.gatherer = gatherer
	}
}

// WithPrometheusRegisterer sets the prometheus.Registerer to use to register metrics from this package.
func WithPrometheusRegisterer(registerer prometheus.Registerer) Option {
	return func(config *optionsConfig) {
		config.registerer = registerer
	}
}

// WithServeMux will configure the health check endpoint to use the provided http.ServeMux.
func WithServeMux(mux *http.ServeMux) Option {
	return func(config *optionsConfig) {
		config.serveMux = mux
	}
}

func defaultListenerFactory() (net.Listener, error) {
	return nil, nil
}
