package errcode

import (
	"context"
	"strings"

	"github.com/pingcap/errcode"
	grpc_err "github.com/pingcap/errcode/grpc"
	"github.com/tidbcloud/serverless-service/internal/errorcode"
	grpc_http "github.com/tidbcloud/serverless-service/internal/grpc/http"
	"github.com/tidbcloud/serverless-service/internal/utils"
	"google.golang.org/grpc"
	"google.golang.org/grpc/status"
)

// errorConvert convert err into status.Status to not set BaseResp,
// but return the error directly
func errorConvert(ctx context.Context, err error) error {
	if err == nil {
		return nil
	}

	// capture error of auth func, and transform errcode into grpc status
	// http status code will be 401/403 rather than 200
	if coded := errcode.CodeChain(err); coded != nil {
		return status.Error(grpc_err.GetCode(coded.Code()), coded.Error())
	}

	// capture error of openapi services
	if strings.HasPrefix(grpc_http.HttpURI(ctx), "/api/internal/") ||
		strings.HasPrefix(grpc_http.HttpURI(ctx), "/api/v1beta/") ||
		strings.HasPrefix(grpc_http.HttpURI(ctx), "/v1beta1/") {
		if bizError, ok := errorcode.AsResponseError(err); ok {
			// 5xx error's message will be converted to `internal error`
			// `errorcode.ErrCodeInternalError` has user-friendly internal error message and should not be redacted.
			if bizError.RawErrCode() != errorcode.ErrCodeInternalError {
				bizError = bizError.Redact()
			}
			return status.Error(utils.BizCode2GrpcCode(bizError.ErrCode()), bizError.ErrMsg())
		}
	}

	// otherwise, bizerr will be returned to set BaseResp in responses
	return err
}

// UnaryServerInterceptor returns a new unary server interceptor that convert err to 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)
		return resp, errorConvert(ctx, err)
	}
}
