package servicemirror

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"reflect"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/linkerd/linkerd2/controller/gen/apis/link/v1alpha3"
	"github.com/linkerd/linkerd2/controller/k8s"
	consts "github.com/linkerd/linkerd2/pkg/k8s"
	"github.com/prometheus/client_golang/prometheus"
	logging "github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
	kerrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/kubernetes/scheme"
	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"
	"k8s.io/client-go/util/workqueue"
)

const (
	eventTypeSkipped = "ServiceMirroringSkipped"

	reasonMirrored         = "Mirrored"
	reasonInvalidService   = "InvalidService"
	reasonError            = "Error"
	reasonMissingNamespace = "MissingNamespace"
)

type (
	// RemoteClusterServiceWatcher is a watcher instantiated for every cluster that is being watched
	// Its main job is to listen to events coming from the remote cluster and react accordingly, keeping
	// the state of the mirrored services in sync. This is achieved by maintaining a SharedInformer
	// on the remote cluster. The basic add/update/delete operations are mapped to a more domain specific
	// events, put onto a work queue and handled by the processing loop. In case processing an event fails
	// it can be requeued up to N times, to ensure that the failure is not due to some temporary network
	// problems or general glitch in the Matrix.
	RemoteClusterServiceWatcher struct {
		serviceMirrorNamespace   string
		link                     *v1alpha3.Link
		remoteAPIClient          *k8s.API
		localAPIClient           *k8s.API
		linksAPIClient           *k8s.API
		probeSvc                 string
		stopper                  chan struct{}
		eventBroadcaster         record.EventBroadcaster
		recorder                 record.EventRecorder
		log                      *logging.Entry
		eventsQueue              workqueue.TypedRateLimitingInterface[any]
		requeueLimit             int
		repairPeriod             time.Duration
		gatewayAlive             bool
		liveness                 chan bool
		headlessServicesEnabled  bool
		namespaceCreationEnabled bool

		informerHandlers
	}

	informerHandlers struct {
		svcHandler cache.ResourceEventHandlerRegistration
		epHandler  cache.ResourceEventHandlerRegistration
		nsHandler  cache.ResourceEventHandlerRegistration
	}

	// RemoteServiceExported is generated whenever a remote service is created Observing
	// this event means that the service in question is not mirrored atm
	RemoteServiceExported struct {
		service *corev1.Service
	}

	// CreateFederatedService is generated whenever a remote service joins a
	// federated service and the local federated service does not exist yet.
	CreateFederatedService struct {
		service *corev1.Service
	}

	// RemoteExportedServiceUpdated is generated when we see something about an already
	// mirrored service change on the remote cluster. In that case we need to
	// reconcile. Most importantly we need to keep track of exposed ports
	// and gateway association changes.
	RemoteExportedServiceUpdated struct {
		remoteUpdate *corev1.Service
	}

	// RemoteServiceJoinedFederatedService is generated when a remote server
	// joins a federated service and the local federated service already exists.
	RemoteServiceJoinsFederatedService struct {
		remoteUpdate *corev1.Service
	}

	// RemoteServiceUnexported when a remote service is going away or it is not
	// considered mirrored anymore
	RemoteServiceUnexported struct {
		Name      string
		Namespace string
	}

	// RemoteServiceLeavesFederatedService when a remote service is going away or
	// it is no longer part of the federated service
	RemoteServiceLeavesFederatedService struct {
		Name      string
		Namespace string
	}

	// ClusterUnregistered is issued when this ClusterWatcher is shut down.
	ClusterUnregistered struct{}

	// OrphanedServicesGcTriggered is a self-triggered event which aims to delete any
	// orphaned services that are no longer on the remote cluster. It is emitted every
	// time a new remote cluster is registered for monitoring. The need for this arises
	// because the following might happen.
	//
	// 1. A cluster is registered for monitoring
	// 2. Services A,B,C are created and mirrored
	// 3. Then this component crashes, leaving the mirrors around
	// 4. In the meantime services B and C are deleted on the remote cluster
	// 5. When the controller starts up again it registers to listen for mirrored services
	// 6. It receives an ADD for A but not a DELETE for B and C
	//
	// This event indicates that we need to make a diff with all services on the remote
	// cluster, ensuring that we do not keep any mirrors that are not relevant anymore
	OrphanedServicesGcTriggered struct{}

	// OnAddCalled is issued when the onAdd function of the
	// shared informer is called
	OnAddCalled struct {
		svc *corev1.Service
	}

	// OnAddEndpointsCalled is issued when the onAdd function of the Endpoints
	// shared informer is called
	OnAddEndpointsCalled struct {
		ep *corev1.Endpoints
	}

	// OnUpdateCalled is issued when the onUpdate function of the
	// shared informer is called
	OnUpdateCalled struct {
		svc *corev1.Service
	}

	// OnUpdateEndpointsCalled is issued when the onUpdate function of the
	// shared Endpoints informer is called
	OnUpdateEndpointsCalled struct {
		ep *corev1.Endpoints
	}
	// OnDeleteCalled is issued when the onDelete function of the
	// shared informer is called
	OnDeleteCalled struct {
		svc *corev1.Service
	}

	// RepairEndpoints is issued when the service mirror and mirror gateway
	// endpoints should be resolved based on the remote gateway and updated.
	RepairEndpoints struct{}

	// OnLocalNamespaceAdded is issued when when a new namespace is added to
	// the local cluster. This means that we should check the remote cluster
	// for exported service in that namespace.
	OnLocalNamespaceAdded struct {
		ns *corev1.Namespace
	}

	// RetryableError is an error that should be retried through requeuing events
	RetryableError struct{ Inner []error }
)

func (re RetryableError) Error() string {
	var errorStrings []string
	for _, err := range re.Inner {
		errorStrings = append(errorStrings, err.Error())
	}
	return fmt.Sprintf("Inner errors:\n\t%s", strings.Join(errorStrings, "\n\t"))
}

// NewRemoteClusterServiceWatcher constructs a new cluster watcher
func NewRemoteClusterServiceWatcher(
	ctx context.Context,
	serviceMirrorNamespace string,
	localAPI *k8s.API,
	remoteAPI *k8s.API,
	linksAPI *k8s.API,
	probeSvc string,
	link *v1alpha3.Link,
	requeueLimit int,
	repairPeriod time.Duration,
	liveness chan bool,
	enableHeadlessSvc bool,
	enableNamespaceCreation bool,
) (*RemoteClusterServiceWatcher, error) {
	_, err := remoteAPI.Client.Discovery().ServerVersion()
	if err != nil {
		remoteAPI.UnregisterGauges()
		return nil, fmt.Errorf("cannot connect to api for target cluster %s: %w", link.Spec.TargetClusterName, err)
	}

	// Create k8s event recorder
	eventBroadcaster := record.NewBroadcaster()
	eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{
		Interface: remoteAPI.Client.CoreV1().Events(""),
	})
	recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{
		Component: fmt.Sprintf("linkerd-service-mirror-%s", link.Spec.TargetClusterName),
	})

	stopper := make(chan struct{})
	return &RemoteClusterServiceWatcher{
		serviceMirrorNamespace: serviceMirrorNamespace,
		link:                   link,
		remoteAPIClient:        remoteAPI,
		localAPIClient:         localAPI,
		linksAPIClient:         linksAPI,
		probeSvc:               probeSvc,
		stopper:                stopper,
		eventBroadcaster:       eventBroadcaster,
		recorder:               recorder,
		log: logging.WithFields(logging.Fields{
			"cluster": link.Spec.TargetClusterName,
		}),
		eventsQueue:              workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[any]()),
		requeueLimit:             requeueLimit,
		repairPeriod:             repairPeriod,
		liveness:                 liveness,
		headlessServicesEnabled:  enableHeadlessSvc,
		namespaceCreationEnabled: enableNamespaceCreation,
		// always instantiate the gatewayAlive=true to prevent unexpected service fail fast
		gatewayAlive: true,
	}, nil
}

func (rcsw *RemoteClusterServiceWatcher) mirrorServiceName(remoteName string) string {
	return fmt.Sprintf("%s-%s", remoteName, rcsw.link.Spec.TargetClusterName)
}

func (rcsw *RemoteClusterServiceWatcher) federatedServiceName(remoteName string) string {
	return fmt.Sprintf("%s-federated", remoteName)
}

func (rcsw *RemoteClusterServiceWatcher) targetResourceName(mirrorName string) string {
	return strings.TrimSuffix(mirrorName, "-"+rcsw.link.Spec.TargetClusterName)
}

func (rcsw *RemoteClusterServiceWatcher) originalResourceName(mirroredName string) string {
	return strings.TrimSuffix(mirroredName, fmt.Sprintf("-%s", rcsw.link.Spec.TargetClusterName))
}

func (rcsw *RemoteClusterServiceWatcher) isLabelExlucded(label string) bool {
	if strings.HasPrefix(label, consts.SvcMirrorPrefix) {
		return true
	}
	for _, excludedLabel := range rcsw.link.Spec.ExcludedLabels {
		if strings.HasSuffix(excludedLabel, "/*") {
			trimmed := strings.TrimSuffix(excludedLabel, "/*")
			if strings.HasPrefix(label, trimmed) {
				return true
			}
		} else if label == excludedLabel {
			return true
		}
	}
	return false
}

// Provides labels for mirrored or federatedservice.
// Copies all labels from the remote service to local service (except labels
// with the "SvcMirrorPrefix").
func (rcsw *RemoteClusterServiceWatcher) getCommonServiceLabels(remoteService *corev1.Service) map[string]string {
	labels := map[string]string{
		consts.MirroredResourceLabel: "true",
	}

	for key, value := range remoteService.ObjectMeta.Labels {
		if rcsw.isLabelExlucded(key) {
			continue
		}
		labels[key] = value
	}

	return labels
}

// Provides labels for mirror service.
// Copies all labels from the remote service to mirrored service (except labels
// with the "SvcMirrorPrefix").
func (rcsw *RemoteClusterServiceWatcher) getMirrorServiceLabels(remoteService *corev1.Service) map[string]string {
	labels := rcsw.getCommonServiceLabels(remoteService)
	labels[consts.RemoteClusterNameLabel] = rcsw.link.Spec.TargetClusterName

	if rcsw.isRemoteDiscovery(remoteService.Labels) {
		labels[consts.RemoteDiscoveryLabel] = rcsw.link.Spec.TargetClusterName
		labels[consts.RemoteServiceLabel] = remoteService.GetName()
	}

	return labels
}

