package prom

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gorilla/mux"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/urfave/negroni"
)

func init() {
	prometheus.MustRegister(serverDuration)
	prometheus.MustRegister(serverRequests)
	prometheus.MustRegister(serverRequestsTotal)
	prometheus.MustRegister(serverResponses4xx)
	prometheus.MustRegister(serverResponses5xx)
}

var (
	serverRequests = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: "rest",
			Subsystem: "server",
			Name:      "requests",
			Help:      "Number of currently processing RESTful requests on server side.",
		},
		[]string{"method", "uri"},
	)

	serverRequestsTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: "rest",
			Subsystem: "server",
			Name:      "requests_total",
			Help:      "Total number of RESTful requests on server side.",
		},
		[]string{"method", "uri"},
	)
	serverResponses4xx = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: "rest",
			Subsystem: "server",
			Name:      "responses_4xx",
			Help:      "Total number of RESTful 4xx response on server side.",
		},
		[]string{"method", "uri", "status"},
	)
	serverResponses5xx = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: "rest",
			Subsystem: "server",
			Name:      "responses_5xx",
			Help:      "Total number of RESTful 5xx response on server side.",
		},
		[]string{"method", "uri", "status"},
	)
	serverDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: "rest",
			Subsystem: "server",
			Name:      "request_duration_seconds",
			Help:      "The RESTful request latencies in milliseconds on server side.",
		},
		[]string{"method", "uri", "status"},
	)
)

func MetricsMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		method := request.Method
		path := request.URL.Path
		// we need to wrap the native response writer to track status code.
		response := negroni.NewResponseWriter(writer)
		defer func(begin time.Time) {
			status := fmt.Sprintf("%d", response.Status())

			serverRequests.WithLabelValues(method, path).Dec()

			timeElapsed := float64(time.Since(begin)) / float64(time.Millisecond)
			serverDuration.WithLabelValues(method, path, status).Observe(timeElapsed)

			if response.Status() >= http.StatusBadRequest {
				serverResponses4xx.WithLabelValues(method, path, status).Inc()
			}

			if response.Status() >= http.StatusInternalServerError {
				serverResponses5xx.WithLabelValues(method, path, status).Inc()
			}

		}(time.Now())
		serverRequests.WithLabelValues(method, path).Inc()
		serverRequestsTotal.WithLabelValues(method, path).Inc()

		next.ServeHTTP(response, request)
	})

}

const (
	// DefaultMetricsPath url path of metrics
	DefaultMetricsPath = "/metrics"
)

func getPath(pathOptions ...string) string {
	path := DefaultMetricsPath
	if len(pathOptions) > 0 {
		path = pathOptions[0]
	}
	return path
}

func RegisterOnMux(router *mux.Router, pathOptions ...string) {
	path := getPath(pathOptions...)
	router.Handle(path, promhttp.Handler())
}
