/******************************************************************************
 * 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 (
	"context"
	"log/slog"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/constants"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/errors"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/message"
	"google.golang.org/protobuf/proto"
)

// responseBody interface contains method for getting field for marshaling to the response body
// this method is generated for response struct from the value of `response_body` in the `google.api.HttpRule`
type responseBody interface {
	XXX_ResponseBody() interface{}
}

var marshaler = &runtime.HTTPBodyMarshaler{
	Marshaler: jsonpb,
}

var fallback = []byte(`{"code": 13, "message": "failed to marshal error message"}`)

func SucceedPb(c *gin.Context, httpCode int, result proto.Message) {
	var buf []byte
	var err error
	if rb, ok := result.(responseBody); ok {
		buf, err = marshaler.Marshal(rb.XXX_ResponseBody())
	} else {
		buf, err = marshaler.Marshal(result)
	}
	if err != nil {
		slog.Error("Marshal message failed", "err", err)
		c.Data(http.StatusInternalServerError, gin.MIMEJSON, fallback)
		return
	}
	c.Data(httpCode, gin.MIMEJSON, buf)
}

func Succeed(c *gin.Context, httpCode int, result interface{}) {
	messageResult := message.Result{
		Code:    0,
		Message: "",
		Data:    result,
	}

	if httpCode != http.StatusOK {
		newCtx := context.WithValue(c.Request.Context(), constants.RequestResultCtxKey, messageResult)
		c.Request = c.Request.WithContext(newCtx)
	}

	c.JSON(httpCode, messageResult)
}

func Fail(c *gin.Context, err error) {
	var (
		status int
		result message.Result
	)

	if e, ok := err.(errors.Error); ok {
		status = e.Code().GetHttpCode()
		result = message.Result{
			Code:    int(e.Code().GetGrpcCode()),
			Message: err.Error(),
		}
	} else {
		status = errors.UnrecognizedError.GetHttpCode()
		result = message.Result{
			Code:    int(errors.UnrecognizedError.GetGrpcCode()),
			Message: err.Error(),
		}
	}

	c.JSON(status, result)
	newCtx := context.WithValue(c.Request.Context(), constants.RequestResultCtxKey, result)
	c.Request = c.Request.WithContext(newCtx)
}
