package ginext

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/rs/zerolog/log"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"gogs.mikescher.com/BlackForestBytes/goext/mathext"
	"net"
	"net/http"
	"strings"
	"time"
)

type GinWrapper struct {
	engine          *gin.Engine
	SuppressGinLogs bool

	allowCors      bool
	ginDebug       bool
	bufferBody     bool
	requestTimeout time.Duration

	routeSpecs []ginRouteSpec
}

type ginRouteSpec struct {
	Method      string
	URL         string
	Middlewares []string
	Handler     string
}

// NewEngine creates a new (wrapped) ginEngine
// Parameters are:
// - [allowCors]    Add cors handler to allow all CORS requests on the default http methods
// - [ginDebug]     Set gin.debug to true (adds more logs)
// - [bufferBody]   Buffers the input body stream, this way the ginext error handler can later include the whole request body
// - [timeout]      The default handler timeout
func NewEngine(allowCors bool, ginDebug bool, bufferBody bool, timeout time.Duration) *GinWrapper {
	engine := gin.New()

	wrapper := &GinWrapper{
		engine:          engine,
		SuppressGinLogs: false,
		allowCors:       allowCors,
		ginDebug:        ginDebug,
		bufferBody:      bufferBody,
		requestTimeout:  timeout,
	}

	engine.RedirectFixedPath = false
	engine.RedirectTrailingSlash = false

	if allowCors {
		engine.Use(CorsMiddleware())
	}

	// do not debug-print routes
	gin.DebugPrintRouteFunc = func(_, _, _ string, _ int) {}

	if !ginDebug {
		gin.SetMode(gin.ReleaseMode)

		ginlogger := gin.Logger()
		engine.Use(func(context *gin.Context) {
			if !wrapper.SuppressGinLogs {
				ginlogger(context)
			}
		})
	} else {
		gin.SetMode(gin.DebugMode)
	}

	return wrapper
}

func (w *GinWrapper) ListenAndServeHTTP(addr string, postInit func(port string)) (chan error, *http.Server) {

	w.DebugPrintRoutes()

	httpserver := &http.Server{
		Addr:    addr,
		Handler: w.engine,
	}

	errChan := make(chan error)

	go func() {

		ln, err := net.Listen("tcp", httpserver.Addr)
		if err != nil {
			errChan <- err
			return
		}

		_, port, err := net.SplitHostPort(ln.Addr().String())
		if err != nil {
			errChan <- err
			return
		}

		log.Info().Str("address", httpserver.Addr).Msg("HTTP-Server started on http://localhost:" + port)

		if postInit != nil {
			postInit(port) // the net.Listener a few lines above is at this point actually already buffering requests
		}

		errChan <- httpserver.Serve(ln)
	}()

	return errChan, httpserver
}

func (w *GinWrapper) DebugPrintRoutes() {
	if !w.ginDebug {
		return
	}

	lines := make([][4]string, 0)

	pad := [4]int{0, 0, 0, 0}

	for _, spec := range w.routeSpecs {

		line := [4]string{
			spec.Method,
			spec.URL,
			strings.Join(spec.Middlewares, " -> "),
			spec.Handler,
		}

		lines = append(lines, line)

		pad[0] = mathext.Max(pad[0], len(line[0]))
		pad[1] = mathext.Max(pad[1], len(line[1]))
		pad[2] = mathext.Max(pad[2], len(line[2]))
		pad[3] = mathext.Max(pad[3], len(line[3]))
	}

	for _, line := range lines {

		fmt.Printf("Gin-Route: %s  %s  -->  %s  -->  %s\n",
			langext.StrPadRight("["+line[0]+"]", " ", pad[0]+2),
			langext.StrPadRight(line[1], " ", pad[1]),
			langext.StrPadRight(line[2], " ", pad[2]),
			langext.StrPadRight(line[3], " ", pad[3]))
	}
}