// Provides labels for mirror endpoint. Copies all labels from the exported
// service to the mirror endpoint (except labels with the "SvcMirrorPrefix").
func (rcsw *RemoteClusterServiceWatcher) getMirrorEndpointLabels(exportedService *corev1.Service) map[string]string {
	labels := rcsw.getCommonServiceLabels(exportedService)

	labels[consts.RemoteClusterNameLabel] = rcsw.link.Spec.TargetClusterName

	return labels
}

// Provides labels for federated services. Copies all labels from the remote
// service to the federated service (except labels with the "SvcMirrorPrefix").
func (rcsw *RemoteClusterServiceWatcher) getFederatedServiceLabels(remoteService *corev1.Service) map[string]string {
	labels := rcsw.getCommonServiceLabels(remoteService)

	return labels
}

func (rcsw *RemoteClusterServiceWatcher) isAnnotationExlucded(annotation string) bool {
	// Topology aware hints are not multicluster aware.
	if annotation == "service.kubernetes.io/topology-aware-hints" || annotation == "service.kubernetes.io/topology-mode" {
		return true
	}
	for _, excludedAnnotation := range rcsw.link.Spec.ExcludedAnnotations {
		if strings.HasSuffix(excludedAnnotation, "/*") {
			trimmed := strings.TrimSuffix(excludedAnnotation, "/*")
			if strings.HasPrefix(annotation, trimmed) {
				return true
			}
		} else if annotation == excludedAnnotation {
			return true
		}
	}
	return false
}

// Provides annotations for mirror or federated services
func (rcsw *RemoteClusterServiceWatcher) getCommonServiceAnnotations(remoteService *corev1.Service) map[string]string {
	annotations := map[string]string{}

	for key, value := range remoteService.ObjectMeta.Annotations {
		if rcsw.isAnnotationExlucded(key) {
			continue
		}
		annotations[key] = value
	}

	value, ok := remoteService.GetAnnotations()[consts.ProxyOpaquePortsAnnotation]
	if ok {
		annotations[consts.ProxyOpaquePortsAnnotation] = value
	}

	return annotations
}

// Provides annotations for mirror services
func (rcsw *RemoteClusterServiceWatcher) getMirrorServiceAnnotations(remoteService *corev1.Service) map[string]string {
	annotations := rcsw.getCommonServiceAnnotations(remoteService)

	annotations[consts.RemoteServiceFqName] = fmt.Sprintf("%s.%s.svc.%s", remoteService.Name, remoteService.Namespace, rcsw.link.Spec.TargetClusterDomain)
	annotations[consts.RemoteResourceVersionAnnotation] = remoteService.ResourceVersion // needed to detect real changes

	return annotations
}

// Provides annotations for federated service
func (rcsw *RemoteClusterServiceWatcher) getFederatedServiceAnnotations(remoteService *corev1.Service) map[string]string {
	annotations := rcsw.getCommonServiceAnnotations(remoteService)

	if rcsw.link.Spec.TargetClusterName == "" {
		// Local discovery
		annotations[consts.LocalDiscoveryAnnotation] = remoteService.Name
	} else {
		// Remote discovery
		annotations[consts.RemoteDiscoveryAnnotation] = fmt.Sprintf("%s@%s", remoteService.Name, rcsw.link.Spec.TargetClusterName)
	}

	return annotations
}

func (rcsw *RemoteClusterServiceWatcher) mirrorNamespaceIfNecessary(ctx context.Context, namespace string) error {
	// if the namespace is already present we do not need to change it.
	// if we are creating it we want to put a label indicating this is a
	// mirrored resource
	if _, err := rcsw.localAPIClient.NS().Lister().Get(namespace); err != nil {
		if kerrors.IsNotFound(err) {
			// if the namespace is not found, we can just create it
			ns := &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						consts.MirroredResourceLabel:  "true",
						consts.RemoteClusterNameLabel: rcsw.link.Spec.TargetClusterName,
					},
					Name: namespace,
				},
			}
			_, err := rcsw.localAPIClient.Client.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
			if err != nil {
				// something went wrong with the create, we can just retry as well
				return RetryableError{[]error{err}}
			}
		} else {
			// something else went wrong, so we can just retry
			return RetryableError{[]error{err}}
		}
	}
	return nil
}

// This method takes care of port remapping. What it does essentially is get the one gateway port
// that we should send traffic to and create endpoint ports that bind to the mirrored service ports
// (same name, etc) but send traffic to the gateway port. This way we do not need to do any remapping
// on the service side of things. It all happens in the endpoints.
func (rcsw *RemoteClusterServiceWatcher) getEndpointsPorts(service *corev1.Service) ([]corev1.EndpointPort, error) {
	gatewayPort, err := strconv.ParseInt(rcsw.link.Spec.GatewayPort, 10, 32)
	if err != nil {
		return nil, err
	}
	var endpointsPorts []corev1.EndpointPort
	for _, remotePort := range service.Spec.Ports {
		endpointsPorts = append(endpointsPorts, corev1.EndpointPort{
			Name:     remotePort.Name,
			Protocol: remotePort.Protocol,
			Port:     int32(gatewayPort),
		})
	}
	return endpointsPorts, nil
}

func (rcsw *RemoteClusterServiceWatcher) cleanupOrphanedServices(ctx context.Context) error {
	matchLabels := map[string]string{
		consts.MirroredResourceLabel:  "true",
		consts.RemoteClusterNameLabel: rcsw.link.Spec.TargetClusterName,
	}

	servicesOnLocalCluster, err := rcsw.localAPIClient.Svc().Lister().List(labels.Set(matchLabels).AsSelector())
	if err != nil {
		innerErr := fmt.Errorf("failed to list services while cleaning up mirror services: %w", err)
		if kerrors.IsNotFound(err) {
			return innerErr
		}
		// if it is something else, we can just retry
		return RetryableError{[]error{innerErr}}
	}

	var errors []error
	for _, srv := range servicesOnLocalCluster {
		mirroredName := srv.Name
		// For headless services with cluster IPs representing the backing pods, the mirrored service name
		// is the root headless service in the source cluster
		if remoteHeadlessSvcName, headlessMirror := srv.Labels[consts.MirroredHeadlessSvcNameLabel]; headlessMirror {
			mirroredName = remoteHeadlessSvcName
		}
		remoteServiceName := rcsw.originalResourceName(mirroredName)
		_, err := rcsw.remoteAPIClient.Svc().Lister().Services(srv.Namespace).Get(remoteServiceName)
		if err != nil {
			if kerrors.IsNotFound(err) {
				// service does not exist anymore. Need to delete
				if err := rcsw.localAPIClient.Client.CoreV1().Services(srv.Namespace).Delete(ctx, srv.Name, metav1.DeleteOptions{}); err != nil {
					// something went wrong with deletion, we need to retry
					errors = append(errors, err)
				} else {
					rcsw.log.Infof("Deleted service %s/%s while cleaning up mirror services", srv.Namespace, srv.Name)
				}
			} else {
				// something went wrong getting the service, we can retry
				errors = append(errors, err)
			}
		}
	}
	if len(errors) > 0 {
		return RetryableError{errors}
	}

	return nil
}

// Whenever we stop watching a cluster, we need to cleanup everything that we have
// created. This piece of code is responsible for doing just that. It takes care of
// services, endpoints and namespaces (if needed)
func (rcsw *RemoteClusterServiceWatcher) cleanupMirroredResources(ctx context.Context) error {
	matchLabels := map[string]string{
		consts.MirroredResourceLabel:  "true",
		consts.RemoteClusterNameLabel: rcsw.link.Spec.TargetClusterName,
	}

	services, err := rcsw.localAPIClient.Svc().Lister().List(labels.Set(matchLabels).AsSelector())
	if err != nil {
		innerErr := fmt.Errorf("could not retrieve mirrored services that need cleaning up: %w", err)
		if kerrors.IsNotFound(err) {
			return innerErr
		}
		// if its not notFound then something else went wrong, so we can retry
		return RetryableError{[]error{innerErr}}
	}

	var errors []error
	for _, svc := range services {
		if err := rcsw.localAPIClient.Client.CoreV1().Services(svc.Namespace).Delete(ctx, svc.Name, metav1.DeleteOptions{}); err != nil {
			if kerrors.IsNotFound(err) {
				continue
			}
			errors = append(errors, fmt.Errorf("Could not delete service %s/%s: %w", svc.Namespace, svc.Name, err))
		} else {
			rcsw.log.Infof("Deleted service %s/%s", svc.Namespace, svc.Name)
		}
	}

	endpoints, err := rcsw.localAPIClient.Endpoint().Lister().List(labels.Set(matchLabels).AsSelector())
	if err != nil {
		innerErr := fmt.Errorf("could not retrieve endpoints that need cleaning up: %w", err)
		if kerrors.IsNotFound(err) {
			return innerErr
		}
		return RetryableError{[]error{innerErr}}
	}

	for _, endpoint := range endpoints {
		if err := rcsw.localAPIClient.Client.CoreV1().Endpoints(endpoint.Namespace).Delete(ctx, endpoint.Name, metav1.DeleteOptions{}); err != nil {
			if kerrors.IsNotFound(err) {
				continue
			}
			errors = append(errors, fmt.Errorf("Could not delete endpoints %s/%s: %w", endpoint.Namespace, endpoint.Name, err))
		} else {
			rcsw.log.Infof("Deleted endpoints %s/%s", endpoint.Namespace, endpoint.Name)
		}
	}

	if len(errors) > 0 {
		return RetryableError{errors}
	}
	return nil
}

