package pb

import (
	"context"
	"errors"
	"fmt"
	"math/rand/v2"
	"net"
	"net/http"
	"os"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/google/uuid"
	"github.com/seaweedfs/seaweedfs/weed/util/request_id"
	"google.golang.org/grpc/metadata"

	"github.com/seaweedfs/seaweedfs/weed/glog"
	"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
	"github.com/seaweedfs/seaweedfs/weed/util"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/keepalive"
	"google.golang.org/grpc/status"

	"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
	"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
	"github.com/seaweedfs/seaweedfs/weed/pb/mq_pb"
)

const (
	Max_Message_Size = 1 << 30 // 1 GB

	// gRPC keepalive settings - must be consistent between client and server
	GrpcKeepAliveTime        = 60 * time.Second // ping interval when no activity
	GrpcKeepAliveTimeout     = 20 * time.Second // ping timeout
	GrpcKeepAliveMinimumTime = 20 * time.Second // minimum interval between client pings (enforcement)
)

var (
	// cache grpc connections
	grpcClients     = make(map[string]*versionedGrpcClient)
	grpcClientsLock sync.Mutex

	// localGrpcSockets maps gRPC port numbers to Unix socket paths for
	// gRPC services running in this process. Used by both the server side
	// (to start serving on the socket) and the client side (to dial it
	// instead of TCP for same-host RPCs).
	localGrpcSockets = make(map[int]string)
	// localGrpcHosts maps gRPC port → set of host strings that count as
	// "this machine" for that port. Only dials whose target host is in this
	// set are routed through the Unix socket; dials to other hosts that
	// happen to use the same port number go over TCP as normal.
	//
	// Without this host check the hijack was keyed purely on port, so a
	// remote server reusing one of our local gRPC ports (e.g. a standalone
	// `weed volume -port=7334` defaulting `port.grpc=17334` against a
	// `weed server` whose in-process volume socket is also on 17334) had
	// its inbound RPCs silently rerouted to our local Unix socket — see
	// issue #9254.
	localGrpcHosts       = make(map[int]map[string]struct{})
	localGrpcSocketsLock sync.RWMutex
)

type versionedGrpcClient struct {
	*grpc.ClientConn
	version  int
	errCount int
}

func init() {
	t := http.DefaultTransport.(*http.Transport)
	t.MaxIdleConnsPerHost = 1024
	t.MaxIdleConns = 1024
	// Bind outbound HTTP to the -ip.bind source address. Reads the setting
	// per dial, so it applies regardless of when SetOutboundLocalIP runs
	// relative to this init.
	t.DialContext = util.OutboundDialContext
}

// RegisterLocalGrpcSocket registers a Unix socket path for a gRPC service
// running on host:grpcPort in this process. After this is called, any
// GrpcDial to host:grpcPort (or to a loopback alias of host on the same
// port) is routed through the Unix socket. Dials to any other host on the
// same port still go over TCP.
//
// No-op on Windows: the /tmp/...sock paths callers pass are POSIX-only and
// the listen/dial would fail at runtime, taking gRPC down with it (#9430).
// Skipping registration leaves the maps empty, so ServeGrpcOnLocalSocket
// and resolveLocalGrpcSocket short-circuit and same-host RPCs go over TCP.
func RegisterLocalGrpcSocket(host string, grpcPort int, socketPath string) {
	if runtime.GOOS == "windows" {
		return
	}
	localGrpcSocketsLock.Lock()
	defer localGrpcSocketsLock.Unlock()
	localGrpcSockets[grpcPort] = socketPath
	hosts, ok := localGrpcHosts[grpcPort]
	if !ok {
		hosts = make(map[string]struct{})
		localGrpcHosts[grpcPort] = hosts
	}
	// The advertised host plus the well-known loopback aliases all refer
	// to "this machine"; the empty entry covers SplitHostPort outputs like
	// ":17334".
	for _, h := range []string{host, "", "localhost", "127.0.0.1", "::1"} {
		hosts[h] = struct{}{}
	}
}

// GetLocalGrpcSocket returns the Unix socket path for a gRPC port, or empty if not registered.
func GetLocalGrpcSocket(grpcPort int) string {
	localGrpcSocketsLock.RLock()
	defer localGrpcSocketsLock.RUnlock()
	return localGrpcSockets[grpcPort]
}

