package api

import (
	"net/http"
	"runtime/debug"

	sentrygin "github.com/getsentry/sentry-go/gin"
	"github.com/gin-gonic/gin"
	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	httpmiddleware "github.com/tidbcloud/pkgv2/http/middleware"
	"github.com/tidbcloud/pkgv2/log"
	"github.com/tidbcloud/pkgv2/logid"
	ginprometheus "github.com/zsais/go-gin-prometheus"
	"go.uber.org/zap"
)

func InitRouter() *gin.Engine {
	r := gin.Default()
	addMiddlewares(r)

	p := ginprometheus.NewPrometheus("gin")
	p.Use(r)

	r.GET("/ping", Ping)    // for health check in k8s
	r.GET("/healthz", Ping) // for health check in k8s
	r.POST("/api/useralerts/v1/alert", FireAlert)

	return r
}

func addMiddlewares(engine *gin.Engine) *gin.Engine {
	engine.Use(
		logid.InjectLogid4GinMW,
		gin.CustomRecovery(func(c *gin.Context, e interface{}) {
			logger := log.WithContextL(c.Request.Context())
			if err, ok := e.(error); ok && errors.HasStack(err) {
				logger.Sugar().Warnf("panic, err: %s, stack: %s", err, errors.GetStackTracer(err).StackTrace())
			} else {
				logger.Sugar().Warnf("panic, err: %s, stack: %s", e, debug.Stack())
			}
			c.AbortWithStatus(http.StatusInternalServerError)
		}),
		httpmiddleware.TraceRequest,
		// repanic, let it catch by gin.CustomRecovery
		sentrygin.New(sentrygin.Options{Repanic: true}),
	)
	return nil
}

func Success(c *gin.Context, result interface{}, code int) {
	if result == nil {
		result = gin.H{}
	}

	c.JSON(code, result)
}

func Fail(c *gin.Context, err error, code int) {
	msg := ""
	ctx := c.Request.Context()

	log.Info(ctx, "got error", zap.Error(err), zap.Int("code", code))
	if e, ok := err.(errcode.ErrorCode); ok {
		if code := e.Code().HTTPCode(); code >= 400 && code < 500 {
			msg = e.Error()

			c.JSON(code, gin.H{"code": code, "message": msg})
			return
		}
	}

	if code >= 500 {
		log.Info(ctx, "internal server error", zap.Error(err))
		msg = "internal server error"
	} else {
		if msg == "" && err != nil {
			msg = err.Error()
		}
	}

	c.JSON(code, gin.H{"code": code, "message": msg})
}

func Ping(c *gin.Context) {
	shouldFail := c.Query("shouldFail")
	if shouldFail != "" {
		Fail(c, nil, http.StatusBadRequest)
		return
	}

	Success(c, "pong", http.StatusOK)
}
