package baseresp

import (
	"context"
	"fmt"
	"reflect"

	"github.com/tidbcloud/serverless-service/internal/errorcode"
	"github.com/tidbcloud/serverless-service/internal/utils"
	"google.golang.org/grpc"
	"google.golang.org/grpc/status"
)

func UnaryServerInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
		resp, err := handler(ctx, req)
		if err != nil {
			// return status error directly
			if _, ok := err.(interface{ GRPCStatus() *status.Status }); ok {
				return resp, err
			}

			// create an empty resp with BaseResp
			t := reflect.TypeOf(resp)
			if t.Kind() != reflect.Pointer {
				panic(fmt.Sprintf("got %T: %v, should be a pointer", resp, resp))
			}
			resp = reflect.New(t.Elem()).Interface()
		}

		typed := utils.GetBaseRespFromResp(resp)
		// return resp and err directly if it is not have BaseReq field (for example: openapi)
		if typed == nil || err == nil {
			return resp, err
		}

		// Skip errors without a response-safe contract. AsResponseError also
		// recognizes wrapped BizErrors and structured TiDB SQL errors.
		be, ok := errorcode.AsResponseError(err)
		if !ok {
			return resp, err
		}

		// 5xx error's message will be converted to `internal error`.
		// `errorcode.ErrCodeInternalError` has user-friendly internal error message and should not be redacted.
		if be.RawErrCode() != errorcode.ErrCodeInternalError {
			be = be.Redact()
		}
		typed.ErrCode = int64(be.ErrCode())
		typed.ErrMsg = be.ErrMsg()
		return resp, nil
	}
}