// resolveLocalGrpcSocket returns the Unix socket path to use for an outgoing
// gRPC dial, or empty if the dial should go over TCP. It matches both the
// host and the port: a remote peer that happens to reuse a local service's
// gRPC port must not be rerouted to the local socket (issue #9254).
func resolveLocalGrpcSocket(address string) string {
	host, portStr, err := net.SplitHostPort(address)
	if err != nil {
		return ""
	}
	port, err := strconv.Atoi(portStr)
	if err != nil {
		return ""
	}
	localGrpcSocketsLock.RLock()
	defer localGrpcSocketsLock.RUnlock()
	if _, ok := localGrpcHosts[port][host]; !ok {
		return ""
	}
	return localGrpcSockets[port]
}

// ServeGrpcOnLocalSocket starts serving a gRPC server on a Unix socket
// if one is registered for the given port.
func ServeGrpcOnLocalSocket(grpcServer *grpc.Server, grpcPort int) {
	socketPath := GetLocalGrpcSocket(grpcPort)
	if socketPath == "" {
		return
	}
	if err := os.Remove(socketPath); err != nil && !os.IsNotExist(err) {
		glog.Warningf("Failed to remove old gRPC socket %s: %v", socketPath, err)
	}
	lc := net.ListenConfig{Control: setLocalSocketBuffers}
	listener, err := lc.Listen(context.Background(), "unix", socketPath)
	if err != nil {
		glog.Errorf("Failed to listen on gRPC Unix socket %s: %v", socketPath, err)
		return
	}
	listener = &localSocketListener{Listener: listener}
	glog.V(0).Infof("gRPC also listening on Unix socket %s", socketPath)
	go func() {
		if err := grpcServer.Serve(listener); err != nil && err != grpc.ErrServerStopped {
			glog.Errorf("gRPC Unix socket server error on %s: %v", socketPath, err)
		}
		os.Remove(socketPath)
	}()
}

// localSocketListener re-applies the buffer sizes to every accepted connection.
type localSocketListener struct {
	net.Listener
}

func (l *localSocketListener) Accept() (net.Conn, error) {
	c, err := l.Listener.Accept()
	if err != nil {
		return nil, err
	}
	applyLocalSocketBuffers(c)
	return c, nil
}

func NewGrpcServer(opts ...grpc.ServerOption) *grpc.Server {
	var options []grpc.ServerOption
	options = append(options,
		grpc.KeepaliveParams(keepalive.ServerParameters{
			Time:    GrpcKeepAliveTime,    // server pings client if no activity for this long
			Timeout: GrpcKeepAliveTimeout, // ping timeout
		}),
		grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
			MinTime:             GrpcKeepAliveMinimumTime, // min time a client should wait before sending a ping
			PermitWithoutStream: true,
		}),
		grpc.MaxRecvMsgSize(Max_Message_Size),
		grpc.MaxSendMsgSize(Max_Message_Size),
		grpc.MaxConcurrentStreams(1000),          // Allow more concurrent streams
		grpc.InitialWindowSize(16*1024*1024),     // 16MB initial window
		grpc.InitialConnWindowSize(16*1024*1024), // 16MB connection window
		grpc.MaxHeaderListSize(8*1024*1024),      // 8MB header list limit
		grpc.UnaryInterceptor(requestIDUnaryInterceptor()),
	)
	for _, opt := range opts {
		if opt != nil {
			options = append(options, opt)
		}
	}
	return grpc.NewServer(options...)
}

