package middleware

import (
	"fmt"
	"net/http"

	"github.com/getsentry/sentry-go"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
)

// SentryRecover middleware pour capturer les panics et les erreurs avec Sentry
func SentryRecover(logger *zap.Logger) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				// Capturer le panic dans Sentry
				hub := sentry.CurrentHub().Clone()
				hub.Scope().SetTag("component", "gin")
				hub.Scope().SetContext("request", map[string]interface{}{
					"method": c.Request.Method,
					"path":   c.Request.URL.Path,
					"query":  c.Request.URL.RawQuery,
					"ip":     c.ClientIP(),
				})

				// Récupérer le request ID si présent
				if requestID, exists := c.Get("request_id"); exists {
					hub.Scope().SetTag("request_id", requestID.(string))
				}

				// Récupérer l'user ID si présent
				if userID, exists := c.Get("user_id"); exists {
					hub.Scope().SetUser(sentry.User{
						ID:       toString(userID),
						Username: toString(userID),
					})
				}

				// Capturer l'erreur
				if errObj, ok := err.(error); ok {
					hub.CaptureException(errObj)
				} else {
					hub.CaptureMessage(fmt.Sprintf("Panic: %v", err))
				}

				// Logger l'erreur localement aussi
				if logger != nil {
					logger.Error("Panic recovered",
						zap.Any("error", err),
						zap.String("method", c.Request.Method),
						zap.String("path", c.Request.URL.Path),
						zap.String("ip", c.ClientIP()),
					)
				}

				// Répondre avec une erreur générique
				c.JSON(http.StatusInternalServerError, gin.H{
					"error":   "internal server error",
					"message": "An unexpected error occurred",
				})
				c.Abort()
			}
		}()

		c.Next()

		// Capturer les erreurs HTTP 5xx
		if c.Writer.Status() >= 500 {
			hub := sentry.CurrentHub().Clone()
			hub.Scope().SetTag("component", "gin")
			hub.Scope().SetTag("status_code", toString(c.Writer.Status()))
			hub.Scope().SetContext("request", map[string]interface{}{
				"method": c.Request.Method,
				"path":   c.Request.URL.Path,
				"status": c.Writer.Status(),
			})

			// Récupérer les erreurs du contexte Gin
			if len(c.Errors) > 0 {
				for _, err := range c.Errors {
					hub.CaptureException(err)
				}
			} else {
				// Créer une erreur générique pour les 5xx sans erreur explicite
				hub.CaptureMessage("HTTP 5xx error without explicit error")
			}
		}
	}
}

// toString convertit une valeur en string de manière sûre
func toString(v interface{}) string {
	if v == nil {
		return ""
	}
	if s, ok := v.(string); ok {
		return s
	}
	return ""
}
