package middleware

import (
	"errors"
	"log/slog"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"runtime/debug"
	"strings"

	"github.com/gin-gonic/gin"
)

func Recovery(logger *slog.Logger) gin.HandlerFunc {
	return func(c *gin.Context) {
		defer func() {
			panicValue := recover()
			if panicValue == nil {
				return
			}
			brokenPipe := isBrokenPipe(panicValue)
			request := recoveryRequestDump(c.Request)
			if logger != nil {
				logger.ErrorContext(c.Request.Context(), "recovery from panic", "mod", "http", "error", panicValue, "request", request, "stack", string(debug.Stack()))
			}
			if brokenPipe {
				if err, ok := panicValue.(error); ok {
					_ = c.Error(err)
				}
				c.Abort()
				return
			}
			c.AbortWithStatus(http.StatusInternalServerError)
		}()
		c.Next()
	}
}

func isBrokenPipe(value any) bool {
	var networkError *net.OpError
	if !errors.As(asError(value), &networkError) {
		return false
	}
	var syscallError *os.SyscallError
	if !errors.As(networkError, &syscallError) {
		return false
	}
	message := strings.ToLower(syscallError.Error())
	return strings.Contains(message, "broken pipe") || strings.Contains(message, "connection reset by peer")
}

func asError(value any) error {
	err, _ := value.(error)
	return err
}

func recoveryRequestDump(request *http.Request) string {
	if request == nil {
		return ""
	}
	clone := request.Clone(request.Context())
	clone.Header = request.Header.Clone()
	for key := range clone.Header {
		switch strings.ToLower(key) {
		case "authorization", "cookie", "set-cookie", "x-token", "proxy-authorization":
			clone.Header[key] = []string{"***"}
		}
	}
	if request.URL != nil {
		clone.URL = new(url.URL)
		*clone.URL = *request.URL
		clone.URL.RawQuery = redactQuery(request.URL.RawQuery)
		clone.RequestURI = clone.URL.RequestURI()
	}
	dumped, err := httputil.DumpRequest(clone, false)
	if err != nil {
		return clone.Method + " " + clone.RequestURI
	}
	return string(dumped)
}