// GrpcDial establishes a gRPC connection.
// IMPORTANT: This function intentionally uses the deprecated grpc.DialContext/grpc.Dial behavior
// to preserve the "passthrough" resolver semantics required for Kubernetes ndots/search-domain DNS behavior.
// This allows kube DNS suffixes to be correctly appended by the OS resolver.
//
// Switching to grpc.NewClient (which defaults to the "dns" resolver) would break this behavior
// in environments with ndots:5 and many-dot hostnames.
//
// Safe alternatives if switching to grpc.NewClient:
//  1. Prefix the target with "passthrough:///" (e.g., "passthrough:///my-service:8080"). This is the recommended primary migration path.
//  2. Call resolver.SetDefaultScheme("passthrough") exactly once during init().
//     WARNING: This is NOT thread-safe, and mutates global resolver state affecting all grpc.NewClient calls in the process.
func GrpcDial(ctx context.Context, address string, waitForReady bool, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
	var options []grpc.DialOption

	// Route through Unix socket if one is registered for this address's port
	if socketPath := resolveLocalGrpcSocket(address); socketPath != "" {
		options = append(options, grpc.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
			d := net.Dialer{Control: setLocalSocketBuffers}
			return d.DialContext(ctx, "unix", socketPath)
		}))
	} else {
		// Always install so a conn cached before SetOutboundLocalIP binds once set;
		// the stock net.Dialer until then preserves gRPC's default dial behavior.
		options = append(options, grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
			if util.OutboundLocalAddr() == nil {
				var d net.Dialer
				return d.DialContext(ctx, "tcp", addr)
			}
			return util.OutboundDialContext(ctx, "tcp", addr)
		}))
	}

	options = append(options,
		grpc.WithDefaultCallOptions(
			grpc.MaxCallSendMsgSize(Max_Message_Size),
			grpc.MaxCallRecvMsgSize(Max_Message_Size),
			grpc.WaitForReady(waitForReady),
		),
		grpc.WithKeepaliveParams(keepalive.ClientParameters{
			Time:    GrpcKeepAliveTime,    // client ping server if no activity for this long
			Timeout: GrpcKeepAliveTimeout, // ping timeout
			// Disable pings when there are no active streams to avoid triggering
			// server enforcement for too-frequent pings from idle clients.
			PermitWithoutStream: false,
		}))
	for _, opt := range opts {
		if opt != nil {
			options = append(options, opt)
		}
	}
	return grpc.DialContext(ctx, address, options...)
}

func getOrCreateConnection(address string, waitForReady bool, opts ...grpc.DialOption) (*versionedGrpcClient, error) {

	grpcClientsLock.Lock()
	defer grpcClientsLock.Unlock()

	existingConnection, found := grpcClients[address]
	if found {
		glog.V(4).Infof("gRPC cache hit for %s (version %d)", address, existingConnection.version)
		return existingConnection, nil
	}

	glog.V(2).Infof("Creating new gRPC connection to %s", address)
	ctx := context.Background()
	grpcConnection, err := GrpcDial(ctx, address, waitForReady, opts...)
	if err != nil {
		return nil, fmt.Errorf("fail to dial %s: %v", address, err)
	}

	vgc := &versionedGrpcClient{
		grpcConnection,
		rand.Int(),
		0,
	}
	grpcClients[address] = vgc
	glog.V(2).Infof("New gRPC connection established to %s (version %d)", address, vgc.version)

	return vgc, nil
}

func requestIDUnaryInterceptor() grpc.UnaryServerInterceptor {
	return func(
		ctx context.Context,
		req interface{},
		info *grpc.UnaryServerInfo,
		handler grpc.UnaryHandler,
	) (interface{}, error) {
		// Get request ID from incoming metadata
		var reqID string
		if incomingMd, ok := metadata.FromIncomingContext(ctx); ok {
			if idList := incomingMd.Get(request_id.AmzRequestIDHeader); len(idList) > 0 {
				reqID = idList[0]
			}
		}
		if reqID == "" {
			reqID = uuid.New().String()
		}

		// Store request ID in context for handlers to access
		ctx = request_id.Set(ctx, reqID)

		// Also set outgoing context so handlers making downstream gRPC calls
		// will automatically propagate the request ID
		ctx = metadata.AppendToOutgoingContext(ctx, request_id.AmzRequestIDHeader, reqID)

		// Set trailer with request ID for response
		grpc.SetTrailer(ctx, metadata.Pairs(request_id.AmzRequestIDHeader, reqID))

		return handler(ctx, req)
	}
}

// InvalidateGrpcConnection drops any cached gRPC ClientConn for the given
// address. Use when a higher-level signal (for example a streaming master
// connection detecting its peer has died) indicates the cached channel is
// stale, even though gRPC itself may still believe the channel is healthy.
// Silently returns if no cached connection exists.
func InvalidateGrpcConnection(address string) {
	grpcClientsLock.Lock()
	vgc, ok := grpcClients[address]
	if ok {
		delete(grpcClients, address)
	}
	grpcClientsLock.Unlock()
	if ok {
		glog.V(1).Infof("Invalidating cached gRPC connection to %s", address)
		vgc.Close()
	}
}

// grpcMarshalErrorPrefix is the library-owned prefix gRPC prepends to every
// client-side proto marshal failure; see grpc-go rpc_util.go encode():
//
//	status.Errorf(codes.Internal, "grpc: error while marshaling: %v", ...)
//
// The "grpc:" token is reserved for gRPC internal diagnostics and will not
// collide with user-produced Internal statuses.
const grpcMarshalErrorPrefix = "grpc: error while marshaling"