// Deletes a locally mirrored service as it is not present on the remote cluster anymore
func (rcsw *RemoteClusterServiceWatcher) handleRemoteServiceUnexported(ctx context.Context, ev *RemoteServiceUnexported) error {
	rcsw.deleteLinkMirrorStatus(
		ev.Name, ev.Namespace,
	)

	localServiceName := rcsw.mirrorServiceName(ev.Name)
	localService, err := rcsw.localAPIClient.Svc().Lister().Services(ev.Namespace).Get(localServiceName)
	var errors []error
	if err != nil {
		if kerrors.IsNotFound(err) {
			rcsw.log.Debugf("Failed to delete mirror service %s/%s: %v", ev.Namespace, ev.Name, err)
			return nil
		}
		errors = append(errors, fmt.Errorf("could not fetch service %s/%s: %w", ev.Namespace, localServiceName, err))
	}

	// If the mirror service is headless, also delete its endpoint mirror
	// services.
	if localService.Spec.ClusterIP == corev1.ClusterIPNone {
		matchLabels := map[string]string{
			consts.MirroredHeadlessSvcNameLabel: localServiceName,
		}
		endpointMirrorServices, err := rcsw.localAPIClient.Svc().Lister().List(labels.Set(matchLabels).AsSelector())
		if err != nil {
			if !kerrors.IsNotFound(err) {
				errors = append(errors, fmt.Errorf("could not fetch endpoint mirrors for mirror service %s/%s: %w", ev.Namespace, localServiceName, err))
			}
		}

		for _, endpointMirror := range endpointMirrorServices {
			err = rcsw.localAPIClient.Client.CoreV1().Services(endpointMirror.Namespace).Delete(ctx, endpointMirror.Name, metav1.DeleteOptions{})
			if err != nil {
				if !kerrors.IsNotFound(err) {
					errors = append(errors, fmt.Errorf("could not delete endpoint mirror %s/%s: %w", endpointMirror.Namespace, endpointMirror.Name, err))
				}
			}
		}
	}

	rcsw.log.Infof("Deleting mirrored service %s/%s", ev.Namespace, localServiceName)
	if err := rcsw.localAPIClient.Client.CoreV1().Services(ev.Namespace).Delete(ctx, localServiceName, metav1.DeleteOptions{}); err != nil {
		if !kerrors.IsNotFound(err) {
			errors = append(errors, fmt.Errorf("could not delete service: %s/%s: %w", ev.Namespace, localServiceName, err))
		}
	}

	if len(errors) > 0 {
		return RetryableError{errors}
	}

	rcsw.log.Infof("Successfully deleted service: %s/%s", ev.Namespace, localServiceName)
	return nil
}

// Removes a remote service from a local federated service.
func (rcsw *RemoteClusterServiceWatcher) handleFederatedServiceLeave(ctx context.Context, ev *RemoteServiceLeavesFederatedService) error {
	rcsw.deleteLinkFederatedStatus(
		ev.Name, ev.Namespace,
	)

	localServiceName := rcsw.federatedServiceName(ev.Name)
	localService, err := rcsw.localAPIClient.Svc().Lister().Services(ev.Namespace).Get(localServiceName)

	if err != nil {
		if kerrors.IsNotFound(err) {
			rcsw.log.Debugf("Failed to update federated service %s/%s: %v", ev.Namespace, ev.Name, err)
			return nil
		}
		return RetryableError{[]error{fmt.Errorf("could not fetch service %s/%s: %w", ev.Namespace, localServiceName, err)}}
	}

	if rcsw.link.Spec.TargetClusterName == "" {
		// Local discovery
		delete(localService.Annotations, consts.LocalDiscoveryAnnotation)
	} else {
		remoteTarget := fmt.Sprintf("%s@%s", ev.Name, rcsw.link.Spec.TargetClusterName)
		if !remoteDiscoveryContains(localService.Annotations[consts.RemoteDiscoveryAnnotation], remoteTarget) {
			return nil
		}

		remoteDiscoveryList := strings.Split(localService.Annotations[consts.RemoteDiscoveryAnnotation], ",")
		newRemoteDiscoveryList := []string{}
		for _, member := range remoteDiscoveryList {
			if member == remoteTarget {
				continue
			}
			newRemoteDiscoveryList = append(newRemoteDiscoveryList, member)
		}
		localService.Annotations[consts.RemoteDiscoveryAnnotation] = strings.Join(newRemoteDiscoveryList, ",")
	}

	if len(localService.Annotations[consts.RemoteDiscoveryAnnotation]) == 0 && len(localService.Annotations[consts.LocalDiscoveryAnnotation]) == 0 {
		rcsw.log.Infof("Deleting federated service %s/%s", ev.Namespace, localServiceName)
		if err := rcsw.localAPIClient.Client.CoreV1().Services(ev.Namespace).Delete(ctx, localServiceName, metav1.DeleteOptions{}); err != nil {
			if !kerrors.IsNotFound(err) {
				return RetryableError{[]error{fmt.Errorf("could not delete service: %s/%s: %w", ev.Namespace, localServiceName, err)}}
			}
		}
		rcsw.log.Infof("Successfully deleted service: %s/%s", ev.Namespace, localServiceName)
		rcsw.deleteLinkFederatedStatus(
			ev.Name, ev.Namespace,
		)
		return nil
	}

	if _, err := rcsw.localAPIClient.Client.CoreV1().Services(ev.Namespace).Update(ctx, localService, metav1.UpdateOptions{}); err != nil {
		return RetryableError{[]error{err}}
	}
	return nil
}

// Updates a locally mirrored service. There might have been some pretty fundamental changes such as
// new gateway being assigned or additional ports exposed. This method takes care of that.
func (rcsw *RemoteClusterServiceWatcher) handleRemoteExportedServiceUpdated(ctx context.Context, ev *RemoteExportedServiceUpdated) error {
	rcsw.log.Infof("Updating mirror service for %s/%s", ev.remoteUpdate.Namespace, ev.remoteUpdate.Name)

	mirrorName := rcsw.mirrorServiceName(ev.remoteUpdate.Name)
	localService, err := rcsw.localAPIClient.Svc().Lister().Services(ev.remoteUpdate.Namespace).Get(mirrorName)
	if err != nil {
		if kerrors.IsNotFound(err) {
			rcsw.log.Infof("Mirror service %s/%s not found, re-creating", ev.remoteUpdate.Namespace, mirrorName)
			rcsw.eventsQueue.Add(&RemoteServiceExported{
				service: ev.remoteUpdate,
			})
			return nil
		}
		return RetryableError{[]error{err}}
	}

	localEndpoints, err := rcsw.localAPIClient.Endpoint().Lister().Endpoints(localService.Namespace).Get(mirrorName)
	if err != nil {
		if kerrors.IsNotFound(err) {
			localEndpoints = nil
		} else {
			return RetryableError{[]error{err}}
		}
	}

	if rcsw.isRemoteDiscovery(ev.remoteUpdate.Labels) {
		// The service is mirrored in remote discovery mode and any local
		// endpoints for it should be deleted if they exist.
		if localEndpoints != nil {
			err := rcsw.localAPIClient.Client.CoreV1().Endpoints(localService.Namespace).Delete(ctx, localService.Name, metav1.DeleteOptions{})
			if err != nil {
				rcsw.updateLinkMirrorStatus(
					ev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),
					mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed to delete mirror endpoints: %s", err), nil),
				)
				return RetryableError{[]error{
					fmt.Errorf("failed to delete mirror endpoints for %s/%s: %w", localService.Namespace, localService.Name, err),
				}}
			}
		}
	} else if localEndpoints == nil {
		// The service is mirrored in gateway mode and gateway endpoints should
		// be created for it.
		err := rcsw.createGatewayEndpoints(ctx, ev.remoteUpdate)
		if err != nil {
			rcsw.updateLinkMirrorStatus(
				ev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),
				mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed to create mirror endpoints: %s", err), nil),
			)
			return err
		}
	} else {
		// The service is mirrored in gateway mode and gateway endpoints already
		// exist for it but may need to be updated.
		gatewayAddresses, err := rcsw.resolveGatewayAddress()
		if err != nil {
			rcsw.updateLinkMirrorStatus(
				ev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),
				mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed to get gateway address: %s", err), nil),
			)
			return err
		}

		copiedEndpoints := localEndpoints.DeepCopy()
		ports, err := rcsw.getEndpointsPorts(ev.remoteUpdate)
		if err != nil {
			return err
		}
		copiedEndpoints.Subsets = []corev1.EndpointSubset{
			{
				Addresses: gatewayAddresses,
				Ports:     ports,
			},
		}

		if copiedEndpoints.Annotations == nil {
			copiedEndpoints.Annotations = make(map[string]string)
		}
		copiedEndpoints.Annotations[consts.RemoteGatewayIdentity] = rcsw.link.Spec.GatewayIdentity

		err = rcsw.updateMirrorEndpoints(ctx, copiedEndpoints)
		if err != nil {
			rcsw.updateLinkMirrorStatus(
				ev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),
				mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed to update mirror endpoints: %s", err), nil),
			)
			return RetryableError{[]error{err}}
		}
	}

	localService.Labels = rcsw.getMirrorServiceLabels(ev.remoteUpdate)
	localService.Annotations = rcsw.getMirrorServiceAnnotations(ev.remoteUpdate)
	localService.Spec.Ports = remapRemoteServicePorts(ev.remoteUpdate.Spec.Ports)

	if _, err := rcsw.localAPIClient.Client.CoreV1().Services(localService.Namespace).Update(ctx, localService, metav1.UpdateOptions{}); err != nil {
		rcsw.updateLinkMirrorStatus(
			ev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),
			mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed to update mirror service: %s", err), nil),
		)
		return RetryableError{[]error{err}}
	}
	rcsw.updateLinkMirrorStatus(
		ev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),
		mirrorStatusCondition(true, reasonMirrored, "", localService),
	)
	return nil
}

