package rushlink

import (
	"log"
	"net/http"
	"time"

	"gitea.hashru.nl/dsprenkels/rushlink/internal/db"
	"github.com/gorilla/mux"
	"github.com/pkg/errors"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

const metricNamespace = "rushlink"

// metricURLsTotalGauge counts the number of requests that are handled by
// the application, partitioned by status code and HTTP method.
//
// This counter is updated by the router.
var metricRequestsTotalCounter = prometheus.NewCounterVec(
	prometheus.CounterOpts{
		Namespace: metricNamespace,
		Subsystem: "http",
		Name:      "requests_total",
		Help:      "How many HTTP requests processed, partitioned by status code and HTTP method.",
	}, []string{"code", "method"})

// metricRequestsLatencyNanoSeconds keeps track of the request latencies for
// each http request.
//
// This historogram is updated by the router.
var metricRequestsLatencyNanoSeconds = prometheus.NewHistogramVec(
	prometheus.HistogramOpts{
		Namespace: metricNamespace,
		Subsystem: "http",
		Name:      "request_duration_seconds",
		Buckets: []float64{
			float64(500e-6),
			float64(1e-3),
			float64(2e-3),
			float64(5e-3),
			float64(10e-3),
			float64(20e-3),
			float64(50e-3),
		},
		Help: "The latency of each HTTP request, partitioned by status code and HTTP method.",
	}, []string{"code", "method"})

// metricURLsTotalGauge measures the amount of pastes stored in the database,
// partitioned by type and state.
//
// Its values are computed on the fly by updateMetrics().
var metricURLsTotalGauge = prometheus.NewGaugeVec(
	prometheus.GaugeOpts{
		Namespace: metricNamespace,
		Subsystem: "pastes",
		Name:      "urls_total",
		Help:      "The current amount of pastes in the database, partitioned by state and type.",
	}, []string{"state", "type"})

// StartMetricsServer starts sering Prometheus metrics exports on addr
func StartMetricsServer(addr string, database *db.Database, fs *db.FileStore) {
	prometheus.MustRegister(metricRequestsTotalCounter)
	prometheus.MustRegister(metricRequestsLatencyNanoSeconds)
	prometheus.MustRegister(metricURLsTotalGauge)

	router := mux.NewRouter()
	router.Handle("/metrics", &MetricsHandler{database}).Methods("GET")
	srv := &http.Server{
		Handler:      router,
		Addr:         addr,
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}
	log.Fatal(srv.ListenAndServe())
}

type MetricsHandler struct {
	db *db.Database
}

func (mh *MetricsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	mh.updateMetrics()
	promhttp.Handler().ServeHTTP(w, r)
}

func (mh *MetricsHandler) updateMetrics() {
	// Update metricURLsTotalGauge
	results := make([](struct {
		Type  db.PasteType
		State db.PasteState
		Count float64
	}), 0)
	query := mh.db.Unscoped().Model(&db.Paste{}).Select("type", "state", "COUNT(*) as count").Group("type, state").Find(&results)
	if err := query.Error; err != nil {
		log.Printf("error: %v", errors.Wrap(err, "fetching pastes_total metric"))
		return
	}
	metricURLsTotalGauge.Reset()
	for _, r := range results {
		labels := map[string]string{
			"type":  r.Type.String(),
			"state": r.State.String(),
		}
		metricURLsTotalGauge.With(labels).Set(r.Count)
	}
}