/******************************************************************************
 * Copyright (c)  2021 PingCAP, Inc.                                          *
 * Licensed under the Apache License, Version 2.0 (the "License");            *
 * you may not use this file except in compliance with the License.           *
 * You may obtain a copy of the License at                                    *
 *                                                                            *
 * http://www.apache.org/licenses/LICENSE-2.0                                 *
 *                                                                            *
 * Unless required by applicable law or agreed to in writing, software        *
 * distributed under the License is distributed on an "AS IS" BASIS,          *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
 * See the License for the specific language governing permissions and        *
 * limitations under the License.                                             *
 ******************************************************************************/

package interceptor

import (
	"fmt"
	"log/slog"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	validator "github.com/go-playground/validator/v10"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/errors"
)

// HandleJsonRequestWithBuiltReq
// @Description: handle common json request with a built request
// validate request using validator.New().Struct(req)
// serialize request using json.Marshal(req)
// @Parameter c
// @Parameter req
// @Parameter appenders append request data out of http body, such as id in path
// @return bool
// @return requestBody
func HandleJsonRequestWithBuiltReq(c *gin.Context, req interface{}) (s bool) {
	return HandleRequest(c,
		req,
		func(c *gin.Context, req interface{}) error {
			return nil
		},
		func(req interface{}) error {
			return validator.New().Struct(req)
		},
	)
}

// HandleJsonRequestFromQuery
// @Description: handle common json request from query
// build request using c.ShouldBindQuery(req, binding.JSON)
// validate request using validator.New().Struct(req)
// @Parameter c
// @Parameter req
// @return ok
// @return requestBody
func HandleJsonRequestFromQuery(c *gin.Context,
	req interface{},
) (ok bool) {
	return HandleRequest(c,
		req,
		func(c *gin.Context, req interface{}) error {
			err := c.ShouldBindQuery(req)
			if err != nil {
				return err
			}
			return nil
		},
		func(req interface{}) error {
			return validator.New().Struct(req)
		},
	)
}

// HandleJsonRequestFromBody
// @Description: handle common json request from body
// build request using c.ShouldBindBodyWith(req, binding.JSON)
// validate request using validator.New().Struct(req)
// @Parameter c
// @Parameter req
// @return err
// @return requestBody
func HandleJsonRequestFromBody(c *gin.Context,
	req interface{},
) (ok bool) {
	return HandleRequest(c,
		req,
		func(c *gin.Context, req interface{}) error {
			err := c.ShouldBindBodyWith(req, binding.JSON)
			if err != nil {
				return err
			}

			return nil
		},
		func(req interface{}) error {
			return validator.New().Struct(req)
		},
	)
}

// HandleJsonPbRequestFromBody
// @Description: handle common jsonpb request from body
// build request using c.ShouldBindBodyWith(req, binding.JSON)
// validate request using validator.New().Struct(req)
// @Parameter c
// @Parameter req
// @return err
// @return requestBody
func HandleJsonPbRequestFromBody(c *gin.Context,
	req interface{},
) (ok bool) {
	return HandleRequest(c,
		req,
		func(c *gin.Context, req interface{}) error {
			err := c.ShouldBindBodyWith(req, JSONPb)
			if err != nil {
				return err
			}

			return nil
		},
		func(req interface{}) error {
			return validator.New().Struct(req)
		},
	)
}

// HandleRequest
// @Description: handle request
// @Parameter c
// @Parameter req
// @Parameter builder build request from gin.Context
// @Parameter validator validate request
// @Parameter serializer serialize request to string
// @return err
// @return requestBody
func HandleRequest(c *gin.Context,
	req interface{},
	builder func(c *gin.Context, req interface{}) error,
	validator func(req interface{}) error) bool {
	ok := errors.OfNullable(nil).
		BreakIf(func() error {
			return errors.OfNullable(builder(c, req)).
				Map(func(err error) error {
					return errors.New(errors.MarshalError, err.Error())
				}).
				Present()
		}).
		BreakIf(func() error {
			return errors.OfNullable(validator(req)).
				Map(func(err error) error {
					return errors.New(errors.ParameterInvalid, fmt.Sprintf("Invalid params: %s", err.Error()))
				}).
				Present()
		}).
		If(func(err error) {
			slog.Error("handle http request failed", "err", err)
			Fail(c, err)
		}).
		IfNil()
	return ok
}