// Updates a federated service to include the remote service as a member.
func (rcsw *RemoteClusterServiceWatcher) handleFederatedServiceJoin(ctx context.Context, ev *RemoteServiceJoinsFederatedService) error {
	federatedName := rcsw.federatedServiceName(ev.remoteUpdate.Name)
	rcsw.log.Infof("Updating federated service %s/%s", ev.remoteUpdate.Namespace, federatedName)

	localService, err := rcsw.localAPIClient.Svc().Lister().Services(ev.remoteUpdate.Namespace).Get(federatedName)
	if err != nil {
		if kerrors.IsNotFound(err) {
			rcsw.eventsQueue.Add(&CreateFederatedService{
				service: ev.remoteUpdate,
			})
			return nil
		}
		rcsw.updateLinkFederatedStatus(
			ev.remoteUpdate.Name, ev.remoteUpdate.Namespace,
			mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed to get federated service: %s", err), nil),
		)
		return RetryableError{[]error{err}}
	}

	previous := localService.DeepCopy()

	if ev.remoteUpdate.Spec.ClusterIP == corev1.ClusterIPNone {
		rcsw.updateLinkFederatedStatus(
			ev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),
			mirrorStatusCondition(false, reasonInvalidService, "Headless service cannot join federated service", nil),
		)
		return fmt.Errorf("headless service %s/%s cannot join federated service", ev.remoteUpdate.GetNamespace(), ev.remoteUpdate.GetName())
	}

	// We treat the member with the oldest Link as the source of truth for Service
	// metadata such as labels and annotations.
	var isOldest bool
	if rcsw.link.Spec.TargetClusterName == "" {
		// Always treat the local cluster as the oldest.
		isOldest = true
	} else if _, localDiscoveryExists := localService.Annotations[consts.LocalDiscoveryAnnotation]; localDiscoveryExists {
		// The local cluster is older than us.
		isOldest = false
	} else {
		// The local cluster is not a member of the federated service. Therefore,
		// we check the remote discovery to see if we are the oldest.
		isOldest = true
		members := strings.Split(localService.Annotations[consts.RemoteDiscoveryAnnotation], ",")
		for _, member := range members {
			target := strings.Split(member, "@")
			if len(target) != 2 {
				rcsw.log.Errorf("Invalid federated service member: %s", member)
				continue
			}
			cluster := target[1]
			link, err := rcsw.linksAPIClient.Link().Lister().Links(rcsw.serviceMirrorNamespace).Get(cluster)
			if err != nil {
				rcsw.log.Errorf("Failed to get link %s: %s", cluster, err)
				continue
			}
			if link.CreationTimestamp.Before(&rcsw.link.CreationTimestamp) {
				rcsw.log.Debugf("ignoring metadata from %s because %s is older", rcsw.link.Spec.TargetClusterName, cluster)
				isOldest = false
				break
			}
		}
	}

	if isOldest {
		preservedAnnotations := map[string]string{}
		for _, k := range []string{consts.RemoteDiscoveryAnnotation, consts.LocalDiscoveryAnnotation} {
			if v, ok := localService.Annotations[k]; ok {
				preservedAnnotations[k] = v
			}
		}
		localService.Labels = rcsw.getFederatedServiceLabels(ev.remoteUpdate)
		localService.Annotations = rcsw.getFederatedServiceAnnotations(ev.remoteUpdate)
		for k, v := range preservedAnnotations {
			localService.Annotations[k] = v
		}

		localService.Spec.Ports = remapRemoteServicePorts(ev.remoteUpdate.Spec.Ports)
	}

	if rcsw.link.Spec.TargetClusterName == "" {
		// Local discovery
		localService.Annotations[consts.LocalDiscoveryAnnotation] = ev.remoteUpdate.Name
	} else {
		// Remote discovery
		remoteTarget := fmt.Sprintf("%s@%s", ev.remoteUpdate.Name, rcsw.link.Spec.TargetClusterName)
		if !remoteDiscoveryContains(localService.Annotations[consts.RemoteDiscoveryAnnotation], remoteTarget) {
			if localService.Annotations[consts.RemoteDiscoveryAnnotation] == "" {
				localService.Annotations[consts.RemoteDiscoveryAnnotation] = remoteTarget
			} else {
				localService.Annotations[consts.RemoteDiscoveryAnnotation] = fmt.Sprintf(
					"%s,%s",
					localService.Annotations[consts.RemoteDiscoveryAnnotation],
					remoteTarget,
				)
			}
		}
	}

	// Don't update the service if it has not changed.
	if reflect.DeepEqual(previous, localService) {
		return nil
	}

	if _, err := rcsw.localAPIClient.Client.CoreV1().Services(localService.Namespace).Update(ctx, localService, metav1.UpdateOptions{}); err != nil {
		rcsw.updateLinkFederatedStatus(
			ev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),
			mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed to update federated service: %s", err), nil),
		)
		return RetryableError{[]error{err}}
	}
	rcsw.updateLinkFederatedStatus(
		ev.remoteUpdate.GetName(), ev.remoteUpdate.GetNamespace(),
		mirrorStatusCondition(true, reasonMirrored, "", localService),
	)
	return nil
}

func remoteDiscoveryContains(remoteDiscoveryList string, remoteTarget string) bool {
	for _, member := range strings.Split(remoteDiscoveryList, ",") {
		if member == remoteTarget {
			return true
		}
	}
	return false
}

func remapRemoteServicePorts(ports []corev1.ServicePort) []corev1.ServicePort {
	// We ignore the NodePort here as its not relevant
	// to the local cluster
	var newPorts []corev1.ServicePort
	for _, port := range ports {
		newPorts = append(newPorts, corev1.ServicePort{
			Name:       port.Name,
			Protocol:   port.Protocol,
			Port:       port.Port,
			TargetPort: port.TargetPort,
		})
	}
	return newPorts
}

func (rcsw *RemoteClusterServiceWatcher) handleRemoteServiceExported(ctx context.Context, ev *RemoteServiceExported) error {
	remoteService := ev.service.DeepCopy()
	if rcsw.headlessServicesEnabled && remoteService.Spec.ClusterIP == corev1.ClusterIPNone {
		rcsw.updateLinkMirrorStatus(
			ev.service.GetName(), ev.service.GetNamespace(),
			mirrorStatusCondition(false, reasonInvalidService, "Headless mirror services are disabled", nil),
		)
		return nil
	}

	serviceInfo := fmt.Sprintf("%s/%s", remoteService.Namespace, remoteService.Name)
	localServiceName := rcsw.mirrorServiceName(remoteService.Name)

	if rcsw.namespaceCreationEnabled {
		if err := rcsw.mirrorNamespaceIfNecessary(ctx, remoteService.Namespace); err != nil {
			rcsw.updateLinkMirrorStatus(
				ev.service.GetName(), ev.service.GetNamespace(),
				mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed to create namespace: %s", err), nil),
			)
			return err
		}
	} else {
		// Ensure the namespace exists, and skip mirroring if it doesn't
		if _, err := rcsw.localAPIClient.Client.CoreV1().Namespaces().Get(ctx, remoteService.Namespace, metav1.GetOptions{}); err != nil {
			if kerrors.IsNotFound(err) {
				rcsw.recorder.Event(remoteService, corev1.EventTypeNormal, eventTypeSkipped, "Skipped mirroring service: namespace does not exist")
				rcsw.log.Warnf("Skipping mirroring of service %s: namespace %s does not exist", serviceInfo, remoteService.Namespace)
				rcsw.updateLinkMirrorStatus(
					ev.service.GetName(), ev.service.GetNamespace(),
					mirrorStatusCondition(false, reasonMissingNamespace, "Namespace does not exist", nil),
				)
				return nil
			}
			rcsw.updateLinkMirrorStatus(
				ev.service.GetName(), ev.service.GetNamespace(),
				mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed get namespace: %s", err), nil),
			)
			// something else went wrong, so we can just retry
			return RetryableError{[]error{err}}
		}
	}

	serviceToCreate := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:        localServiceName,
			Namespace:   remoteService.Namespace,
			Annotations: rcsw.getMirrorServiceAnnotations(remoteService),
			Labels:      rcsw.getMirrorServiceLabels(remoteService),
		},
		Spec: corev1.ServiceSpec{
			Ports: remapRemoteServicePorts(remoteService.Spec.Ports),
		},
	}

	rcsw.log.Infof("Creating a new service mirror for %s", serviceInfo)
	if _, err := rcsw.localAPIClient.Client.CoreV1().Services(remoteService.Namespace).Create(ctx, serviceToCreate, metav1.CreateOptions{}); err != nil {
		if !kerrors.IsAlreadyExists(err) {
			rcsw.updateLinkMirrorStatus(
				ev.service.GetName(), ev.service.GetNamespace(),
				mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed to create mirror service: %s", err), nil),
			)
			// we might have created it during earlier attempt, if that is not the case, we retry
			return RetryableError{[]error{err}}
		}
	}

	if rcsw.isRemoteDiscovery(remoteService.Labels) {
		// For remote discovery services, skip creating gateway endpoints.
		rcsw.updateLinkMirrorStatus(
			ev.service.GetName(), ev.service.GetNamespace(),
			mirrorStatusCondition(true, reasonMirrored, "", serviceToCreate),
		)
		return nil
	}

	err := rcsw.createGatewayEndpoints(ctx, remoteService)
	if err != nil {
		rcsw.updateLinkMirrorStatus(
			ev.service.GetName(), ev.service.GetNamespace(),
			mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed to create mirror endpoints: %s", err), nil),
		)
		return err
	}

	rcsw.updateLinkMirrorStatus(
		ev.service.GetName(), ev.service.GetNamespace(),
		mirrorStatusCondition(true, reasonMirrored, "", serviceToCreate),
	)
	return nil
}

