package ginutils

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/log"
	"go.uber.org/zap"
)

func ErrorMiddleWare(c *gin.Context) {
	defer func() {
		var err error = nil
		if recover_err := recover(); recover_err != nil {
			if re, ok := recover_err.(error); ok {
				err = billing_errors.NewSystemError(re)
			} else {
				err = billing_errors.NewSystemError(fmt.Errorf("%v", err))
			}
		}
		if err == nil {
			new_err := c.Errors.Last()
			if new_err == nil {
				return
			}
			err = new_err
		}
		log.Error("gin return error", zap.String("request", c.Request.URL.Path), zap.Error(err))
		billingError := &billing_errors.BaseError{}
		if errors.As(err, billingError) {
			c.JSON(billingError.Status, gin.H{
				"error":      billingError.Error(),
				"stacktrace": fmt.Sprintf("%+v", billingError.Unwrap()),
				"code":       billingError.Code,
			})
		} else {
			c.JSON(http.StatusInternalServerError, gin.H{
				"error": err.Error(),
			})
		}
	}()
	c.Next()
}

// so ugly for conflict in golang lint and gin standard error handle.
func ReturnError(ctx *gin.Context, err error) {
	ctx.Error(err) // nolint: errcheck
}

type HandlerFuncResult interface{}

type HandlerFunc func(c *gin.Context) (HandlerFuncResult, error)

type Route gin.IRoutes

type MyRoute struct {
	Route
}

type WrapHandler struct {
	handlerFunc HandlerFunc
}

func (handler *WrapHandler) Handle(c *gin.Context) {
	res, err := handler.handlerFunc(c)
	if err != nil {
		ReturnError(c, err)
		return
	}
	res = FilterByQueryMiddlwWare(c, res)

	c.JSON(http.StatusOK, res)
}

func (r MyRoute) HandleWithError(method string, path string, handlerFunc HandlerFunc) {
	r.Handle(method, path, (&WrapHandler{handlerFunc: handlerFunc}).Handle)
}

type FilterResult map[string]json.RawMessage

func FilterByQuery(results []FilterResult, filters map[string]string) []FilterResult {
	res := []FilterResult{}
	for _, result := range results {
		filtered := false
		for filter, value := range filters {
			valueStr := string(result[filter])
			_ = json.Unmarshal(result[filter], &valueStr)

			if valueStr != value {
				filtered = true
				continue
			}
		}
		if !filtered {
			res = append(res, result)
		}
	}
	return res
}

var (
	QueryWhiteList = []string{"orderby", "sortby"}
)

func FilterByQueryMiddlwWare(c *gin.Context, res HandlerFuncResult) HandlerFuncResult {
	query := c.Request.URL.Query()
	filter := map[string]string{}
	for k, v := range query {
		if len(v) != 1 || v[0] == "" {
			continue
		}
		filter[k] = v[0]
	}

	if len(query) == 0 {
		return res
	}

	bytes, err := json.Marshal(res)
	if err != nil {
		return res
	}

	filterListRes := []FilterResult{}

	err = json.Unmarshal(bytes, &filterListRes)
	if err != nil {
		return res
	}

	result := FilterByQuery(filterListRes, filter)
	return result
}