// grpcStatusError is the minimal interface every gRPC status error satisfies.
// Using it with errors.As lets us reach through arbitrary fmt.Errorf("...: %w")
// wrapping to pull out the *original* gRPC Status — important because
// status.FromError rewrites Status.Message with the outermost err.Error()
// whenever it has to unwrap, which would defeat a prefix check on the
// library-owned message.
type grpcStatusError interface{ GRPCStatus() *status.Status }

// isClientSideMarshalError reports whether err originates from gRPC failing to
// marshal the outgoing request on the client side. These errors are surfaced
// with codes.Internal (because gRPC has no better code for them) but do NOT
// reflect a problem with the TCP/HTTP2 connection: the request never left the
// process. Tearing down the shared cached ClientConn in response would cancel
// every other in-flight RPC on that connection — which is exactly the cascade
// seen in seaweedfs#9139 when a single file with invalid-UTF-8 bytes in its
// name produces an avalanche of "connection is closing" errors on unrelated
// LookupEntry / ReadDirAll / UpdateEntry calls.
//
// errors.Is against a proto-level sentinel is not viable: gRPC's encode()
// collapses the inner proto error with "%v" before wrapping it in a Status,
// so the original error type does not survive. The structural signal that
// *does* survive is the Status itself — we recover it via errors.As and
// match the library-owned "grpc:" prefix to disambiguate from a server-side
// application Internal status that genuinely warrants invalidation.
func isClientSideMarshalError(err error) bool {
	var gse grpcStatusError
	if !errors.As(err, &gse) {
		return false
	}
	s := gse.GRPCStatus()
	if s == nil {
		return false
	}
	return s.Code() == codes.Internal && strings.HasPrefix(s.Message(), grpcMarshalErrorPrefix)
}

// shouldInvalidateConnection checks if an error indicates the cached connection
// should be invalidated. ctx must be the context that bounds this RPC attempt; it
// disambiguates a genuinely broken channel from the RPC hitting its own deadline
// or cancellation. A nil or non-cancellable ctx carries no such evidence.
func shouldInvalidateConnection(ctx context.Context, err error) bool {
	if err == nil {
		return false
	}

	// Client-side marshal errors (e.g. invalid UTF-8 in a proto string field)
	// are per-request bugs, not connection failures. Do not poison the shared
	// ClientConn because of them.
	if isClientSideMarshalError(err) {
		return false
	}

	// A metadata subscriber reads log chunks over HTTP from volume servers and
	// returns what went wrong there through the stream. Those failures read like
	// transport failures below, and would drop a filer channel that is fine.
	if errors.Is(err, ErrLogFileRead) {
		return false
	}

	// gRPC raises this locally, before the RPC reaches the wire, when this
	// process already closed the ClientConn. The caller is a bystander of
	// someone else's teardown, and knows nothing about the peer. Its message
	// is the only thing separating it from any other Canceled, so the plain
	// codes.Canceled its deprecation notice recommends cannot stand in: that
	// is the very code this function has to keep telling apart below.
	if errors.Is(err, grpc.ErrClientConnClosing) {
		return false
	}

	// Check gRPC status codes first (more reliable)
	if s, ok := status.FromError(err); ok {
		code := s.Code()
		switch code {
		case codes.Unavailable, codes.Aborted, codes.Internal:
			return true
		case codes.ResourceExhausted:
			// Server-side backpressure on a healthy channel (e.g. the master's
			// assign shed while volume growth is in flight), not a dead connection.
			// Invalidating would Close() the shared conn and cancel every other
			// in-flight RPC with "the client connection is closing"; keep it and let
			// the caller retry.
			return false
		case codes.Canceled, codes.DeadlineExceeded:
			// Ambiguous: a stale cached channel rejects RPCs this way (a peer
			// restart behind a k8s Service VIP), and so does the RPC's own context
			// expiring. Tearing the channel down for the latter cancels every other
			// in-flight RPC on it with "the client connection is closing", turning
			// one abandoned request into a flood of failures. Only a cancellable
			// context bounds an RPC attempt and can tell the two apart:
			// Background/TODO never expires, so its Err() would answer
			// "stale channel" for every caller with no deadline to honor.
			return ctx != nil && ctx.Done() != nil && ctx.Err() == nil
		}
	}

	// Fall back to string matching for transport-level errors not captured by gRPC codes
	errStr := err.Error()
	errLower := strings.ToLower(errStr)
	return strings.Contains(errLower, "transport") ||
		strings.Contains(errLower, "connection closed") ||
		strings.Contains(errLower, "dns") ||
		strings.Contains(errLower, "connection refused") ||
		strings.Contains(errLower, "no route to host") ||
		strings.Contains(errLower, "network is unreachable") ||
		strings.Contains(errLower, "connection reset")
}