func (rcsw *RemoteClusterServiceWatcher) handleCreateFederatedService(ctx context.Context, ev *CreateFederatedService) error {
	remoteService := ev.service.DeepCopy()
	serviceInfo := fmt.Sprintf("%s/%s", remoteService.Namespace, remoteService.Name)

	if remoteService.Spec.ClusterIP == corev1.ClusterIPNone {
		rcsw.updateLinkFederatedStatus(
			remoteService.GetName(), remoteService.GetNamespace(),
			mirrorStatusCondition(false, reasonInvalidService, "Headless service cannot join federated service", nil),
		)
		return fmt.Errorf("headless service %s cannot join federated service", serviceInfo)
	}

	localServiceName := rcsw.federatedServiceName(remoteService.Name)

	if rcsw.namespaceCreationEnabled {
		if err := rcsw.mirrorNamespaceIfNecessary(ctx, remoteService.Namespace); err != nil {
			rcsw.updateLinkFederatedStatus(
				remoteService.GetName(), remoteService.GetNamespace(),
				mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed to create namespace: %s", err), nil),
			)
			return err
		}
	} else {
		// Ensure the namespace exists, and skip mirroring if it doesn't
		if _, err := rcsw.localAPIClient.Client.CoreV1().Namespaces().Get(ctx, remoteService.Namespace, metav1.GetOptions{}); err != nil {
			if kerrors.IsNotFound(err) {
				rcsw.recorder.Event(remoteService, corev1.EventTypeNormal, eventTypeSkipped, "Skipped mirroring service: namespace does not exist")
				rcsw.log.Warnf("Skipping mirroring of service %s: namespace %s does not exist", serviceInfo, remoteService.Namespace)
				rcsw.updateLinkFederatedStatus(
					remoteService.GetName(), remoteService.GetNamespace(),
					mirrorStatusCondition(false, reasonMissingNamespace, "Namespace does not exist", nil),
				)
				return nil
			}
			// something else went wrong, so we can just retry
			rcsw.updateLinkFederatedStatus(
				remoteService.GetName(), remoteService.GetNamespace(),
				mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed get namespace: %s", err), nil),
			)
			return RetryableError{[]error{err}}
		}
	}

	serviceToCreate := &corev1.Service{
		ObjectMeta: metav1.ObjectMeta{
			Name:        localServiceName,
			Namespace:   remoteService.Namespace,
			Annotations: rcsw.getFederatedServiceAnnotations(remoteService),
			Labels:      rcsw.getFederatedServiceLabels(remoteService),
		},
		Spec: corev1.ServiceSpec{
			Ports: remapRemoteServicePorts(remoteService.Spec.Ports),
		},
	}

	rcsw.log.Infof("Creating a new federated service for %s", serviceInfo)
	if _, err := rcsw.localAPIClient.Client.CoreV1().Services(remoteService.Namespace).Create(ctx, serviceToCreate, metav1.CreateOptions{}); err != nil {
		if !kerrors.IsAlreadyExists(err) {
			// we might have created it during earlier attempt, if that is not the case, we retry
			rcsw.updateLinkFederatedStatus(
				remoteService.GetName(), remoteService.GetNamespace(),
				mirrorStatusCondition(false, reasonError, fmt.Sprintf("Failed to create federated service: %s", err), nil),
			)
			return RetryableError{[]error{err}}
		}
	}

	rcsw.updateLinkFederatedStatus(
		remoteService.GetName(), remoteService.GetNamespace(),
		mirrorStatusCondition(true, reasonMirrored, "", serviceToCreate),
	)
	return nil
}

func (rcsw *RemoteClusterServiceWatcher) handleLocalNamespaceAdded(ns *corev1.Namespace) error {
	// When a local namespace is added, we issue a create event for all the services in the corresponding namespace in
	// case any of them are exported and need to be mirrored.
	svcs, err := rcsw.remoteAPIClient.Svc().Lister().Services(ns.Name).List(labels.Everything())
	if err != nil {
		return RetryableError{[]error{err}}
	}
	for _, svc := range svcs {
		rcsw.eventsQueue.Add(&OnAddCalled{
			svc: svc,
		})
	}
	return nil
}

// isEmptyService returns true if any of these conditions are true:
// - svc's Endpoint is not found
// - svc's Endpoint has no Subsets (happens when there's no associated Pod)
// - svc's Endpoint has Subsets, but none have addresses (only notReadyAddresses,
// when the pod is not ready yet)
func (rcsw *RemoteClusterServiceWatcher) isEmptyService(svc *corev1.Service) (bool, error) {
	ep, err := rcsw.remoteAPIClient.Endpoint().Lister().Endpoints(svc.Namespace).Get(svc.Name)
	if err != nil {
		if kerrors.IsNotFound(err) {
			rcsw.log.Debugf("target endpoint %s/%s not found", svc.Namespace, svc.Name)
			return true, nil
		}

		return true, err
	}
	return rcsw.isEmptyEndpoints(ep), nil
}

// isEmptyEndpoints returns true if any of these conditions are true:
// - The Endpoint is not found
// - The Endpoint has no Subsets (happens when there's no associated Pod)
// - The Endpoint has Subsets, but none have addresses (only notReadyAddresses,
// when the pod is not ready yet)
func (rcsw *RemoteClusterServiceWatcher) isEmptyEndpoints(ep *corev1.Endpoints) bool {
	if len(ep.Subsets) == 0 {
		rcsw.log.Debugf("endpoint %s/%s has no Subsets", ep.Namespace, ep.Name)
		return true
	}
	for _, subset := range ep.Subsets {
		if len(subset.Addresses) > 0 {
			return false
		}
	}
	rcsw.log.Debugf("endpoint %s/%s has no ready addresses", ep.Namespace, ep.Name)
	return true
}

func (rcsw *RemoteClusterServiceWatcher) createGatewayEndpoints(ctx context.Context, exportedService *corev1.Service) error {
	empty, err := rcsw.isEmptyService(exportedService)
	if err != nil {
		return RetryableError{[]error{err}}
	}

	gatewayAddresses, err := rcsw.resolveGatewayAddress()
	if err != nil {
		return err
	}

	localServiceName := rcsw.mirrorServiceName(exportedService.Name)
	serviceInfo := fmt.Sprintf("%s/%s", exportedService.Namespace, exportedService.Name)
	endpointsToCreate := &corev1.Endpoints{
		ObjectMeta: metav1.ObjectMeta{
			Name:      localServiceName,
			Namespace: exportedService.Namespace,
			Labels:    rcsw.getMirrorEndpointLabels(exportedService),
			Annotations: map[string]string{
				consts.RemoteServiceFqName: fmt.Sprintf("%s.%s.svc.%s", exportedService.Name, exportedService.Namespace, rcsw.link.Spec.TargetClusterDomain),
			},
		},
	}

	rcsw.log.Infof("Resolved gateway [%v:%s] for %s", gatewayAddresses, rcsw.link.Spec.GatewayPort, serviceInfo)

	ports, err := rcsw.getEndpointsPorts(exportedService)
	if err != nil {
		return err
	}
	if !empty && len(gatewayAddresses) > 0 {

		endpointsToCreate.Subsets = []corev1.EndpointSubset{
			{
				Addresses: gatewayAddresses,
				Ports:     ports,
			},
		}
	} else if !empty {
		endpointsToCreate.Subsets = []corev1.EndpointSubset{
			{
				NotReadyAddresses: gatewayAddresses,
				Ports:             ports,
			},
		}
		rcsw.log.Warnf("could not resolve gateway addresses for %s; setting endpoint subsets to not ready", serviceInfo)
	} else {
		rcsw.log.Warnf("exported service %s is empty", serviceInfo)
	}

	if rcsw.link.Spec.GatewayIdentity != "" {
		endpointsToCreate.Annotations[consts.RemoteGatewayIdentity] = rcsw.link.Spec.GatewayIdentity
	}

	rcsw.log.Infof("Creating a new endpoints for %s", serviceInfo)
	err = rcsw.createMirrorEndpoints(ctx, endpointsToCreate)
	if err != nil {
		if svcErr := rcsw.localAPIClient.Client.CoreV1().Services(exportedService.Namespace).Delete(ctx, localServiceName, metav1.DeleteOptions{}); svcErr != nil {
			rcsw.log.Errorf("Failed to delete service %s after endpoints creation failed: %s", localServiceName, svcErr)
		}
		return RetryableError{[]error{err}}
	}
	return nil
}

// this method is common to both CREATE and UPDATE because if we have been
// offline for some time due to a crash a CREATE for a service that we have
// observed before is simply a case of UPDATE
func (rcsw *RemoteClusterServiceWatcher) createOrUpdateService(service *corev1.Service) error {
	mirrorName := rcsw.mirrorServiceName(service.Name)

	if rcsw.isExported(service.Labels) || rcsw.isRemoteDiscovery(service.Labels) {
		// The desired state is that the local mirror service should exist.
		localService, err := rcsw.localAPIClient.Svc().Lister().Services(service.Namespace).Get(mirrorName)
		if err != nil {
			if kerrors.IsNotFound(err) {
				rcsw.eventsQueue.Add(&RemoteServiceExported{
					service: service,
				})
				return nil
			}
			return RetryableError{[]error{err}}
		}
		// if we have the local service present, we need to issue an update
		lastMirroredRemoteVersion, ok := localService.Annotations[consts.RemoteResourceVersionAnnotation]
		if ok && lastMirroredRemoteVersion != service.ResourceVersion {
			rcsw.eventsQueue.Add(&RemoteExportedServiceUpdated{
				remoteUpdate: service,
			})
		}
	} else {
		// The desired state is that the local mirror service should not exist.
		localSvc, err := rcsw.localAPIClient.Svc().Lister().Services(service.Namespace).Get(mirrorName)
		if err == nil {
			if localSvc.Labels != nil {
				_, isMirroredRes := localSvc.Labels[consts.MirroredResourceLabel]
				clusterName := localSvc.Labels[consts.RemoteClusterNameLabel]
				if isMirroredRes && (clusterName == rcsw.link.Spec.TargetClusterName) {
					rcsw.eventsQueue.Add(&RemoteServiceUnexported{
						Name:      service.Name,
						Namespace: service.Namespace,
					})
				}
			}
		}
	}

	federatedName := rcsw.federatedServiceName(service.Name)

	if rcsw.isFederatedServiceMember(service.Labels) {
		// The desired state is that the local federated service should exist.
		rcsw.eventsQueue.Add(&RemoteServiceJoinsFederatedService{
			remoteUpdate: service,
		})
	} else {
		// The desired state is that the local federated service should not
		// include the remote service.
		localSvc, err := rcsw.localAPIClient.Svc().Lister().Services(service.Namespace).Get(federatedName)
		if err == nil {
			if localSvc.Labels != nil {
				_, isMirroredRes := localSvc.Labels[consts.MirroredResourceLabel]
				if isMirroredRes {
					rcsw.eventsQueue.Add(&RemoteServiceLeavesFederatedService{
						Name:      service.Name,
						Namespace: service.Namespace,
					})
				}
			}
		}
	}

	return nil
}

