// Package server provides router-level middlewares.
package server

import (
	"time"

	"github.com/gin-gonic/gin"
	myerr "github.com/pingcap-inc/clinic/clinic_data_service/pkg/errors"
	"github.com/pingcap-inc/clinic/clinic_data_service/pkg/log"
	clinictracing "github.com/pingcap-inc/clinic/clinic_data_service/pkg/tracing"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/propagation"
	"go.opentelemetry.io/otel/trace"
	"go.uber.org/zap"
)

// RegisterMiddlewares attaches recovery and unified error handling middlewares.
//
// Design:
// 1. CustomRecovery handles ALL panics and guarantees process safety.
// 2. Error middleware only handles normal errors (c.Error), never panics.
// 3. Never writes response twice.
// 4. Never overrides handler-written responses.
func RegisterMiddlewares(r *gin.Engine) {
	// Entry points embedded outside cmd/api still need to honor Kong's trace.
	clinictracing.ConfigurePropagation()

	// ---- 1. Panic recovery (MUST be first) ----
	r.Use(gin.CustomRecovery(func(c *gin.Context, recovered any) {
		ctx := c.Request.Context()

		// log full panic info
		log.Error(ctx, "panic recovered: %v", zap.Any("recovered", recovered))

		// if response already written, just abort
		if c.Writer.Written() {
			c.Abort()
			return
		}

		c.AbortWithStatusJSON(500, myerr.APIError{
			Code:    "Internal",
			Message: "internal server error",
			Details: "panic occurred",
		})
	}))

	// ---- 1.5. OpenTelemetry tracing middleware ----
	r.Use(func(c *gin.Context) {
		parent := otel.GetTextMapPropagator().Extract(c.Request.Context(), propagation.HeaderCarrier(c.Request.Header))
		tracer := otel.Tracer("slowquery-api")
		spanName := c.FullPath()
		if spanName == "" {
			spanName = c.Request.Method + " " + c.Request.URL.Path
		}
		ctx, span := tracer.Start(parent, spanName,
			trace.WithAttributes(
				attribute.String("http.method", c.Request.Method),
				attribute.String("http.route", c.FullPath()),
				attribute.String("http.target", c.Request.URL.Path),
				attribute.String("http.client_ip", c.ClientIP()),
			),
		)
		c.Request = c.Request.WithContext(ctx)
		traceID := span.SpanContext().TraceID().String()
		c.Writer.Header().Set("X-Trace-ID", traceID)
		start := time.Now()
		c.Next()
		latency := time.Since(start)
		span.SetAttributes(
			attribute.Int("http.status_code", c.Writer.Status()),
			attribute.Int64("http.response_size", int64(c.Writer.Size())),
			attribute.String("http.user_agent", c.Request.UserAgent()),
			attribute.String("http.trace_id", traceID),
			attribute.String("http.latency", latency.String()),
		)
		span.End()
	})

	// ---- 2. Unified error handling middleware ----
	r.Use(func(c *gin.Context) {
		start := time.Now()
		c.Next()

		// placeholder for timing / metrics
		_ = start

		// if handler already wrote response, do nothing
		if c.Writer.Written() {
			return
		}

		// no errors recorded
		if len(c.Errors) == 0 {
			return
		}

		// handle the last error
		e := c.Errors.Last()

		c.AbortWithStatusJSON(500, myerr.APIError{
			Code:    "Internal",
			Message: "internal error",
			Details: e.Error(),
		})
	})
}