// WithGrpcClient In streamingMode, always use a fresh connection. Otherwise, try to reuse an existing connection.
// ctx must be the context that bounds the RPC fn issues, so that a
// Canceled/DeadlineExceeded fired by ctx itself does not tear down the shared
// cached ClientConn out from under other concurrent callers. Pass
// context.Background() when fn picks its own context: a Canceled/DeadlineExceeded
// then never invalidates, since nothing here can attribute it to the channel.
func WithGrpcClient(ctx context.Context, streamingMode bool, signature int32, fn func(*grpc.ClientConn) error, address string, waitForReady bool, opts ...grpc.DialOption) error {

	if !streamingMode {
		vgc, err := getOrCreateConnection(address, waitForReady, opts...)
		if err != nil {
			return fmt.Errorf("getOrCreateConnection %s: %v", address, err)
		}
		executionErr := fn(vgc.ClientConn)
		if executionErr != nil && shouldInvalidateConnection(ctx, executionErr) {
			grpcClientsLock.Lock()
			t, ok := grpcClients[address]
			shouldClose := ok && t.version == vgc.version
			if shouldClose {
				delete(grpcClients, address)
			}
			grpcClientsLock.Unlock()
			if shouldClose {
				glog.V(1).Infof("Removing cached gRPC connection to %s due to error: %v", address, executionErr)
				vgc.Close()
			}
		}
		return executionErr
	}

	// Streaming mode: dedicate a fresh ClientConn to this call.
	dialCtx := context.Background()
	if signature != 0 {
		// Optimize: Use AppendToOutgoingContext instead of creating new map
		dialCtx = metadata.AppendToOutgoingContext(dialCtx, "sw-client-id", fmt.Sprintf("%d", signature))
	}
	grpcConnection, err := GrpcDial(dialCtx, address, waitForReady, opts...)
	if err != nil {
		return fmt.Errorf("fail to dial %s: %v", address, err)
	}
	defer grpcConnection.Close()
	executionErr := fn(grpcConnection)
	if executionErr != nil && shouldInvalidateConnection(ctx, executionErr) {
		// The streaming channel is dedicated to this caller, but unrelated
		// request-path callers share a cached non-streaming ClientConn to the
		// same peer. When the stream dies of a peer that went away, drop that
		// cached channel so the next caller dials fresh: this recovers cases
		// where a stable L4 endpoint (k8s Service VIP, external LB) hides a
		// peer restart from the transport layer, leaving the cached ClientConn
		// healthy-looking but silently cancelling RPCs. A stream that merely
		// ended, or that its own caller gave up on, says nothing about the peer.
		InvalidateGrpcConnection(address)
	}
	return executionErr
}

func hostAndPort(address string) (host string, port uint64, err error) {
	colonIndex := strings.LastIndex(address, ":")
	if colonIndex < 0 {
		return "", 0, fmt.Errorf("server should have hostname:port format: %v", address)
	}
	dotIndex := strings.LastIndex(address, ".")
	if dotIndex > colonIndex {
		// port format is "port.grpcPort"
		port, err = strconv.ParseUint(address[colonIndex+1:dotIndex], 10, 64)
		if err != nil {
			return "", 0, fmt.Errorf("server port parse error: %w", err)
		}
		return address[:colonIndex], port, err
	}
	port, err = strconv.ParseUint(address[colonIndex+1:], 10, 64)
	if err != nil {
		return "", 0, fmt.Errorf("server port parse error: %w", err)
	}

	return address[:colonIndex], port, err
}

func ServerToGrpcAddress(server string) (serverGrpcAddress string) {

	colonIndex := strings.LastIndex(server, ":")
	if colonIndex >= 0 {
		if dotIndex := strings.LastIndex(server, "."); dotIndex > colonIndex {
			// port format is "port.grpcPort"
			// return the host:grpcPort
			host := server[:colonIndex]
			grpcPort := server[dotIndex+1:]
			if _, err := strconv.ParseUint(grpcPort, 10, 64); err == nil {
				return util.JoinHostPort(host, int(0+util.ParseInt(grpcPort, 0)))
			}
		}
	}

	host, port, parseErr := hostAndPort(server)
	if parseErr != nil {
		glog.Fatalf("server address %s parse error: %v", server, parseErr)
	}

	grpcPort := int(port) + 10000

	return util.JoinHostPort(host, grpcPort)
}