func (rcsw *RemoteClusterServiceWatcher) getMirrorServices() (*corev1.ServiceList, error) {
	matchLabels := map[string]string{
		consts.MirroredResourceLabel:  "true",
		consts.RemoteClusterNameLabel: rcsw.link.Spec.TargetClusterName,
	}
	services, err := rcsw.localAPIClient.Client.CoreV1().Services("").List(context.Background(), metav1.ListOptions{LabelSelector: labels.SelectorFromSet(matchLabels).String()})
	if err != nil {
		return nil, err
	}
	return services, nil
}

func (rcsw *RemoteClusterServiceWatcher) handleOnDelete(service *corev1.Service) {
	if rcsw.isExported(service.Labels) || rcsw.isRemoteDiscovery(service.Labels) {
		rcsw.eventsQueue.Add(&RemoteServiceUnexported{
			Name:      service.Name,
			Namespace: service.Namespace,
		})
	}
	if rcsw.isFederatedServiceMember(service.Labels) {
		rcsw.eventsQueue.Add(&RemoteServiceLeavesFederatedService{
			Name:      service.Name,
			Namespace: service.Namespace,
		})
	}
}

func (rcsw *RemoteClusterServiceWatcher) processNextEvent(ctx context.Context) (bool, interface{}, error) {
	event, done := rcsw.eventsQueue.Get()
	if event != nil {
		rcsw.log.Infof("Received: %s", event)
	} else if done {
		rcsw.log.Infof("Received: Stop")
	}

	var err error
	switch ev := event.(type) {
	case *OnAddCalled:
		err = rcsw.createOrUpdateService(ev.svc)
	case *OnAddEndpointsCalled:
		err = rcsw.handleCreateOrUpdateEndpoints(ctx, ev.ep)
	case *OnUpdateCalled:
		err = rcsw.createOrUpdateService(ev.svc)
	case *OnUpdateEndpointsCalled:
		err = rcsw.handleCreateOrUpdateEndpoints(ctx, ev.ep)
	case *OnDeleteCalled:
		rcsw.handleOnDelete(ev.svc)
	case *RemoteServiceExported:
		err = rcsw.handleRemoteServiceExported(ctx, ev)
	case *RemoteExportedServiceUpdated:
		err = rcsw.handleRemoteExportedServiceUpdated(ctx, ev)
	case *RemoteServiceUnexported:
		err = rcsw.handleRemoteServiceUnexported(ctx, ev)
	case *CreateFederatedService:
		err = rcsw.handleCreateFederatedService(ctx, ev)
	case *RemoteServiceJoinsFederatedService:
		err = rcsw.handleFederatedServiceJoin(ctx, ev)
	case *RemoteServiceLeavesFederatedService:
		err = rcsw.handleFederatedServiceLeave(ctx, ev)
	case *ClusterUnregistered:
		err = rcsw.cleanupMirroredResources(ctx)
	case *OrphanedServicesGcTriggered:
		err = rcsw.cleanupOrphanedServices(ctx)
	case *RepairEndpoints:
		err = rcsw.repairEndpoints(ctx)
	case *OnLocalNamespaceAdded:
		err = rcsw.handleLocalNamespaceAdded(ev.ns)
	default:
		if ev != nil || !done { // we get a nil in case we are shutting down...
			rcsw.log.Warnf("Received unknown event: %v", ev)
		}
	}

	return done, event, err

}

// the main processing loop in which we handle more domain specific events
// and deal with retries
func (rcsw *RemoteClusterServiceWatcher) processEvents(ctx context.Context) {
	for {
		done, event, err := rcsw.processNextEvent(ctx)
		rcsw.eventsQueue.Done(event)
		// the logic here is that there might have been an API
		// connectivity glitch or something. So its not a bad idea to requeue
		// the event and try again up to a number of limits, just to ensure
		// that we are not diverging in states due to bad luck...
		if err == nil {
			rcsw.eventsQueue.Forget(event)
		} else {
			var re RetryableError
			if errors.As(err, &re) {
				rcsw.log.Warnf("Requeues: %d, Limit: %d for event %s", rcsw.eventsQueue.NumRequeues(event), rcsw.requeueLimit, event)
				if (rcsw.eventsQueue.NumRequeues(event) < rcsw.requeueLimit) && !done {
					rcsw.log.Errorf("Error processing %s (will retry): %s", event, re)
					rcsw.eventsQueue.AddRateLimited(event)
				} else {
					rcsw.log.Errorf("Error processing %s (giving up): %s", event, re)
					rcsw.eventsQueue.Forget(event)
				}
			} else {
				rcsw.log.Errorf("Error processing %s (will not retry): %s", event, err)
			}
		}
		if done {
			rcsw.log.Infof("Shutting down events processor")
			return
		}
	}
}

// Start starts watching the remote cluster
func (rcsw *RemoteClusterServiceWatcher) Start(ctx context.Context) error {
	rcsw.remoteAPIClient.Sync(rcsw.stopper)
	rcsw.eventsQueue.Add(&OrphanedServicesGcTriggered{})
	var err error
	rcsw.svcHandler, err = rcsw.remoteAPIClient.Svc().Informer().AddEventHandler(
		cache.ResourceEventHandlerFuncs{
			AddFunc: func(svc interface{}) {
				rcsw.eventsQueue.Add(&OnAddCalled{svc.(*corev1.Service)})
			},
			DeleteFunc: func(obj interface{}) {
				service, ok := obj.(*corev1.Service)
				if !ok {
					tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
					if !ok {
						rcsw.log.Errorf("couldn't get object from DeletedFinalStateUnknown %#v", obj)
						return
					}
					service, ok = tombstone.Obj.(*corev1.Service)
					if !ok {
						rcsw.log.Errorf("DeletedFinalStateUnknown contained object that is not a Service %#v", obj)
						return
					}
				}
				rcsw.eventsQueue.Add(&OnDeleteCalled{service})
			},
			UpdateFunc: func(_, new interface{}) {
				rcsw.eventsQueue.Add(&OnUpdateCalled{new.(*corev1.Service)})
			},
		},
	)
	if err != nil {
		return err
	}

	rcsw.epHandler, err = rcsw.remoteAPIClient.Endpoint().Informer().AddEventHandler(
		cache.ResourceEventHandlerFuncs{
			// AddFunc only relevant for exported headless endpoints
			AddFunc: func(obj interface{}) {
				ep, ok := obj.(*corev1.Endpoints)
				if !ok {
					rcsw.log.Errorf("error processing endpoints object: got %#v, expected *corev1.Endpoints", ep)
					return
				}

				if !rcsw.isExported(ep.Labels) {
					rcsw.log.Debugf("skipped processing endpoints object %s/%s: missing %s label", ep.Namespace, ep.Name, consts.DefaultExportedServiceSelector)
					return
				}

				if !isHeadlessEndpoints(ep, rcsw.log) {
					return
				}

				rcsw.eventsQueue.Add(&OnAddEndpointsCalled{obj.(*corev1.Endpoints)})
			},
			// AddFunc relevant for all kind of exported endpoints
			UpdateFunc: func(_, new interface{}) {
				epNew, ok := new.(*corev1.Endpoints)
				if !ok {
					rcsw.log.Errorf("error processing endpoints object: got %#v, expected *corev1.Endpoints", epNew)
					return
				}
				if !rcsw.isExported(epNew.Labels) {
					rcsw.log.Debugf("skipped processing endpoints object %s/%s: missing %s label", epNew.Namespace, epNew.Name, consts.DefaultExportedServiceSelector)
					return
				}
				if rcsw.isRemoteDiscovery(epNew.Labels) {
					rcsw.log.Debugf("skipped processing endpoints object %s/%s (service labeled for remote-discovery mode)", epNew.Namespace, epNew.Name)
					return
				}
				rcsw.eventsQueue.Add(&OnUpdateEndpointsCalled{epNew})
			},
		},
	)
	if err != nil {
		return err
	}

	rcsw.nsHandler, err = rcsw.localAPIClient.NS().Informer().AddEventHandler(
		cache.ResourceEventHandlerFuncs{
			AddFunc: func(obj interface{}) {
				rcsw.eventsQueue.Add(&OnLocalNamespaceAdded{obj.(*corev1.Namespace)})
			},
		},
	)
	if err != nil {
		return err
	}

	go rcsw.processEvents(ctx)

	// If no gateway address is present, do not repair endpoints
	if rcsw.link.Spec.GatewayAddress == "" {
		return nil
	}

	// We need to issue a RepairEndpoints immediately to populate the gateway
	// mirror endpoints.
	ev := RepairEndpoints{}
	rcsw.eventsQueue.Add(&ev)

	go func() {
		ticker := time.NewTicker(rcsw.repairPeriod)
		for {
			select {
			case <-ticker.C:
				ev := RepairEndpoints{}
				rcsw.eventsQueue.Add(&ev)
			case alive := <-rcsw.liveness:
				rcsw.log.Debugf("gateway liveness change from %t to %t", rcsw.gatewayAlive, alive)
				rcsw.gatewayAlive = alive
				ev := RepairEndpoints{}
				rcsw.eventsQueue.Add(&ev)
			case <-rcsw.stopper:
				return
			}
		}
	}()

	return nil
}

// Stop stops watching the cluster and cleans up all mirrored resources
func (rcsw *RemoteClusterServiceWatcher) Stop(cleanupState bool) {
	close(rcsw.stopper)
	if cleanupState {
		rcsw.eventsQueue.Add(&ClusterUnregistered{})
	}
	rcsw.eventsQueue.ShutDown()
	rcsw.eventBroadcaster.Shutdown()

	if rcsw.svcHandler != nil {
		if err := rcsw.remoteAPIClient.Svc().Informer().RemoveEventHandler(rcsw.svcHandler); err != nil {
			rcsw.log.Warnf("error removing service informer handler: %s", err)
		}
	}
	if rcsw.epHandler != nil {
		if err := rcsw.remoteAPIClient.Endpoint().Informer().RemoveEventHandler(rcsw.epHandler); err != nil {
			rcsw.log.Warnf("error removing service informer handler: %s", err)
		}
	}
	if rcsw.nsHandler != nil {
		if err := rcsw.localAPIClient.NS().Informer().RemoveEventHandler(rcsw.nsHandler); err != nil {
			rcsw.log.Warnf("error removing service informer handler: %s", err)
		}
	}

	if rcsw.remoteAPIClient != nil {
		rcsw.remoteAPIClient.UnregisterGauges()
	}
}

