package grpc

import (
	"context"
	"time"

	coordinator "github.com/rwadurian/mpc-system/api/grpc/coordinator/v1"
	pb "github.com/rwadurian/mpc-system/api/grpc/router/v1"
	"github.com/rwadurian/mpc-system/pkg/logger"
	"github.com/rwadurian/mpc-system/services/message-router/application/use_cases"
	"github.com/rwadurian/mpc-system/services/message-router/domain"
	"github.com/rwadurian/mpc-system/services/message-router/domain/entities"
	"github.com/rwadurian/mpc-system/services/message-router/domain/repositories"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// MessageBroker defines the interface for message subscription
type MessageBroker interface {
	SubscribeToPartyMessages(ctx context.Context, partyID string) (<-chan *entities.MessageDTO, error)
	SubscribeToSessionMessages(ctx context.Context, sessionID string, partyID string) (<-chan *entities.MessageDTO, error)
}

// MessageRouterServer implements the gRPC MessageRouter service
type MessageRouterServer struct {
	pb.UnimplementedMessageRouterServer
	routeMessageUC       *use_cases.RouteMessageUseCase
	getPendingMessagesUC *use_cases.GetPendingMessagesUseCase
	messageBroker        MessageBroker
	partyRegistry        *domain.PartyRegistry
	eventBroadcaster     *domain.SessionEventBroadcaster
	messageRepo          repositories.MessageRepository
	coordinatorConn      *grpc.ClientConn // Connection to Session Coordinator for proxying
}

// NewMessageRouterServer creates a new gRPC server
func NewMessageRouterServer(
	routeMessageUC *use_cases.RouteMessageUseCase,
	getPendingMessagesUC *use_cases.GetPendingMessagesUseCase,
	messageBroker MessageBroker,
	partyRegistry *domain.PartyRegistry,
	eventBroadcaster *domain.SessionEventBroadcaster,
	messageRepo repositories.MessageRepository,
) *MessageRouterServer {
	return &MessageRouterServer{
		routeMessageUC:       routeMessageUC,
		getPendingMessagesUC: getPendingMessagesUC,
		messageBroker:        messageBroker,
		partyRegistry:        partyRegistry,
		eventBroadcaster:     eventBroadcaster,
		messageRepo:          messageRepo,
	}
}

// SetCoordinatorConnection sets the Session Coordinator gRPC connection for proxying
// This allows server-parties to only connect to Message Router
func (s *MessageRouterServer) SetCoordinatorConnection(conn *grpc.ClientConn) {
	s.coordinatorConn = conn
	logger.Info("Session Coordinator connection configured for proxying")
}

// RouteMessage routes an MPC message
func (s *MessageRouterServer) RouteMessage(
	ctx context.Context,
	req *pb.RouteMessageRequest,
) (*pb.RouteMessageResponse, error) {
	input := use_cases.RouteMessageInput{
		SessionID:   req.SessionId,
		FromParty:   req.FromParty,
		ToParties:   req.ToParties,
		RoundNumber: int(req.RoundNumber),
		MessageType: req.MessageType,
		Payload:     req.Payload,
	}

	output, err := s.routeMessageUC.Execute(ctx, input)
	if err != nil {
		return nil, toGRPCError(err)
	}

	return &pb.RouteMessageResponse{
		Success:   output.Success,
		MessageId: output.MessageID,
	}, nil
}

// SubscribeMessages subscribes to messages for a party (streaming)
// On subscription, it first sends any pending messages from the database
// to ensure no messages are lost during reconnection
func (s *MessageRouterServer) SubscribeMessages(
	req *pb.SubscribeMessagesRequest,
	stream pb.MessageRouter_SubscribeMessagesServer,
) error {
	ctx := stream.Context()

	logger.Info("Party subscribing to messages",
		zap.String("session_id", req.SessionId),
		zap.String("party_id", req.PartyId))

	// First, send any pending messages from the database (message recovery on reconnect)
	if s.getPendingMessagesUC != nil && req.SessionId != "" {
		input := use_cases.GetPendingMessagesInput{
			SessionID:      req.SessionId,
			PartyID:        req.PartyId,
			AfterTimestamp: 0, // Get all pending messages
		}

		pendingMessages, err := s.getPendingMessagesUC.Execute(ctx, input)
		if err != nil {
			logger.Warn("Failed to get pending messages on subscribe",
				zap.String("session_id", req.SessionId),
				zap.String("party_id", req.PartyId),
				zap.Error(err))
		} else if len(pendingMessages) > 0 {
			logger.Info("Sending pending messages on subscribe",
				zap.String("session_id", req.SessionId),
				zap.String("party_id", req.PartyId),
				zap.Int("count", len(pendingMessages)))

			for _, msg := range pendingMessages {
				if err := sendMessage(stream, msg); err != nil {
					logger.Error("Failed to send pending message",
						zap.String("message_id", msg.ID),
						zap.Error(err))
					return err
				}
			}
		}
	}

	// Subscribe to party messages
	partyCh, err := s.messageBroker.SubscribeToPartyMessages(ctx, req.PartyId)
	if err != nil {
		return status.Error(codes.Internal, err.Error())
	}

	// Subscribe to session messages (broadcasts)
	sessionCh, err := s.messageBroker.SubscribeToSessionMessages(ctx, req.SessionId, req.PartyId)
	if err != nil {
		return status.Error(codes.Internal, err.Error())
	}

	// Merge channels and stream messages
	for {
		select {
		case <-ctx.Done():
			logger.Info("Party unsubscribed from messages",
				zap.String("session_id", req.SessionId),
				zap.String("party_id", req.PartyId))
			return nil
		case msg, ok := <-partyCh:
			if !ok {
				return nil
			}
			if err := sendMessage(stream, msg); err != nil {
				return err
			}
		case msg, ok := <-sessionCh:
			if !ok {
				return nil
			}
			if err := sendMessage(stream, msg); err != nil {
				return err
			}
		}
	}
}

// GetPendingMessages retrieves pending messages (polling alternative)
func (s *MessageRouterServer) GetPendingMessages(
	ctx context.Context,
	req *pb.GetPendingMessagesRequest,
) (*pb.GetPendingMessagesResponse, error) {
	input := use_cases.GetPendingMessagesInput{
		SessionID:      req.SessionId,
		PartyID:        req.PartyId,
		AfterTimestamp: req.AfterTimestamp,
	}

	messages, err := s.getPendingMessagesUC.Execute(ctx, input)
	if err != nil {
		return nil, toGRPCError(err)
	}

	protoMessages := make([]*pb.MPCMessage, len(messages))
	for i, msg := range messages {
		protoMessages[i] = &pb.MPCMessage{
			MessageId:   msg.ID,
			SessionId:   msg.SessionID,
			FromParty:   msg.FromParty,
			IsBroadcast: msg.IsBroadcast,
			RoundNumber: int32(msg.RoundNumber),
			MessageType: msg.MessageType,
			Payload:     msg.Payload,
			CreatedAt:   msg.CreatedAt,
		}
	}

	return &pb.GetPendingMessagesResponse{
		Messages: protoMessages,
	}, nil
}

// RegisterParty registers a party with the message router
func (s *MessageRouterServer) RegisterParty(
	ctx context.Context,
	req *pb.RegisterPartyRequest,
) (*pb.RegisterPartyResponse, error) {
	if req.PartyId == "" {
		return nil, status.Error(codes.InvalidArgument, "party_id is required")
	}

	// Convert notification channel if provided
	var notification *domain.NotificationChannel
	if req.Notification != nil {
		notification = &domain.NotificationChannel{
			Email:     req.Notification.Email,
			Phone:     req.Notification.Phone,
			PushToken: req.Notification.PushToken,
		}
	}

	// Register party with notification channel
	party := s.partyRegistry.RegisterWithNotification(req.PartyId, req.PartyRole, req.Version, notification)

	mode := "real-time"
	if party.IsOfflineMode() {
		mode = "offline"
	}

	logger.Info("Party registered",
		zap.String("party_id", req.PartyId),
		zap.String("role", req.PartyRole),
		zap.String("version", req.Version),
		zap.String("mode", mode),
		zap.Bool("has_notification", notification != nil && notification.HasAnyChannel()))

	return &pb.RegisterPartyResponse{
		Success:      true,
		Message:      "Party registered successfully",
		RegisteredAt: party.RegisteredAt.UnixMilli(),
	}, nil
}

// Heartbeat handles party heartbeat to keep the connection alive
func (s *MessageRouterServer) Heartbeat(
	ctx context.Context,
	req *pb.HeartbeatRequest,
) (*pb.HeartbeatResponse, error) {
	if req.PartyId == "" {
		return nil, status.Error(codes.InvalidArgument, "party_id is required")
	}

	// Update heartbeat
	if !s.partyRegistry.Heartbeat(req.PartyId) {
		return nil, status.Error(codes.NotFound, "party not registered")
	}

	// Count pending messages for this party
	pendingMessages := int32(0)
	if s.messageRepo != nil {
		count, err := s.messageRepo.CountPendingByParty(ctx, req.PartyId)
		if err != nil {
			logger.Warn("Failed to count pending messages",
				zap.String("party_id", req.PartyId),
				zap.Error(err))
		} else {
			pendingMessages = int32(count)
		}
	}

	return &pb.HeartbeatResponse{
		Success:         true,
		ServerTimestamp: time.Now().UnixMilli(),
		PendingMessages: pendingMessages,
	}, nil
}

// AcknowledgeMessage acknowledges receipt of a message
func (s *MessageRouterServer) AcknowledgeMessage(
	ctx context.Context,
	req *pb.AcknowledgeMessageRequest,
) (*pb.AcknowledgeMessageResponse, error) {
	if req.MessageId == "" {
		return nil, status.Error(codes.InvalidArgument, "message_id is required")
	}
	if req.PartyId == "" {
		return nil, status.Error(codes.InvalidArgument, "party_id is required")
	}

	// TODO: Store acknowledgment in database for message tracking
	// For now, just log and return success
	logger.Debug("Message acknowledged",
		zap.String("message_id", req.MessageId),
		zap.String("party_id", req.PartyId),
		zap.String("session_id", req.SessionId),
		zap.Bool("success", req.Success))

	if !req.Success {
		logger.Warn("Message processing failed",
			zap.String("message_id", req.MessageId),
			zap.String("party_id", req.PartyId),
			zap.String("error", req.ErrorMessage))
	}

	return &pb.AcknowledgeMessageResponse{
		Success: true,
		Message: "Acknowledgment received",
	}, nil
}

// GetMessageStatus returns the delivery status of a message
func (s *MessageRouterServer) GetMessageStatus(
	ctx context.Context,
	req *pb.GetMessageStatusRequest,
) (*pb.GetMessageStatusResponse, error) {
	if req.MessageId == "" {
		return nil, status.Error(codes.InvalidArgument, "message_id is required")
	}

	// TODO: Implement actual message status tracking in database
	// For now, return a placeholder response
	return &pb.GetMessageStatusResponse{
		MessageId:       req.MessageId,
		SessionId:       req.SessionId,
		Deliveries:      []*pb.MessageDeliveryStatus{},
		AllAcknowledged: false,
	}, nil
}

// SubscribeSessionEvents subscribes to session lifecycle events (streaming)
func (s *MessageRouterServer) SubscribeSessionEvents(
	req *pb.SubscribeSessionEventsRequest,
	stream pb.MessageRouter_SubscribeSessionEventsServer,
) error {
	ctx := stream.Context()

	if req.PartyId == "" {
		return status.Error(codes.InvalidArgument, "party_id is required")
	}

	// Check if party is registered
	if _, exists := s.partyRegistry.Get(req.PartyId); !exists {
		return status.Error(codes.FailedPrecondition, "party not registered")
	}

	logger.Info("Party subscribed to session events",
		zap.String("party_id", req.PartyId))

	// Subscribe to events
	// The channel is used for identity check in Unsubscribe to prevent
	// accidentally removing a newer subscription when this stream exits
	eventCh, _ := s.eventBroadcaster.Subscribe(req.PartyId)
	defer s.eventBroadcaster.Unsubscribe(req.PartyId, eventCh)

	// Stream events
	for {
		select {
		case <-ctx.Done():
			logger.Info("Party unsubscribed from session events",
				zap.String("party_id", req.PartyId))
			return nil

		case event, ok := <-eventCh:
			if !ok {
				return nil
			}

			// Send event to party
			if err := stream.Send(event); err != nil {
				logger.Error("Failed to send session event",
					zap.String("party_id", req.PartyId),
					zap.Error(err))
				return err
			}

			logger.Debug("Sent session event to party",
				zap.String("party_id", req.PartyId),
				zap.String("event_type", event.EventType),
				zap.String("session_id", event.SessionId))
		}
	}
}

// PublishSessionEvent publishes a session event to subscribed parties
// This is called by Session Coordinator via gRPC
func (s *MessageRouterServer) PublishSessionEvent(
	ctx context.Context,
	req *pb.PublishSessionEventRequest,
) (*pb.PublishSessionEventResponse, error) {
	event := req.Event
	if event == nil {
		return nil, status.Error(codes.InvalidArgument, "event is required")
	}

	// If selected_parties is specified, send only to those parties
	if len(event.SelectedParties) > 0 {
		s.eventBroadcaster.BroadcastToParties(event, event.SelectedParties)
		logger.Info("Published session event to selected parties",
			zap.String("event_type", event.EventType),
			zap.String("session_id", event.SessionId),
			zap.Int("party_count", len(event.SelectedParties)))

		return &pb.PublishSessionEventResponse{
			Success:         true,
			SubscriberCount: int32(len(event.SelectedParties)),
		}, nil
	}

	// Broadcast to all subscribers
	s.eventBroadcaster.Broadcast(event)
	subscriberCount := s.eventBroadcaster.SubscriberCount()

	logger.Info("Broadcast session event to all parties",
		zap.String("event_type", event.EventType),
		zap.String("session_id", event.SessionId),
		zap.Int("subscriber_count", subscriberCount))

	return &pb.PublishSessionEventResponse{
		Success:         true,
		SubscriberCount: int32(subscriberCount),
	}, nil
}

// GetRegisteredParties returns all registered parties
// This is called by Session Coordinator for party discovery
func (s *MessageRouterServer) GetRegisteredParties(
	ctx context.Context,
	req *pb.GetRegisteredPartiesRequest,
) (*pb.GetRegisteredPartiesResponse, error) {
	var parties []*domain.RegisteredParty

	// Filter by role if specified
	if req.RoleFilter != "" {
		parties = s.partyRegistry.GetByRole(req.RoleFilter)
	} else {
		parties = s.partyRegistry.GetAll()
	}

	// Convert to protobuf format
	protoParties := make([]*pb.RegisteredParty, 0, len(parties))
	for _, party := range parties {
		// Filter by online status if requested
		if req.OnlyOnline && !party.Online {
			continue
		}

		protoParty := &pb.RegisteredParty{
			PartyId:      party.PartyID,
			Role:         party.Role,
			Online:       party.Online,
			RegisteredAt: party.RegisteredAt.UnixMilli(),
			LastSeenAt:   party.LastSeen.UnixMilli(),
		}

		// Include notification channel if present
		if party.Notification != nil && party.Notification.HasAnyChannel() {
			protoParty.Notification = &pb.NotificationChannel{
				Email:     party.Notification.Email,
				Phone:     party.Notification.Phone,
				PushToken: party.Notification.PushToken,
			}
		}

		protoParties = append(protoParties, protoParty)
	}

	logger.Debug("GetRegisteredParties called",
		zap.String("role_filter", req.RoleFilter),
		zap.Bool("only_online", req.OnlyOnline),
		zap.Int("party_count", len(protoParties)))

	return &pb.GetRegisteredPartiesResponse{
		Parties:    protoParties,
		TotalCount: int32(len(protoParties)),
	}, nil
}

func sendMessage(stream pb.MessageRouter_SubscribeMessagesServer, msg *entities.MessageDTO) error {
	protoMsg := &pb.MPCMessage{
		MessageId:   msg.ID,
		SessionId:   msg.SessionID,
		FromParty:   msg.FromParty,
		IsBroadcast: msg.IsBroadcast,
		RoundNumber: int32(msg.RoundNumber),
		MessageType: msg.MessageType,
		Payload:     msg.Payload,
		CreatedAt:   msg.CreatedAt,
	}
	return stream.Send(protoMsg)
}

func toGRPCError(err error) error {
	switch err {
	case use_cases.ErrInvalidSessionID:
		return status.Error(codes.InvalidArgument, err.Error())
	case use_cases.ErrInvalidPartyID:
		return status.Error(codes.InvalidArgument, err.Error())
	case use_cases.ErrEmptyPayload:
		return status.Error(codes.InvalidArgument, err.Error())
	default:
		return status.Error(codes.Internal, err.Error())
	}
}

// ============================================
// Session Operations (Proxied to Coordinator)
// These methods allow server-parties to only connect to Message Router
// ============================================

// JoinSession proxies the join session request to Session Coordinator
func (s *MessageRouterServer) JoinSession(
	ctx context.Context,
	req *pb.JoinSessionRequest,
) (*pb.JoinSessionResponse, error) {
	if s.coordinatorConn == nil {
		return nil, status.Error(codes.Unavailable, "session coordinator not configured")
	}

	// Convert to coordinator request
	coordReq := &coordinator.JoinSessionRequest{
		SessionId: req.SessionId,
		PartyId:   req.PartyId,
		JoinToken: req.JoinToken,
	}
	if req.DeviceInfo != nil {
		coordReq.DeviceInfo = &coordinator.DeviceInfo{
			DeviceType: req.DeviceInfo.DeviceType,
			DeviceId:   req.DeviceInfo.DeviceId,
			Platform:   req.DeviceInfo.Platform,
			AppVersion: req.DeviceInfo.AppVersion,
		}
	}

	// Call Session Coordinator
	coordResp := &coordinator.JoinSessionResponse{}
	err := s.coordinatorConn.Invoke(ctx, "/mpc.coordinator.v1.SessionCoordinator/JoinSession", coordReq, coordResp)
	if err != nil {
		logger.Error("Failed to proxy JoinSession to coordinator",
			zap.String("session_id", req.SessionId),
			zap.String("party_id", req.PartyId),
			zap.Error(err))
		return nil, err
	}

	// Convert response
	resp := &pb.JoinSessionResponse{
		Success: coordResp.Success,
	}

	if coordResp.SessionInfo != nil {
		resp.SessionInfo = &pb.SessionInfo{
			SessionId:       req.SessionId,
			SessionType:     coordResp.SessionInfo.SessionType,
			ThresholdN:      coordResp.SessionInfo.ThresholdN,
			ThresholdT:      coordResp.SessionInfo.ThresholdT,
			MessageHash:     coordResp.SessionInfo.MessageHash,
			KeygenSessionId: coordResp.SessionInfo.KeygenSessionId,
			Status:          coordResp.SessionInfo.Status, // 修复: 添加缺失的 Status 字段
		}
	}

	// Use party_index directly from coordinator response
	resp.PartyIndex = coordResp.PartyIndex

	if len(coordResp.OtherParties) > 0 {
		resp.OtherParties = make([]*pb.PartyInfo, len(coordResp.OtherParties))
		for i, p := range coordResp.OtherParties {
			resp.OtherParties[i] = &pb.PartyInfo{
				PartyId:    p.PartyId,
				PartyIndex: p.PartyIndex,
			}
		}
	}

	keygenSessionID := ""
	if resp.SessionInfo != nil {
		keygenSessionID = resp.SessionInfo.KeygenSessionId
	}
	logger.Info("Proxied JoinSession to coordinator",
		zap.String("session_id", req.SessionId),
		zap.String("party_id", req.PartyId),
		zap.Int32("party_index", resp.PartyIndex),
		zap.Bool("success", resp.Success),
		zap.String("keygen_session_id", keygenSessionID))

	return resp, nil
}

// MarkPartyReady proxies the mark ready request to Session Coordinator
func (s *MessageRouterServer) MarkPartyReady(
	ctx context.Context,
	req *pb.MarkPartyReadyRequest,
) (*pb.MarkPartyReadyResponse, error) {
	if s.coordinatorConn == nil {
		return nil, status.Error(codes.Unavailable, "session coordinator not configured")
	}

	coordReq := &coordinator.MarkPartyReadyRequest{
		SessionId: req.SessionId,
		PartyId:   req.PartyId,
	}

	coordResp := &coordinator.MarkPartyReadyResponse{}
	err := s.coordinatorConn.Invoke(ctx, "/mpc.coordinator.v1.SessionCoordinator/MarkPartyReady", coordReq, coordResp)
	if err != nil {
		logger.Error("Failed to proxy MarkPartyReady to coordinator",
			zap.String("session_id", req.SessionId),
			zap.String("party_id", req.PartyId),
			zap.Error(err))
		return nil, err
	}

	logger.Debug("Proxied MarkPartyReady to coordinator",
		zap.String("session_id", req.SessionId),
		zap.String("party_id", req.PartyId),
		zap.Bool("all_ready", coordResp.AllReady))

	return &pb.MarkPartyReadyResponse{
		Success:  true,
		AllReady: coordResp.AllReady,
	}, nil
}

// ReportCompletion proxies the completion report to Session Coordinator
func (s *MessageRouterServer) ReportCompletion(
	ctx context.Context,
	req *pb.ReportCompletionRequest,
) (*pb.ReportCompletionResponse, error) {
	if s.coordinatorConn == nil {
		return nil, status.Error(codes.Unavailable, "session coordinator not configured")
	}

	coordReq := &coordinator.ReportCompletionRequest{
		SessionId: req.SessionId,
		PartyId:   req.PartyId,
		PublicKey: req.PublicKey,
	}

	coordResp := &coordinator.ReportCompletionResponse{}
	err := s.coordinatorConn.Invoke(ctx, "/mpc.coordinator.v1.SessionCoordinator/ReportCompletion", coordReq, coordResp)
	if err != nil {
		logger.Error("Failed to proxy ReportCompletion to coordinator",
			zap.String("session_id", req.SessionId),
			zap.String("party_id", req.PartyId),
			zap.Error(err))
		return nil, err
	}

	logger.Info("Proxied ReportCompletion to coordinator",
		zap.String("session_id", req.SessionId),
		zap.String("party_id", req.PartyId),
		zap.Bool("all_completed", coordResp.AllCompleted))

	return &pb.ReportCompletionResponse{
		Success:      true,
		AllCompleted: coordResp.AllCompleted,
	}, nil
}

// GetSessionStatus proxies the status request to Session Coordinator
func (s *MessageRouterServer) GetSessionStatus(
	ctx context.Context,
	req *pb.GetSessionStatusRequest,
) (*pb.GetSessionStatusResponse, error) {
	if s.coordinatorConn == nil {
		return nil, status.Error(codes.Unavailable, "session coordinator not configured")
	}

	coordReq := &coordinator.GetSessionStatusRequest{
		SessionId: req.SessionId,
	}

	coordResp := &coordinator.GetSessionStatusResponse{}
	err := s.coordinatorConn.Invoke(ctx, "/mpc.coordinator.v1.SessionCoordinator/GetSessionStatus", coordReq, coordResp)
	if err != nil {
		logger.Error("Failed to proxy GetSessionStatus to coordinator",
			zap.String("session_id", req.SessionId),
			zap.Error(err))
		return nil, err
	}

	// Convert participants from coordinator response
	var participants []*pb.PartyInfo
	if len(coordResp.Participants) > 0 {
		participants = make([]*pb.PartyInfo, len(coordResp.Participants))
		for i, p := range coordResp.Participants {
			participants[i] = &pb.PartyInfo{
				PartyId:    p.PartyId,
				PartyIndex: p.PartyIndex,
			}
		}
	}

	return &pb.GetSessionStatusResponse{
		SessionId:    req.SessionId,
		Status:       coordResp.Status,
		ThresholdN:   coordResp.ThresholdN,   // Actual threshold N from session config
		ThresholdT:   coordResp.ThresholdT,   // Actual threshold T from session config
		Participants: participants,           // Include participants for co_managed_keygen
	}, nil
}

// SubmitDelegateShare proxies the delegate share submission to Session Coordinator
func (s *MessageRouterServer) SubmitDelegateShare(
	ctx context.Context,
	req *pb.SubmitDelegateShareRequest,
) (*pb.SubmitDelegateShareResponse, error) {
	if s.coordinatorConn == nil {
		return nil, status.Error(codes.Unavailable, "session coordinator not configured")
	}

	coordReq := &coordinator.SubmitDelegateShareRequest{
		SessionId:      req.SessionId,
		PartyId:        req.PartyId,
		EncryptedShare: req.EncryptedShare,
		PublicKey:      req.PublicKey,
		PartyIndex:     req.PartyIndex,
	}

	coordResp := &coordinator.SubmitDelegateShareResponse{}
	err := s.coordinatorConn.Invoke(ctx, "/mpc.coordinator.v1.SessionCoordinator/SubmitDelegateShare", coordReq, coordResp)
	if err != nil {
		logger.Error("Failed to proxy SubmitDelegateShare to coordinator",
			zap.String("session_id", req.SessionId),
			zap.String("party_id", req.PartyId),
			zap.Error(err))
		return nil, err
	}

	logger.Info("Proxied SubmitDelegateShare to coordinator",
		zap.String("session_id", req.SessionId),
		zap.String("party_id", req.PartyId),
		zap.Bool("success", coordResp.Success))

	return &pb.SubmitDelegateShareResponse{
		Success: coordResp.Success,
	}, nil
}