func GrpcAddressToServerAddress(grpcAddress string) (serverAddress string) {
	host, grpcPort, parseErr := hostAndPort(grpcAddress)
	if parseErr != nil {
		glog.Fatalf("server grpc address %s parse error: %v", grpcAddress, parseErr)
	}

	port := int(grpcPort) - 10000

	return util.JoinHostPort(host, port)
}

// WithMasterClient threads the context bounding this RPC attempt into the
// connection-invalidation decision, so a Canceled/DeadlineExceeded from the
// attempt's own timeout does not invalidate the shared cached master connection.
// Pass context.Background() when fn picks its own context.
func WithMasterClient(ctx context.Context, streamingMode bool, master ServerAddress, grpcDialOption grpc.DialOption, waitForReady bool, fn func(client master_pb.SeaweedClient) error) error {
	return WithGrpcClient(ctx, streamingMode, 0, func(grpcConnection *grpc.ClientConn) error {
		client := master_pb.NewSeaweedClient(grpcConnection)
		return fn(client)
	}, master.ToGrpcAddress(), waitForReady, grpcDialOption)

}

func WithVolumeServerClient(streamingMode bool, volumeServer ServerAddress, grpcDialOption grpc.DialOption, fn func(client volume_server_pb.VolumeServerClient) error) error {
	return WithGrpcClient(context.Background(), streamingMode, 0, func(grpcConnection *grpc.ClientConn) error {
		client := volume_server_pb.NewVolumeServerClient(grpcConnection)
		return fn(client)
	}, volumeServer.ToGrpcAddress(), false, grpcDialOption)

}

func WithOneOfGrpcMasterClients(streamingMode bool, masterGrpcAddresses map[string]ServerAddress, grpcDialOption grpc.DialOption, fn func(client master_pb.SeaweedClient) error) (err error) {

	for _, masterGrpcAddress := range masterGrpcAddresses {
		err = WithGrpcClient(context.Background(), streamingMode, 0, func(grpcConnection *grpc.ClientConn) error {
			client := master_pb.NewSeaweedClient(grpcConnection)
			return fn(client)
		}, masterGrpcAddress.ToGrpcAddress(), false, grpcDialOption)
		if err == nil {
			return nil
		}
	}

	return err
}

func WithBrokerGrpcClient(streamingMode bool, brokerGrpcAddress string, grpcDialOption grpc.DialOption, fn func(client mq_pb.SeaweedMessagingClient) error) error {

	return WithGrpcClient(context.Background(), streamingMode, 0, func(grpcConnection *grpc.ClientConn) error {
		client := mq_pb.NewSeaweedMessagingClient(grpcConnection)
		return fn(client)
	}, brokerGrpcAddress, false, grpcDialOption)

}

func WithFilerClient(streamingMode bool, signature int32, filer ServerAddress, grpcDialOption grpc.DialOption, fn func(client filer_pb.SeaweedFilerClient) error) error {

	return WithGrpcFilerClient(streamingMode, signature, filer, grpcDialOption, fn)

}

func WithGrpcFilerClient(streamingMode bool, signature int32, filerAddress ServerAddress, grpcDialOption grpc.DialOption, fn func(client filer_pb.SeaweedFilerClient) error) error {

	return WithGrpcClient(context.Background(), streamingMode, signature, func(grpcConnection *grpc.ClientConn) error {
		client := filer_pb.NewSeaweedFilerClient(grpcConnection)
		return fn(client)
	}, filerAddress.ToGrpcAddress(), false, grpcDialOption)

}

func WithOneOfGrpcFilerClients(streamingMode bool, filerAddresses []ServerAddress, grpcDialOption grpc.DialOption, fn func(client filer_pb.SeaweedFilerClient) error) (err error) {

	for _, filerAddress := range filerAddresses {
		err = WithGrpcClient(context.Background(), streamingMode, 0, func(grpcConnection *grpc.ClientConn) error {
			client := filer_pb.NewSeaweedFilerClient(grpcConnection)
			return fn(client)
		}, filerAddress.ToGrpcAddress(), false, grpcDialOption)
		if err == nil {
			return nil
		}
	}

	return err
}
