package tigin

import (
	"net/http"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/pkgv2/bizerr"
	"go.opentelemetry.io/otel/trace"
	"go.uber.org/zap"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
)

var (
	reqCount = promauto.NewCounterVec(prometheus.CounterOpts{
		Namespace: "tiinf",
		Subsystem: "gin",
		Name:      "request_total",
	}, []string{"code", "method", "handler", "host", "url"})
	reqDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
		Namespace: "tiinf",
		Subsystem: "gin",
		Name:      "request_duration_seconds",
	}, []string{"method", "host", "url"})
)

// The TraceRequest middleware record the start and end time of the request
// the log contains trace_id by default
func TraceRequest(c *gin.Context) {
	ctx := c.Request.Context()
	logger := tilog.WithContextL(ctx).With(
		zap.String("path", c.Request.URL.Path),
		zap.String("method", c.Request.Method),
	)

	start := time.Now()
	logger.Info("begin")
	c.Next()
	cost := time.Since(start)
	logger.Info("end", zap.String("cost", cost.String()), zap.Int("statusCode", c.Writer.Status()))
}

func RecordMetrics(c *gin.Context) {
	start := time.Now()
	c.Next()
	status := strconv.Itoa(c.Writer.Status())
	elapsed := float64(time.Since(start)) / float64(time.Second)
	reqCount.WithLabelValues(status, c.Request.Method, c.HandlerName(), c.Request.Host, c.FullPath()).Inc()
	reqDuration.WithLabelValues(c.Request.Method, c.Request.Host, c.FullPath()).Observe(elapsed)
}

// The SetResponseTraceID middleware add a trace_id debug header to the response
func SetResponseTraceID(c *gin.Context) {
	spanCtx := trace.SpanContextFromContext(c.Request.Context())
	if spanCtx.HasTraceID() {
		c.Header("x-debug-trace-id", spanCtx.TraceID().String())
	}
	c.Next()
}

func AbortWithError(c *gin.Context, err error) {
	if te, ok := errs.AsTiError(err); ok {
		c.JSON(te.HTTPCode(), gin.H{"code": te.BizCode(), "message": te.ErrMsg()})
	} else if be, ok := err.(bizerr.BizError); ok {
		c.JSON(te.HTTPCode(), gin.H{"code": be.ErrCode(), "message": be.ErrMsg()})
	} else {
		c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "Internal Server Error"})
	}
	c.Abort()
}
