package middlewares

// |@@| C

import (
	"fmt"
	"gardening/src/lib/dev_tool"
	erro "gardening/src/lib/error"
	"gardening/src/lib/out_extra"
	"gardening/src/lib/route"
	"gardening/src/lib/special_views"
	"gardening/src/srv/layouts"
	"github.com/gin-gonic/gin"
	"gitlab.com/ccyrillee/kitcla/goc"
	"net/http"
	"runtime"
	"strings"
	"time"
)

type RecoveryMiddleware struct {
	EmptyLayout *layouts.EmptyLayout
	ErrorView   *special_views.ErrorView
	AppRoute    *route.AppRoute
	DevTool     *dev_tool.DevTool
}

func (this *RecoveryMiddleware) Act() gin.HandlerFunc {
	return gin.CustomRecovery(this.GlobalErrorRoute)
}

func (this *RecoveryMiddleware) GlobalErrorRoute(c *gin.Context, recovered any) {
	err1, ok := recovered.(error)
	if !ok {
		c.AbortWithStatus(http.StatusInternalServerError)
		return
	}
	err2, ok := recovered.(*erro.Error)

	// Capture debug information
	startTime := c.GetTime("start_time")
	errorTime := time.Now()

	mod := &special_views.ErrorViewMod{
		Ctx:             c,
		Err1:            err1,
		RequestMethod:   c.Request.Method,
		RequestURL:      c.Request.URL.String(),
		UserAgent:       c.GetHeader("User-Agent"),
		RequestID:       this.generateRequestID(c),
		ErrorTime:       errorTime.Format("2006-01-02 15:04:05.000"),
		RequestDuration: this.calculateDuration(startTime, errorTime),
		Headers:         this.extractHeaders(c),
		QueryParams:     this.extractQueryParams(c),
		FormData:        this.extractFormData(c),
		RoutePath:       c.FullPath(),
		RoutePattern:    c.FullPath(),
		HandlerName:     c.HandlerName(),
		RouteParams:     this.extractRouteParams(c),
	}

	this.extractMaeInfo(c, mod)
	this.extractStackTraceInfo(mod)

	if ok {
		mod.Err2 = err2
	}

	extra := &out_extra.OutExtra{}
	h := this.EmptyLayout.H(this.ErrorView.H(mod))
	extra.Messages = this.AppRoute.CombineMessages(c, extra.Messages)
	s := goc.RenderRoot(h)
	s = "<!DOCTYPE html>\n" + s
	c.Data(http.StatusInternalServerError, "text/html; charset=utf-8", []byte(s))
}

func (this *RecoveryMiddleware) generateRequestID(c *gin.Context) string {
	if id := c.GetHeader("X-Request-ID"); id != "" {
		return id
	}
	return fmt.Sprintf("req_%d", time.Now().UnixNano())
}

func (this *RecoveryMiddleware) calculateDuration(start time.Time, end time.Time) string {
	if start.IsZero() {
		return "unknown"
	}
	duration := end.Sub(start)
	return duration.String()
}

func (this *RecoveryMiddleware) extractHeaders(c *gin.Context) map[string]string {
	headers := make(map[string]string)
	for key, values := range c.Request.Header {
		if len(values) > 0 {
			// Skip sensitive headers
			lowerKey := strings.ToLower(key)
			if strings.Contains(lowerKey, "authorization") || strings.Contains(lowerKey, "cookie") {
				headers[key] = "[REDACTED]"
			} else {
				headers[key] = values[0]
			}
		}
	}
	return headers
}

func (this *RecoveryMiddleware) extractQueryParams(c *gin.Context) map[string]string {
	params := make(map[string]string)
	for key, values := range c.Request.URL.Query() {
		if len(values) > 0 {
			params[key] = values[0]
		}
	}
	return params
}

func (this *RecoveryMiddleware) extractRouteParams(c *gin.Context) map[string]string {
	params := make(map[string]string)
	for _, param := range c.Params {
		params[param.Key] = param.Value
	}
	return params
}

func (this *RecoveryMiddleware) extractFormData(c *gin.Context) map[string]string {
	formData := make(map[string]string)
	c.Request.ParseForm()
	for key, values := range c.Request.PostForm {
		if len(values) > 0 {
			formData[key] = values[0]
		}
	}
	return formData
}

func (this *RecoveryMiddleware) extractMaeInfo(c *gin.Context, mod *special_views.ErrorViewMod) {
	if this.DevTool != nil {
		if record := this.DevTool.GetCurrentRecord(c); record != nil && record.RouteInfo != nil {
			if record.RouteInfo.Mae != "" {
				mod.MaeName = record.RouteInfo.Mae
			} else if record.RouteInfo.MaeIn != "" {
				mod.MaeName = strings.TrimSuffix(record.RouteInfo.MaeIn, "In")
			}
		}
	}
}

func (this *RecoveryMiddleware) extractStackTraceInfo(mod *special_views.ErrorViewMod) {
	var middlewareMatched []string
	var middlewarePaths []string

	for i := 0; i < 50; i++ {
		_, file, _, ok := runtime.Caller(i)
		if !ok {
			break
		}

		if strings.Contains(file, "src/mae/") && strings.HasSuffix(file, ".go") {
			if mod.MaeMatched == "" {
				mod.MaeMatched = this.extractFileName(file)
				mod.MaeMatchedPath = file
			}
		}

		if strings.Contains(file, "src/app/routing/") && strings.HasSuffix(file, ".go") {
			if mod.RouteMatched == "" {
				mod.RouteMatched = this.extractFileName(file)
				mod.RouteMatchedPath = file
			}
		}

		if strings.Contains(file, "src/lib/") && strings.Contains(file, "_middleware.go") {
			middlewareName := this.extractFileName(file)
			if !this.contains(middlewareMatched, middlewareName) {
				middlewareMatched = append(middlewareMatched, middlewareName)
				middlewarePaths = append(middlewarePaths, file)
			}
		}
	}

	mod.MiddlewareMatched = middlewareMatched
	mod.MiddlewarePaths = middlewarePaths
}

func (this *RecoveryMiddleware) extractFileName(filePath string) string {
	parts := strings.Split(filePath, "/")
	if len(parts) > 0 {
		fileName := parts[len(parts)-1]
		return strings.TrimSuffix(fileName, ".go")
	}
	return filePath
}

func (this *RecoveryMiddleware) contains(slice []string, item string) bool {
	for _, s := range slice {
		if s == item {
			return true
		}
	}
	return false
}