func (rcsw *RemoteClusterServiceWatcher) resolveGatewayAddress() ([]corev1.EndpointAddress, error) {
	var gatewayEndpoints []corev1.EndpointAddress
	var errors []error
	for _, addr := range strings.Split(rcsw.link.Spec.GatewayAddress, ",") {
		ipAddrs, err := net.LookupIP(addr)
		if err != nil {
			err = fmt.Errorf("Error resolving '%s': %w", addr, err)
			rcsw.log.Warn(err)
			errors = append(errors, err)
			continue
		}

		for _, ipAddr := range ipAddrs {
			gatewayEndpoints = append(gatewayEndpoints, corev1.EndpointAddress{
				IP: ipAddr.String(),
			})
		}
	}

	if len(gatewayEndpoints) == 0 {
		return nil, RetryableError{errors}
	}

	sort.SliceStable(gatewayEndpoints, func(i, j int) bool {
		return gatewayEndpoints[i].IP < gatewayEndpoints[j].IP
	})
	return gatewayEndpoints, nil
}

func (rcsw *RemoteClusterServiceWatcher) repairEndpoints(ctx context.Context) error {
	endpointRepairCounter.With(prometheus.Labels{
		gatewayClusterName: rcsw.link.Spec.TargetClusterName,
	}).Inc()

	// Create or update the gateway mirror endpoints responsible for driving
	// the cluster watcher's gateway liveness status.
	gatewayAddresses, err := rcsw.resolveGatewayAddress()
	if err != nil {
		return err
	}
	err = rcsw.createOrUpdateGatewayEndpoints(ctx, gatewayAddresses)
	if err != nil {
		rcsw.log.Errorf("Failed to create/update gateway mirror endpoints: %s", err)
	}

	// Repair mirror service endpoints.
	mirrorServices, err := rcsw.getMirrorServices()
	if err != nil {
		return RetryableError{[]error{fmt.Errorf("Failed to list mirror services: %w", err)}}
	}
	for _, svc := range mirrorServices.Items {
		svc := svc

		// Mirrors for headless services are also headless, and their
		// Endpoints point to auxiliary services instead of pointing to
		// the gateway, so they're skipped.
		if svc.Spec.ClusterIP == corev1.ClusterIPNone {
			rcsw.log.Debugf("Skipped repairing endpoints for headless mirror %s/%s", svc.Namespace, svc.Name)
			continue
		}

		if _, ok := svc.Labels[consts.RemoteDiscoveryLabel]; ok {
			rcsw.log.Debugf("Skipped repairing endpoints for service in remote-discovery mode %s/%s", svc.Namespace, svc.Name)
			continue
		}

		endpoints, err := rcsw.localAPIClient.Endpoint().Lister().Endpoints(svc.Namespace).Get(svc.Name)
		if err != nil {
			if !kerrors.IsNotFound(err) {
				rcsw.log.Errorf("Failed to list local endpoints: %s", err)
				continue
			}
			endpoints, err = rcsw.localAPIClient.Client.CoreV1().Endpoints(svc.Namespace).Get(ctx, svc.Name, metav1.GetOptions{})
			if err != nil {
				rcsw.log.Errorf("Failed to get local endpoints %s/%s: %s", svc.Namespace, svc.Name, err)
				continue
			}
		}
		updatedEndpoints := endpoints.DeepCopy()
		ports, err := rcsw.getEndpointsPorts(&svc)
		if err != nil {
			rcsw.log.Errorf("Failed to get endpoints ports: %s", err)
			continue
		}
		updatedEndpoints.Subsets = []corev1.EndpointSubset{
			{
				Addresses: gatewayAddresses,
				Ports:     ports,
			},
		}

		// We want to skip this service empty check for auxiliary services --
		// services which are not headless but do belong to a headless
		// mirrored service. This is because they do not have a corresponding
		// endpoint on the target cluster, only a pod. If we attempt to find
		// endpoints for services like this, they'll always be set to empty.
		if _, found := svc.Labels[consts.MirroredHeadlessSvcNameLabel]; !found {
			targetService := svc.DeepCopy()
			targetService.Name = rcsw.targetResourceName(svc.Name)
			empty, err := rcsw.isEmptyService(targetService)
			if err != nil {
				rcsw.log.Errorf("Could not check service emptiness: %s", err)
				continue
			}
			if empty {
				rcsw.log.Warnf("Exported service %s/%s is empty", targetService.Namespace, targetService.Name)
				updatedEndpoints.Subsets = []corev1.EndpointSubset{}
			}
		}

		if updatedEndpoints.Annotations == nil {
			updatedEndpoints.Annotations = make(map[string]string)
		}
		updatedEndpoints.Annotations[consts.RemoteGatewayIdentity] = rcsw.link.Spec.GatewayIdentity

		err = rcsw.updateMirrorEndpoints(ctx, updatedEndpoints)
		if err != nil {
			rcsw.log.Error(err)
		}
	}

	return nil
}

// createOrUpdateGatewayEndpoints will create or update the gateway mirror
// endpoints for a remote cluster. These endpoints are required for the probe
// worker responsible for probing gateway liveness, so these endpoints are
// never in a not ready state.
func (rcsw *RemoteClusterServiceWatcher) createOrUpdateGatewayEndpoints(ctx context.Context, addressses []corev1.EndpointAddress) error {
	probePort, err := strconv.ParseInt(rcsw.link.Spec.ProbeSpec.Port, 10, 32)
	if err != nil {
		return fmt.Errorf("failed to parse probe port: %w", err)
	}
	endpoints := &corev1.Endpoints{
		ObjectMeta: metav1.ObjectMeta{
			Name:      rcsw.probeSvc,
			Namespace: rcsw.serviceMirrorNamespace,
			Labels: map[string]string{
				consts.RemoteClusterNameLabel: rcsw.link.Spec.TargetClusterName,
			},
			Annotations: map[string]string{
				consts.RemoteGatewayIdentity: rcsw.link.Spec.GatewayIdentity,
			},
		},
		Subsets: []corev1.EndpointSubset{
			{
				Addresses: addressses,
				Ports: []corev1.EndpointPort{
					{
						Name:     "mc-probe",
						Port:     int32(probePort),
						Protocol: "TCP",
					},
				},
			},
		},
	}
	_, err = rcsw.localAPIClient.Client.CoreV1().Endpoints(endpoints.Namespace).Get(ctx, endpoints.Name, metav1.GetOptions{})
	if err != nil {
		if !kerrors.IsNotFound(err) {
			return err
		}

		// Mirror endpoints for the gateway do not exist so they need to be
		// created. As mentioned above, these endpoints are required for the
		// probe worker and therefore should never be put in a not ready
		// state.
		_, err = rcsw.localAPIClient.Client.CoreV1().Endpoints(endpoints.Namespace).Create(ctx, endpoints, metav1.CreateOptions{})
		if err != nil {
			return err
		}
		return nil
	}

	// Mirror endpoints for the gateway already exist so they need to be
	// updated. As mentioned above, these endpoints are required for the probe
	// worker and therefore should never be put in a not ready state.
	_, err = rcsw.localAPIClient.Client.CoreV1().Endpoints(endpoints.Namespace).Update(ctx, endpoints, metav1.UpdateOptions{})
	return err
}

// handleCreateOrUpdateEndpoints forwards the call to
// createOrUpdateHeadlessEndpoints when adding/updating exported headless
// endpoints. Otherwise, it handles updates to endpoints to check if they've
// become empty/filled since their creation, in order to empty/fill the
// mirrored endpoints as well
func (rcsw *RemoteClusterServiceWatcher) handleCreateOrUpdateEndpoints(
	ctx context.Context,
	exportedEndpoints *corev1.Endpoints,
) error {
	if isHeadlessEndpoints(exportedEndpoints, rcsw.log) {
		if rcsw.headlessServicesEnabled {
			return rcsw.createOrUpdateHeadlessEndpoints(ctx, exportedEndpoints)
		}
		return nil
	}

	localServiceName := rcsw.mirrorServiceName(exportedEndpoints.Name)
	ep, err := rcsw.localAPIClient.Endpoint().Lister().Endpoints(exportedEndpoints.Namespace).Get(localServiceName)
	if err != nil {
		return RetryableError{[]error{err}}
	}

	if (rcsw.isEmptyEndpoints(ep) && rcsw.isEmptyEndpoints(exportedEndpoints)) ||
		(!rcsw.isEmptyEndpoints(ep) && !rcsw.isEmptyEndpoints(exportedEndpoints)) {
		return nil
	}

	rcsw.log.Infof("Updating subsets for mirror endpoint %s/%s", exportedEndpoints.Namespace, exportedEndpoints.Name)
	if rcsw.isEmptyEndpoints(exportedEndpoints) {
		ep.Subsets = []corev1.EndpointSubset{}
	} else {
		exportedService, err := rcsw.remoteAPIClient.Svc().Lister().Services(exportedEndpoints.Namespace).Get(exportedEndpoints.Name)
		if err != nil {
			return RetryableError{[]error{
				fmt.Errorf("error retrieving exported service %s/%s: %w", exportedEndpoints.Namespace, exportedEndpoints.Name, err),
			}}
		}
		gatewayAddresses, err := rcsw.resolveGatewayAddress()
		if err != nil {
			return err
		}
		ports, err := rcsw.getEndpointsPorts(exportedService)
		if err != nil {
			return err
		}
		ep.Subsets = []corev1.EndpointSubset{
			{
				Addresses: gatewayAddresses,
				Ports:     ports,
			},
		}
	}
	return rcsw.updateMirrorEndpoints(ctx, ep)
}

