package metrics

import (
	"strconv"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
)

const serviceName = "cp-service"

var (
	labels = prometheus.Labels{"service": serviceName}

	KafkaConsumeSuccessCounter = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Name:        prometheus.BuildFQName("cp", "kafka", "consume_success"),
			Help:        "记录kafka消息消费成功的次数",
			ConstLabels: labels,
		}, []string{"topic", "bid"},
	)
	KafkaConsumeErrorCounter = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Name:        prometheus.BuildFQName("cp", "kafka", "consume_error"),
			Help:        "记录kafka消息消费失败的次数",
			ConstLabels: labels,
		}, []string{"topic", "bid"},
	)
	KafkaConsumeProcessingCounter = promauto.NewGaugeVec(prometheus.GaugeOpts{
		Name:        prometheus.BuildFQName("cp", "kafka", "consume_processing"),
		Help:        "当前正在处理的kafka消息个数",
		ConstLabels: labels,
	}, []string{"topic", "bid"},
	)

	requestInProgressTotal = promauto.NewGaugeVec(prometheus.GaugeOpts{
		Name:        prometheus.BuildFQName("cp", "http", "requests_in_progress_total"),
		Help:        "All the requests in progress",
		ConstLabels: labels,
	}, []string{"method"})

	requestsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
		Name:        prometheus.BuildFQName("cp", "http", "requests_total"),
		Help:        "Count all http requests by status code, method and path.",
		ConstLabels: labels,
	},
		[]string{"status_code", "method", "path"},
	)
	requestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
		Name:        prometheus.BuildFQName("cp", "http", "request_duration_seconds"),
		Help:        "Duration of all HTTP requests by status code, method and path.",
		ConstLabels: labels,
		Buckets: []float64{
			0.000000001, // 1ns
			0.000000002,
			0.000000005,
			0.00000001, // 10ns
			0.00000002,
			0.00000005,
			0.0000001, // 100ns
			0.0000002,
			0.0000005,
			0.000001, // 1µs
			0.000002,
			0.000005,
			0.00001, // 10µs
			0.00002,
			0.00005,
			0.0001, // 100µs
			0.0002,
			0.0005,
			0.001, // 1ms
			0.002,
			0.005,
			0.01, // 10ms
			0.02,
			0.05,
			0.1, // 100 ms
			0.2,
			0.5,
			1.0, // 1s
			2.0,
			5.0,
			10.0, // 10s
			15.0,
			20.0,
			30.0,
		},
	},
		[]string{"status_code", "method", "path"},
	)
)

func NewMiddleware(cfgs ...Config) fiber.Handler {
	cfg := configDefault(cfgs...)
	return func(ctx *fiber.Ctx) error {
		if cfg.Next != nil && cfg.Next(ctx) {
			return ctx.Next()
		}
		start := time.Now()
		method := ctx.Route().Method

		if ctx.Route().Path == "/metrics" {
			return ctx.Next()
		}

		requestInProgressTotal.WithLabelValues(method).Inc()
		defer requestInProgressTotal.WithLabelValues(method).Dec()

		err := ctx.Next()
		// initialize with default error code
		// https://docs.gofiber.io/guide/error-handling
		status := fiber.StatusInternalServerError
		if err != nil {
			if e, ok := err.(*fiber.Error); ok {
				// Get correct error code from fiber.Error type
				status = e.Code
			}
		} else {
			status = ctx.Response().StatusCode()
		}

		path := ctx.Route().Path

		statusCode := strconv.Itoa(status)
		requestsTotal.WithLabelValues(statusCode, method, path).Inc()

		elapsed := float64(time.Since(start).Nanoseconds()) / 1e9
		requestDuration.WithLabelValues(statusCode, method, path).Observe(elapsed)

		return err
	}
}