// revive:disable:var-naming

// Package grpc_errcode provides a middleware to convert an errcode error to a GRPC error
package grpc_errcode

// revive:enable:var-naming

import (
	"context"

	"github.com/golang/protobuf/proto"
	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	"github.com/pingcap/errcode"
	grpcerr "github.com/pingcap/errcode/grpc"
	"github.com/tidbcloud/dataflow-service/internal/bizutils"
	"google.golang.org/grpc"
	"google.golang.org/grpc/status"
)

type detailedCodeStatus struct {
	grpcerr.ErrorCodeStatus
	details []proto.Message
}

func (wrapper detailedCodeStatus) GRPCStatus() *status.Status {
	status := wrapper.ErrorCodeStatus.GRPCStatus()
	wrappedStatus, err := status.WithDetails(wrapper.details...)
	if err != nil {
		return status
	} else {
		return wrappedStatus
	}
}

func wrapCodeStatusWithDetail(ecs grpcerr.ErrorCodeStatus, details ...proto.Message) grpcerr.ErrorCodeStatus {
	return detailedCodeStatus{
		ErrorCodeStatus: ecs,
		details:         details,
	}
}

func errorConvert(ctx context.Context, err error) error {
	if err == nil {
		return nil
	}

	if coded := errcode.CodeChain(err); coded != nil {
		errCodeStatus := grpcerr.WrapAsGRPC(coded)
		details := bizutils.GetErrorDetailsFromContext(ctx)
		if details != nil {
			return wrapCodeStatusWithDetail(errCodeStatus, details...)
		}
		return errCodeStatus
	}
	return err
}

// UnaryServerInterceptor returns a new unary server interceptors that adds zap.Logger to the context.
func UnaryServerInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		wrappedCtx := bizutils.WithInitMutableErrorDetails(ctx)
		resp, err := handler(wrappedCtx, req)
		return resp, errorConvert(wrappedCtx, err)
	}
}

// StreamServerInterceptor returns a new streaming server interceptor that adds zap.Logger to the context.
func StreamServerInterceptor() grpc.StreamServerInterceptor {
	return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
		wrapped := grpc_middleware.WrapServerStream(stream)
		wrapped.WrappedContext = bizutils.WithInitMutableErrorDetails(wrapped.Context())
		err := handler(srv, wrapped)
		return errorConvert(wrapped.Context(), err)
	}
}
