package middleware

import (
	"crypto/rand"
	"encoding/hex"
	"log/slog"
	"strconv"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
)

// CtxRequestID 是请求 ID 在 gin.Context 中的键。
const CtxRequestID = "request_id"

// ---- Prometheus 指标 ----

var (
	httpRequests = promauto.NewCounterVec(prometheus.CounterOpts{
		Name: "sundynix_http_requests_total",
		Help: "HTTP 请求总数（按方法/路由模板/状态码）。",
	}, []string{"method", "route", "status"})

	httpDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
		Name:    "sundynix_http_request_duration_seconds",
		Help:    "HTTP 请求耗时（秒）。",
		Buckets: prometheus.DefBuckets,
	}, []string{"method", "route"})

	httpInFlight = promauto.NewGauge(prometheus.GaugeOpts{
		Name: "sundynix_http_requests_in_flight",
		Help: "当前处理中的 HTTP 请求数。",
	})
)

// 访问日志走全局默认 slog（otelx.SetupSlog 安装，链路感知）：用请求 ctx 打点即自动带 trace_id。

// RequestID 为每个请求生成/透传 X-Request-ID，写入上下文与响应头，供日志关联。
func RequestID() gin.HandlerFunc {
	return func(c *gin.Context) {
		id := c.GetHeader("X-Request-ID")
		if id == "" {
			id = newRequestID()
		}
		c.Set(CtxRequestID, id)
		c.Header("X-Request-ID", id)
		c.Next()
	}
}

// Observe 记录 Prometheus 指标 + 结构化访问日志。放在中间件链较前位置。
func Observe() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		httpInFlight.Inc()
		c.Next()
		httpInFlight.Dec()

		route := c.FullPath() // 路由模板（/tasks/:id/...），避免按真实路径产生高基数
		if route == "" {
			route = "unmatched"
		}
		status := c.Writer.Status()
		dur := time.Since(start)
		method := c.Request.Method

		httpRequests.WithLabelValues(method, route, strconv.Itoa(status)).Inc()
		httpDuration.WithLabelValues(method, route).Observe(dur.Seconds())

		uid, _ := c.Get(CtxUserID)
		rid, _ := c.Get(CtxRequestID)
		slog.InfoContext(c.Request.Context(), "http",
			"request_id", rid,
			"method", method,
			"route", route,
			"path", c.Request.URL.Path,
			"status", status,
			"latency_ms", dur.Milliseconds(),
			"ip", c.ClientIP(),
			"uid", uid,
			"bytes", c.Writer.Size(),
		)
	}
}

func newRequestID() string {
	var b [8]byte
	_, _ = rand.Read(b[:])
	return hex.EncodeToString(b[:])
}