// createMirrorEndpoints will create endpoints based off gateway liveness. If
// the gateway is not alive, then the addresses in each subset will be set to
// not ready.
func (rcsw *RemoteClusterServiceWatcher) createMirrorEndpoints(ctx context.Context, endpoints *corev1.Endpoints) error {
	rcsw.updateReadiness(endpoints)
	_, err := rcsw.localAPIClient.Client.CoreV1().Endpoints(endpoints.Namespace).Create(ctx, endpoints, metav1.CreateOptions{})
	if err != nil {
		return fmt.Errorf("failed to create mirror endpoints for %s/%s: %w", endpoints.Namespace, endpoints.Name, err)
	}
	return nil
}

// updateMirrorEndpoints will update endpoints based off gateway liveness. If
// the gateway is not alive, then the addresses in each subset will be set to
// not ready. Future calls to updateMirrorEndpoints can set the addresses back
// to ready if the gateway is alive.
func (rcsw *RemoteClusterServiceWatcher) updateMirrorEndpoints(ctx context.Context, endpoints *corev1.Endpoints) error {
	rcsw.updateReadiness(endpoints)
	_, err := rcsw.localAPIClient.Client.CoreV1().Endpoints(endpoints.Namespace).Update(ctx, endpoints, metav1.UpdateOptions{})
	if err != nil {
		return fmt.Errorf("failed to update mirror endpoints for %s/%s: %w", endpoints.Namespace, endpoints.Name, err)
	}
	return err
}

func (rcsw *RemoteClusterServiceWatcher) updateReadiness(endpoints *corev1.Endpoints) {
	if !rcsw.gatewayAlive {
		rcsw.log.Warnf("gateway for %s/%s does not have ready addresses; setting addresses to not ready", endpoints.Namespace, endpoints.Name)
		for i := range endpoints.Subsets {
			endpoints.Subsets[i].NotReadyAddresses = append(endpoints.Subsets[i].NotReadyAddresses, endpoints.Subsets[i].Addresses...)
			endpoints.Subsets[i].Addresses = nil
		}
	}
}

func (rcsw *RemoteClusterServiceWatcher) isExported(l map[string]string) bool {
	// Treat an empty selector as "Nothing" instead of "Everything" so that
	// when the selector field is unset, we don't export all Services.
	if rcsw.link.Spec.Selector == nil {
		return false
	}
	if len(rcsw.link.Spec.Selector.MatchExpressions)+len(rcsw.link.Spec.Selector.MatchLabels) == 0 {
		return false
	}
	selector, err := metav1.LabelSelectorAsSelector(rcsw.link.Spec.Selector)
	if err != nil {
		rcsw.log.Errorf("Invalid selector: %s", err)
		return false
	}
	return selector.Matches(labels.Set(l))
}

func (rcsw *RemoteClusterServiceWatcher) isRemoteDiscovery(l map[string]string) bool {
	// Treat an empty remoteDiscoverySelector as "Nothing" instead of
	// "Everything" so that when the remoteDiscoverySelector field is unset, we
	// don't export all Services.
	if rcsw.link.Spec.RemoteDiscoverySelector == nil {
		return false
	}
	if len(rcsw.link.Spec.RemoteDiscoverySelector.MatchExpressions)+len(rcsw.link.Spec.RemoteDiscoverySelector.MatchLabels) == 0 {
		return false
	}
	remoteDiscoverySelector, err := metav1.LabelSelectorAsSelector(rcsw.link.Spec.RemoteDiscoverySelector)
	if err != nil {
		rcsw.log.Errorf("Invalid selector: %s", err)
		return false
	}

	return remoteDiscoverySelector.Matches(labels.Set(l))
}

func (rcsw *RemoteClusterServiceWatcher) isFederatedServiceMember(l map[string]string) bool {
	// Treat an empty federatedServiceSelector as "Nothing" instead of
	// "Everything" so that when the federatedServiceSelector field is unset, we
	// don't export all Services.
	if rcsw.link.Spec.FederatedServiceSelector == nil {
		return false
	}
	if len(rcsw.link.Spec.FederatedServiceSelector.MatchExpressions)+len(rcsw.link.Spec.FederatedServiceSelector.MatchLabels) == 0 {
		return false
	}
	federatedServiceSelector, err := metav1.LabelSelectorAsSelector(rcsw.link.Spec.FederatedServiceSelector)
	if err != nil {
		rcsw.log.Errorf("Invalid selector: %s", err)
		return false
	}

	return federatedServiceSelector.Matches(labels.Set(l))
}

func (rcsw *RemoteClusterServiceWatcher) updateLinkMirrorStatus(remoteName, namespace string, condition v1alpha3.LinkCondition) {
	if rcsw.link.Spec.TargetClusterName == "" {
		// The local cluster has no Link resource.
		return
	}
	link, err := rcsw.linksAPIClient.L5dClient.LinkV1alpha3().Links(rcsw.link.GetNamespace()).Get(context.Background(), rcsw.link.Name, metav1.GetOptions{})
	if err != nil {
		rcsw.log.Errorf("Failed to get link %s/%s: %s", rcsw.link.Namespace, rcsw.link.Name, err)
		return
	}
	link.Status.MirrorServices = updateServiceStatus(remoteName, namespace, condition, link.Status.MirrorServices)
	rcsw.patchLinkStatus(link.Status)
}

func (rcsw *RemoteClusterServiceWatcher) updateLinkFederatedStatus(remoteName, namespace string, condition v1alpha3.LinkCondition) {
	if rcsw.link.Spec.TargetClusterName == "" {
		// The local cluster has no Link resource.
		return
	}
	link, err := rcsw.linksAPIClient.L5dClient.LinkV1alpha3().Links(rcsw.link.GetNamespace()).Get(context.Background(), rcsw.link.Name, metav1.GetOptions{})
	if err != nil {
		rcsw.log.Errorf("Failed to get link %s/%s: %s", rcsw.link.Namespace, rcsw.link.Name, err)
	}
	link.Status.FederatedServices = updateServiceStatus(remoteName, namespace, condition, link.Status.FederatedServices)
	rcsw.patchLinkStatus(link.Status)
}

func (rcsw *RemoteClusterServiceWatcher) deleteLinkMirrorStatus(remoteName, namespace string) {
	if rcsw.link.Spec.TargetClusterName == "" {
		// The local cluster has no Link resource.
		return
	}
	link, err := rcsw.linksAPIClient.L5dClient.LinkV1alpha3().Links(rcsw.link.GetNamespace()).Get(context.Background(), rcsw.link.Name, metav1.GetOptions{})
	if err != nil {
		rcsw.log.Errorf("Failed to get link %s/%s: %s", rcsw.link.Namespace, rcsw.link.Name, err)
	}
	link.Status.MirrorServices = deleteServiceStatus(remoteName, namespace, link.Status.MirrorServices)
	rcsw.patchLinkStatus(link.Status)
}

func (rcsw *RemoteClusterServiceWatcher) deleteLinkFederatedStatus(remoteName, namespace string) {
	if rcsw.link.Spec.TargetClusterName == "" {
		// The local cluster has no Link resource.
		return
	}
	link, err := rcsw.linksAPIClient.L5dClient.LinkV1alpha3().Links(rcsw.link.GetNamespace()).Get(context.Background(), rcsw.link.Name, metav1.GetOptions{})
	if err != nil {
		rcsw.log.Errorf("Failed to get link %s/%s: %s", rcsw.link.Namespace, rcsw.link.Name, err)
	}
	link.Status.FederatedServices = deleteServiceStatus(remoteName, namespace, link.Status.FederatedServices)
	rcsw.patchLinkStatus(link.Status)
}

func (rcsw *RemoteClusterServiceWatcher) patchLinkStatus(status v1alpha3.LinkStatus) {
	rcsw.log.Infof("patching link status %s/%s", rcsw.link.Namespace, rcsw.link.Name)
	statusBytes, err := json.Marshal(status)
	if err != nil {
		rcsw.log.Errorf("Failed to marshal link status: %s", err)
	}
	_, err = rcsw.linksAPIClient.L5dClient.LinkV1alpha3().Links(rcsw.link.GetNamespace()).Patch(
		context.Background(),
		rcsw.link.Name,
		types.MergePatchType,
		[]byte(fmt.Sprintf(`{"status": %s}`, string(statusBytes))),
		metav1.PatchOptions{},
		"status",
	)
	if err != nil {
		rcsw.log.Errorf("Failed to patch link status %s/%s: %s", rcsw.link.Namespace, rcsw.link.Name, err)
	}
}

func updateServiceStatus(remoteName, namespace string, condition v1alpha3.LinkCondition, statuses []v1alpha3.ServiceStatus) []v1alpha3.ServiceStatus {
	foundStatus := false
	for i, status := range statuses {
		if status.RemoteRef.Name == remoteName && status.RemoteRef.Namespace == namespace {
			foundStatus = true
			status.Conditions = []v1alpha3.LinkCondition{condition}
			statuses[i] = status
		}
	}
	if !foundStatus {
		statuses = append(statuses, v1alpha3.ServiceStatus{
			ControllerName: "linkerd.io/service-mirror",
			RemoteRef: v1alpha3.ObjectRef{
				Name:      remoteName,
				Namespace: namespace,
				Kind:      "Service",
				Group:     corev1.GroupName,
			},
			Conditions: []v1alpha3.LinkCondition{condition},
		})
	}
	return statuses
}

func deleteServiceStatus(remoteName, namespace string, statuses []v1alpha3.ServiceStatus) []v1alpha3.ServiceStatus {
	newStatuses := make([]v1alpha3.ServiceStatus, 0)
	for _, status := range statuses {
		if status.RemoteRef.Name == remoteName && status.RemoteRef.Namespace == namespace {
			continue
		}
		newStatuses = append(newStatuses, status)
	}
	return newStatuses
}

func mirrorStatusCondition(success bool, reason string, message string, localRef *corev1.Service) v1alpha3.LinkCondition {
	status := metav1.ConditionTrue
	if !success {
		status = metav1.ConditionFalse
	}
	condition := v1alpha3.LinkCondition{
		LastTransitionTime: metav1.Now(),
		Message:            message,
		Reason:             reason,
		Status:             status,
		Type:               "Mirrored",
	}
	if localRef != nil {
		condition.LocalRef = &v1alpha3.ObjectRef{
			Name:      localRef.Name,
			Namespace: localRef.Namespace,
			Kind:      "Service",
			Group:     corev1.GroupName,
		}
	}
	return condition
}
