/*
Copyright 2025 The KubeFleet Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package e2e

import (
	"fmt"
	"strings"
	"time"

	"github.com/google/go-cmp/cmp"
	. "github.com/onsi/ginkgo/v2"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	placementv1beta1 "go.goms.io/fleet/apis/placement/v1beta1"
	"go.goms.io/fleet/pkg/controllers/workapplier"
	scheduler "go.goms.io/fleet/pkg/scheduler/framework"
	"go.goms.io/fleet/pkg/utils/condition"
	"go.goms.io/fleet/test/e2e/framework"
)

func validateWorkNamespaceOnCluster(cluster *framework.Cluster, name types.NamespacedName) error {
	ns := &corev1.Namespace{}
	if err := cluster.KubeClient.Get(ctx, name, ns); err != nil {
		return err
	}

	// Use the object created in the hub cluster as reference; this helps to avoid the trouble
	// of having to ignore default fields in the spec.
	wantNS := &corev1.Namespace{}
	if err := hubClient.Get(ctx, name, wantNS); err != nil {
		return err
	}

	if diff := cmp.Diff(
		ns, wantNS,
		ignoreNamespaceStatusField,
		ignoreObjectMetaAutoGeneratedFields,
		ignoreObjectMetaAnnotationField,
	); diff != "" {
		return fmt.Errorf("work namespace diff (-got, +want): %s", diff)
	}
	return nil
}

func validateAnnotationOfWorkNamespaceOnCluster(cluster *framework.Cluster, wantAnnotations map[string]string) error {
	workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
	ns := &corev1.Namespace{}
	if err := cluster.KubeClient.Get(ctx, types.NamespacedName{Name: workNamespaceName}, ns); err != nil {
		return err
	}

	for k, v := range wantAnnotations {
		if ns.Annotations[k] != v {
			return fmt.Errorf("work namespace annotation %s got %s, want %s", k, ns.Annotations[k], v)
		}
	}
	return nil
}

func validateNamespaceNoAnnotationOnCluster(cluster *framework.Cluster, key string) error {
	workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
	ns := &corev1.Namespace{}
	if err := cluster.KubeClient.Get(ctx, types.NamespacedName{Name: workNamespaceName}, ns); err != nil {
		return err
	}

	if _, exist := ns.Annotations[key]; !exist {
		return nil
	}
	return fmt.Errorf("namespace `%s` annotation still got  key `%s`, value `%s`", workNamespaceName, key, ns.Annotations[key])
}

func validateConfigMapOnCluster(cluster *framework.Cluster, name types.NamespacedName) error {
	configMap := &corev1.ConfigMap{}
	if err := cluster.KubeClient.Get(ctx, name, configMap); err != nil {
		return err
	}

	// Use the object created in the hub cluster as reference.
	wantConfigMap := &corev1.ConfigMap{}
	if err := hubClient.Get(ctx, name, wantConfigMap); err != nil {
		return err
	}

	if diff := cmp.Diff(
		configMap, wantConfigMap,
		ignoreObjectMetaAutoGeneratedFields,
		ignoreObjectMetaAnnotationField,
	); diff != "" {
		return fmt.Errorf("app config map diff (-got, +want): %s", diff)
	}

	return nil
}

func validateAnnotationOfConfigMapOnCluster(cluster *framework.Cluster, wantAnnotations map[string]string) error {
	workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
	appConfigMapName := fmt.Sprintf(appConfigMapNameTemplate, GinkgoParallelProcess())

	configMap := &corev1.ConfigMap{}
	if err := cluster.KubeClient.Get(ctx, types.NamespacedName{Namespace: workNamespaceName, Name: appConfigMapName}, configMap); err != nil {
		return err
	}

	for k, v := range wantAnnotations {
		if configMap.Annotations[k] != v {
			return fmt.Errorf("app config map annotation %s got %s, want %s", k, configMap.Annotations[k], v)
		}
	}
	return nil
}

func validateConfigMapNoAnnotationKeyOnCluster(cluster *framework.Cluster, key string) error {
	workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
	appConfigMapName := fmt.Sprintf(appConfigMapNameTemplate, GinkgoParallelProcess())

	configMap := &corev1.ConfigMap{}
	if err := cluster.KubeClient.Get(ctx, types.NamespacedName{Namespace: workNamespaceName, Name: appConfigMapName}, configMap); err != nil {
		return err
	}

	if _, exist := configMap.Annotations[key]; !exist {
		return nil
	}
	return fmt.Errorf("app config map annotation still got  key `%s`, value `%s`", key, configMap.Annotations[key])
}

func workNamespaceAndConfigMapPlacedOnClusterActual(cluster *framework.Cluster) func() error {
	workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
	appConfigMapName := fmt.Sprintf(appConfigMapNameTemplate, GinkgoParallelProcess())

	return func() error {
		if err := validateWorkNamespaceOnCluster(cluster, types.NamespacedName{Name: workNamespaceName}); err != nil {
			return err
		}

		return validateConfigMapOnCluster(cluster, types.NamespacedName{Namespace: workNamespaceName, Name: appConfigMapName})
	}
}

func configMapPlacedOnClusterActual(cluster *framework.Cluster, wantConfigMap *corev1.ConfigMap) func() error {
	workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
	appConfigMapName := fmt.Sprintf(appConfigMapNameTemplate, GinkgoParallelProcess())
	cmName := types.NamespacedName{Namespace: workNamespaceName, Name: appConfigMapName}
	return func() error {
		configMap := &corev1.ConfigMap{}
		if err := cluster.KubeClient.Get(ctx, cmName, configMap); err != nil {
			return err
		}

		if diff := cmp.Diff(
			configMap, wantConfigMap,
			ignoreObjectMetaAutoGeneratedFields,
			ignoreObjectMetaAnnotationField,
		); diff != "" {
			return fmt.Errorf("app config map diff (-got, +want): %s on cluster %s", diff, cluster.ClusterName)
		}
		return nil
	}
}

func workNamespacePlacedOnClusterActual(cluster *framework.Cluster) func() error {
	workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())

	return func() error {
		return validateWorkNamespaceOnCluster(cluster, types.NamespacedName{Name: workNamespaceName})
	}
}

func placementRolloutCompletedConditions(placementKey types.NamespacedName, generation int64, hasOverride bool) []metav1.Condition {
	if placementKey.Namespace == "" {
		return crpRolloutCompletedConditions(generation, hasOverride)
	} else {
		return rpRolloutCompletedConditions(generation, hasOverride)
	}
}

func placementScheduledConditions(placementKey types.NamespacedName, generation int64) []metav1.Condition {
	if placementKey.Namespace == "" {
		return crpScheduledConditions(generation)
	} else {
		return rpScheduledConditions(generation)
	}
}

func placementSchedulePartiallyFailedConditions(placementKey types.NamespacedName, generation int64) []metav1.Condition {
	if placementKey.Namespace == "" {
		return crpSchedulePartiallyFailedConditions(generation)
	} else {
		return rpSchedulePartiallyFailedConditions(generation)
	}
}

func placementScheduleFailedConditions(placementKey types.NamespacedName, generation int64) []metav1.Condition {
	if placementKey.Namespace == "" {
		return crpScheduleFailedConditions(generation)
	} else {
		return rpScheduleFailedConditions(generation)
	}
}

func placementOverrideFailedConditions(placementKey types.NamespacedName, generation int64) []metav1.Condition {
	if placementKey.Namespace == "" {
		return crpOverrideFailedConditions(generation)
	} else {
		return rpOverrideFailedConditions(generation)
	}
}

func placementWorkSynchronizedFailedConditions(placementKey types.NamespacedName, generation int64, hasOverrides bool) []metav1.Condition {
	if placementKey.Namespace == "" {
		return crpWorkSynchronizedFailedConditions(generation, hasOverrides)
	} else {
		return rpWorkSynchronizedFailedConditions(generation, hasOverrides)
	}
}

func placementRolloutStuckConditions(placementKey types.NamespacedName, generation int64) []metav1.Condition {
	if placementKey.Namespace == "" {
		return crpRolloutStuckConditions(generation)
	} else {
		return rpRolloutStuckConditions(generation)
	}
}

func rpRolloutCompletedConditions(generation int64, hasOverride bool) []metav1.Condition {
	overrideConditionReason := condition.OverrideNotSpecifiedReason
	if hasOverride {
		overrideConditionReason = condition.OverriddenSucceededReason
	}
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             overrideConditionReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.WorkSynchronizedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementAppliedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ApplySucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementAvailableConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.AvailableReason,
			ObservedGeneration: generation,
		},
	}
}

func rpSchedulePartiallyFailedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionFalse,
			ObservedGeneration: generation,
			Reason:             scheduler.NotFullyScheduledReason,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.OverrideNotSpecifiedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.WorkSynchronizedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementAppliedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ApplySucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementAvailableConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.AvailableReason,
			ObservedGeneration: generation,
		},
	}
}

func rpScheduleFailedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionFalse,
			ObservedGeneration: generation,
			Reason:             scheduler.NotFullyScheduledReason,
		},
	}
}

func rpScheduledConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
	}
}

func rpRolloutStuckConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			ObservedGeneration: generation,
			Reason:             scheduler.FullyScheduledReason,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.RolloutNotStartedYetReason,
			ObservedGeneration: generation,
		},
	}
}

func rpOverrideFailedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.OverriddenFailedReason,
			ObservedGeneration: generation,
		},
	}
}

func rpWorkSynchronizedFailedConditions(generation int64, hasOverrides bool) []metav1.Condition {
	overridenCondReason := condition.OverrideNotSpecifiedReason
	if hasOverrides {
		overridenCondReason = condition.OverriddenSucceededReason
	}
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             overridenCondReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.WorkNotSynchronizedYetReason,
			ObservedGeneration: generation,
		},
	}
}

func crpScheduledConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			ObservedGeneration: generation,
			Reason:             scheduler.FullyScheduledReason,
		},
	}
}

func crpScheduleFailedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionFalse,
			ObservedGeneration: generation,
			Reason:             scheduler.NotFullyScheduledReason,
		},
	}
}

func crpSchedulePartiallyFailedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionFalse,
			ObservedGeneration: generation,
			Reason:             scheduler.NotFullyScheduledReason,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.OverrideNotSpecifiedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.WorkSynchronizedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementAppliedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ApplySucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementAvailableConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.AvailableReason,
			ObservedGeneration: generation,
		},
	}
}

func crpRolloutStuckConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.RolloutNotStartedYetReason,
			ObservedGeneration: generation,
		},
	}
}

func crpRolloutPendingDueToExternalStrategyConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionUnknown,
			Reason:             condition.RolloutControlledByExternalControllerReason,
			ObservedGeneration: generation,
		},
	}
}

func rpAppliedFailedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.OverrideNotSpecifiedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.WorkSynchronizedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementAppliedConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.ApplyFailedReason,
			ObservedGeneration: generation,
		},
	}
}

func rpDiffReportingFailedConditions(generation int64, hasOverride bool) []metav1.Condition {
	overrideConditionReason := condition.OverrideNotSpecifiedReason
	if hasOverride {
		overrideConditionReason = condition.OverriddenSucceededReason
	}
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             overrideConditionReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.WorkSynchronizedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementDiffReportedConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.DiffReportedStatusFalseReason,
			ObservedGeneration: generation,
		},
	}
}

func crpAppliedFailedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.OverrideNotSpecifiedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.WorkSynchronizedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementAppliedConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.ApplyFailedReason,
			ObservedGeneration: generation,
		},
	}
}

func crpNotAvailableConditions(generation int64, hasOverride bool) []metav1.Condition {
	overrideConditionReason := condition.OverrideNotSpecifiedReason
	if hasOverride {
		overrideConditionReason = condition.OverriddenSucceededReason
	}
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             overrideConditionReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.WorkSynchronizedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementAppliedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ApplySucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementAvailableConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.NotAvailableYetReason,
			ObservedGeneration: generation,
		},
	}
}

func rpDiffReportedConditions(generation int64, hasOverride bool) []metav1.Condition {
	overrideConditionReason := condition.OverrideNotSpecifiedReason
	if hasOverride {
		overrideConditionReason = condition.OverriddenSucceededReason
	}
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             overrideConditionReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.WorkSynchronizedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourcePlacementDiffReportedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.DiffReportedStatusTrueReason,
			ObservedGeneration: generation,
		},
	}
}

func crpDiffReportedConditions(generation int64, hasOverride bool) []metav1.Condition {
	overrideConditionReason := condition.OverrideNotSpecifiedReason
	if hasOverride {
		overrideConditionReason = condition.OverriddenSucceededReason
	}
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             overrideConditionReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.WorkSynchronizedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementDiffReportedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.DiffReportedStatusTrueReason,
			ObservedGeneration: generation,
		},
	}
}

func crpDiffReportingFailedConditions(generation int64, hasOverride bool) []metav1.Condition {
	overrideConditionReason := condition.OverrideNotSpecifiedReason
	if hasOverride {
		overrideConditionReason = condition.OverriddenSucceededReason
	}
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             overrideConditionReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.WorkSynchronizedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementDiffReportedConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.DiffReportedStatusFalseReason,
			ObservedGeneration: generation,
		},
	}
}

func crpRolloutCompletedConditions(generation int64, hasOverride bool) []metav1.Condition {
	overrideConditionReason := condition.OverrideNotSpecifiedReason
	if hasOverride {
		overrideConditionReason = condition.OverriddenSucceededReason
	}
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             overrideConditionReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.WorkSynchronizedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementAppliedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ApplySucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementAvailableConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.AvailableReason,
			ObservedGeneration: generation,
		},
	}
}

func crpWorkSynchronizedFailedConditions(generation int64, hasOverrides bool) []metav1.Condition {
	overridenCondReason := condition.OverrideNotSpecifiedReason
	if hasOverrides {
		overridenCondReason = condition.OverriddenSucceededReason
	}
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             overridenCondReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementWorkSynchronizedConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.WorkNotSynchronizedYetReason,
			ObservedGeneration: generation,
		},
	}
}

func crpOverrideFailedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             scheduler.FullyScheduledReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterResourcePlacementOverriddenConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.OverriddenFailedReason,
			ObservedGeneration: generation,
		},
	}
}

func perClusterSyncPendingConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.PerClusterScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ScheduleSucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterRolloutStartedConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.RolloutNotStartedYetReason,
			ObservedGeneration: generation,
		},
	}
}

func perClusterRolloutUnknownConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.PerClusterScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ScheduleSucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterRolloutStartedConditionType),
			Status:             metav1.ConditionUnknown,
			Reason:             condition.RolloutStartedUnknownReason,
			ObservedGeneration: generation,
		},
	}
}

func perClusterApplyFailedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.PerClusterScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ScheduleSucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.OverrideNotSpecifiedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.AllWorkSyncedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterAppliedConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.WorkNotAppliedReason,
			ObservedGeneration: generation,
		},
	}
}

func perClusterDiffReportedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.PerClusterScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ScheduleSucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.OverrideNotSpecifiedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.AllWorkSyncedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourceBindingDiffReported),
			Status:             metav1.ConditionTrue,
			Reason:             condition.AllWorkDiffReportedReason,
			ObservedGeneration: generation,
		},
	}
}

func perClusterDiffReportingFailedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.PerClusterScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ScheduleSucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.OverrideNotSpecifiedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.AllWorkSyncedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ResourceBindingDiffReported),
			Status:             metav1.ConditionFalse,
			Reason:             condition.WorkNotDiffReportedReason,
			ObservedGeneration: generation,
		},
	}
}

func perClusterRolloutCompletedConditions(generation int64, resourceIsTrackable bool, hasOverride bool) []metav1.Condition {
	availableConditionReason := condition.WorkNotAvailabilityTrackableReason
	if resourceIsTrackable {
		availableConditionReason = condition.AllWorkAvailableReason
	}
	overrideConditionReason := condition.OverrideNotSpecifiedReason
	if hasOverride {
		overrideConditionReason = condition.OverriddenSucceededReason
	}

	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.PerClusterScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ScheduleSucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             overrideConditionReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterWorkSynchronizedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.AllWorkSyncedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterAppliedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.AllWorkAppliedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterAvailableConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             availableConditionReason,
			ObservedGeneration: generation,
		},
	}
}

func perClusterScheduleFailedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.PerClusterScheduledConditionType),
			Status:             metav1.ConditionFalse,
			ObservedGeneration: generation,
			Reason:             condition.ResourceScheduleFailedReason,
		},
	}
}

func perClusterOverrideFailedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.PerClusterScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ScheduleSucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterOverriddenConditionType),
			Status:             metav1.ConditionFalse,
			ObservedGeneration: generation,
			Reason:             condition.OverriddenFailedReason,
		},
	}
}

func perClusterWorkSynchronizedFailedConditions(generation int64, hasOverrides bool) []metav1.Condition {
	overridenCondReason := condition.OverrideNotSpecifiedReason
	if hasOverrides {
		overridenCondReason = condition.OverriddenSucceededReason
	}
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.PerClusterScheduledConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ScheduleSucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterRolloutStartedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.RolloutStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.PerClusterOverriddenConditionType),
			Status:             metav1.ConditionTrue,
			ObservedGeneration: generation,
			Reason:             overridenCondReason,
		},
		{
			Type:               string(placementv1beta1.PerClusterWorkSynchronizedConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.SyncWorkFailedReason,
			ObservedGeneration: generation,
		},
	}
}

func workResourceIdentifiers() []placementv1beta1.ResourceIdentifier {
	workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
	appConfigMapName := fmt.Sprintf(appConfigMapNameTemplate, GinkgoParallelProcess())

	return []placementv1beta1.ResourceIdentifier{
		{
			Kind:    "Namespace",
			Name:    workNamespaceName,
			Version: "v1",
		},
		{
			Kind:      "ConfigMap",
			Name:      appConfigMapName,
			Version:   "v1",
			Namespace: workNamespaceName,
		},
	}
}

func workNamespaceIdentifiers() []placementv1beta1.ResourceIdentifier {
	workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
	return []placementv1beta1.ResourceIdentifier{
		{
			Kind:    "Namespace",
			Name:    workNamespaceName,
			Version: "v1",
		},
	}
}

func appConfigMapIdentifiers() []placementv1beta1.ResourceIdentifier {
	workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
	appConfigMapName := fmt.Sprintf(appConfigMapNameTemplate, GinkgoParallelProcess())
	return []placementv1beta1.ResourceIdentifier{
		{
			Kind:      "ConfigMap",
			Name:      appConfigMapName,
			Version:   "v1",
			Namespace: workNamespaceName,
		},
	}
}

func multipleAppConfigMapIdentifiers() []placementv1beta1.ResourceIdentifier {
	workNamespaceName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
	appConfigMapName1 := fmt.Sprintf(appConfigMapNameTemplate, GinkgoParallelProcess())
	appConfigMapName2 := fmt.Sprintf(appConfigMapNameTemplate+"-%d", GinkgoParallelProcess(), 2)
	return []placementv1beta1.ResourceIdentifier{
		{
			Kind:      "ConfigMap",
			Name:      appConfigMapName1,
			Version:   "v1",
			Namespace: workNamespaceName,
		},
		{
			Kind:      "ConfigMap",
			Name:      appConfigMapName2,
			Version:   "v1",
			Namespace: workNamespaceName,
		},
	}
}

func crpStatusWithOverrideUpdatedActual(
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters []string,
	wantObservedResourceIndex string,
	wantClusterResourceOverrides []string,
	wantResourceOverrides []placementv1beta1.NamespacedName) func() error {
	crpKey := types.NamespacedName{Name: fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())}
	return placementStatusWithOverrideUpdatedActual(crpKey, wantSelectedResourceIdentifiers, wantSelectedClusters,
		wantObservedResourceIndex, wantClusterResourceOverrides, wantResourceOverrides)
}

func rpStatusWithOverrideUpdatedActual(
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters []string,
	wantObservedResourceIndex string,
	wantClusterResourceOverrides []string,
	wantResourceOverrides []placementv1beta1.NamespacedName) func() error {
	rpKey := types.NamespacedName{Name: fmt.Sprintf(rpNameTemplate, GinkgoParallelProcess()), Namespace: appNamespace().Name}
	return placementStatusWithOverrideUpdatedActual(rpKey, wantSelectedResourceIdentifiers, wantSelectedClusters,
		wantObservedResourceIndex, wantClusterResourceOverrides, wantResourceOverrides)
}

func placementStatusWithOverrideUpdatedActual(
	placementKey types.NamespacedName,
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters []string,
	wantObservedResourceIndex string,
	wantClusterResourceOverrides []string,
	wantResourceOverrides []placementv1beta1.NamespacedName,
) func() error {
	return func() error {
		placement, err := retrievePlacement(placementKey)
		if err != nil {
			return err
		}
		hasOverride := len(wantResourceOverrides) > 0 || len(wantClusterResourceOverrides) > 0
		var wantPlacementStatus []placementv1beta1.PerClusterPlacementStatus
		for _, name := range wantSelectedClusters {
			wantPlacementStatus = append(wantPlacementStatus, placementv1beta1.PerClusterPlacementStatus{
				ClusterName:                        name,
				Conditions:                         perClusterRolloutCompletedConditions(placement.GetGeneration(), true, hasOverride),
				ApplicableResourceOverrides:        wantResourceOverrides,
				ApplicableClusterResourceOverrides: wantClusterResourceOverrides,
				ObservedResourceIndex:              wantObservedResourceIndex,
			})
		}
		wantStatus := &placementv1beta1.PlacementStatus{
			Conditions:                  placementRolloutCompletedConditions(placementKey, placement.GetGeneration(), hasOverride),
			PerClusterPlacementStatuses: wantPlacementStatus,
			SelectedResources:           wantSelectedResourceIdentifiers,
			ObservedResourceIndex:       wantObservedResourceIndex,
		}
		if diff := cmp.Diff(placement.GetPlacementStatus(), wantStatus, placementStatusCmpOptions...); diff != "" {
			return fmt.Errorf("Placement status diff (-got, +want): %s", diff)
		}
		return nil
	}
}

func namespaceAccessibleCRPStatusUpdatedActual(wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier, wantSelectedClusters, wantUnselectedClusters []string, wantObservedResourceIndex string, statusSyncedConditionStatus metav1.ConditionStatus, isResourceSelectorInvalid bool) func() error {
	crpKey := types.NamespacedName{Name: fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())}
	return customizedCRPStatusUpdatedActualWithStatusSynced(crpKey, wantSelectedResourceIdentifiers, wantSelectedClusters, wantUnselectedClusters, wantObservedResourceIndex, true, statusSyncedConditionStatus, isResourceSelectorInvalid)
}

func crpStatusUpdatedActual(wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier, wantSelectedClusters, wantUnselectedClusters []string, wantObservedResourceIndex string) func() error {
	crpKey := types.NamespacedName{Name: fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())}
	return customizedPlacementStatusUpdatedActual(crpKey, wantSelectedResourceIdentifiers, wantSelectedClusters, wantUnselectedClusters, wantObservedResourceIndex, true)
}

func rpStatusUpdatedActual(wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier, wantSelectedClusters, wantUnselectedClusters []string, wantObservedResourceIndex string) func() error {
	rpKey := types.NamespacedName{Name: fmt.Sprintf(rpNameTemplate, GinkgoParallelProcess()), Namespace: appNamespace().Name}
	return customizedPlacementStatusUpdatedActual(rpKey, wantSelectedResourceIdentifiers, wantSelectedClusters, wantUnselectedClusters, wantObservedResourceIndex, true)
}

func crpStatusWithOverrideUpdatedFailedActual(
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters []string,
	wantObservedResourceIndex string,
	wantClusterResourceOverrides []string,
	wantResourceOverrides []placementv1beta1.NamespacedName) func() error {
	crpKey := types.NamespacedName{Name: fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())}
	return placementStatusWithOverrideUpdatedFailedActual(crpKey, wantSelectedResourceIdentifiers, wantSelectedClusters,
		wantObservedResourceIndex, wantClusterResourceOverrides, wantResourceOverrides)
}

func rpStatusWithOverrideUpdatedFailedActual(
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters []string,
	wantObservedResourceIndex string,
	wantClusterResourceOverrides []string,
	wantResourceOverrides []placementv1beta1.NamespacedName) func() error {
	rpKey := types.NamespacedName{Name: fmt.Sprintf(rpNameTemplate, GinkgoParallelProcess()), Namespace: appNamespace().Name}
	return placementStatusWithOverrideUpdatedFailedActual(rpKey, wantSelectedResourceIdentifiers, wantSelectedClusters,
		wantObservedResourceIndex, wantClusterResourceOverrides, wantResourceOverrides)
}

func placementStatusWithOverrideUpdatedFailedActual(
	placementKey types.NamespacedName,
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters []string,
	wantObservedResourceIndex string,
	wantClusterResourceOverrides []string,
	wantResourceOverrides []placementv1beta1.NamespacedName,
) func() error {
	return func() error {
		placement, err := retrievePlacement(placementKey)
		if err != nil {
			return err
		}
		var wantPlacementStatus []placementv1beta1.PerClusterPlacementStatus
		for _, name := range wantSelectedClusters {
			wantPlacementStatus = append(wantPlacementStatus, placementv1beta1.PerClusterPlacementStatus{
				ClusterName:                        name,
				Conditions:                         perClusterOverrideFailedConditions(placement.GetGeneration()),
				ApplicableResourceOverrides:        wantResourceOverrides,
				ApplicableClusterResourceOverrides: wantClusterResourceOverrides,
				ObservedResourceIndex:              wantObservedResourceIndex,
			})
		}
		wantStatus := &placementv1beta1.PlacementStatus{
			Conditions:                  placementOverrideFailedConditions(placementKey, placement.GetGeneration()),
			PerClusterPlacementStatuses: wantPlacementStatus,
			SelectedResources:           wantSelectedResourceIdentifiers,
			ObservedResourceIndex:       wantObservedResourceIndex,
		}
		if diff := cmp.Diff(placement.GetPlacementStatus(), wantStatus, placementStatusCmpOptions...); diff != "" {
			return fmt.Errorf("Placement status diff (-got, +want): %s", diff)
		}
		return nil
	}
}

func rpStatusWithWorkSynchronizedUpdatedFailedActual(
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters []string,
	wantObservedResourceIndex string,
	wantClusterResourceOverrides []string,
	wantResourceOverrides []placementv1beta1.NamespacedName,
) func() error {
	rpKey := types.NamespacedName{Name: fmt.Sprintf(rpNameTemplate, GinkgoParallelProcess()), Namespace: appNamespace().Name}
	return placementStatusWithWorkSynchronizedUpdatedFailedActual(rpKey, wantSelectedResourceIdentifiers, wantSelectedClusters,
		wantObservedResourceIndex, wantClusterResourceOverrides, wantResourceOverrides)
}

func placementStatusWithWorkSynchronizedUpdatedFailedActual(
	placementKey types.NamespacedName,
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters []string,
	wantObservedResourceIndex string,
	wantClusterResourceOverrides []string,
	wantResourceOverrides []placementv1beta1.NamespacedName,
) func() error {
	return func() error {
		placement, err := retrievePlacement(placementKey)
		if err != nil {
			return err
		}
		var wantPlacementStatus []placementv1beta1.PerClusterPlacementStatus
		hasOverrides := len(wantResourceOverrides) > 0 || len(wantClusterResourceOverrides) > 0
		for _, name := range wantSelectedClusters {
			wantPlacementStatus = append(wantPlacementStatus, placementv1beta1.PerClusterPlacementStatus{
				ClusterName:                        name,
				Conditions:                         perClusterWorkSynchronizedFailedConditions(placement.GetGeneration(), hasOverrides),
				ApplicableResourceOverrides:        wantResourceOverrides,
				ApplicableClusterResourceOverrides: wantClusterResourceOverrides,
				ObservedResourceIndex:              wantObservedResourceIndex,
			})
		}
		wantStatus := &placementv1beta1.PlacementStatus{
			Conditions:                  placementWorkSynchronizedFailedConditions(placementKey, placement.GetGeneration(), hasOverrides),
			PerClusterPlacementStatuses: wantPlacementStatus,
			SelectedResources:           wantSelectedResourceIdentifiers,
			ObservedResourceIndex:       wantObservedResourceIndex,
		}
		if diff := cmp.Diff(placement.GetPlacementStatus(), wantStatus, placementStatusCmpOptions...); diff != "" {
			return fmt.Errorf("Placement status diff (-got, +want): %s", diff)
		}
		return nil
	}
}

func crpStatusWithWorkSynchronizedUpdatedFailedActual(
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters []string,
	wantObservedResourceIndex string,
	wantClusterResourceOverrides []string,
	wantResourceOverrides []placementv1beta1.NamespacedName) func() error {
	crpKey := types.NamespacedName{Name: fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())}
	return placementStatusWithWorkSynchronizedUpdatedFailedActual(crpKey, wantSelectedResourceIdentifiers, wantSelectedClusters,
		wantObservedResourceIndex, wantClusterResourceOverrides, wantResourceOverrides)
}

func crpStatusWithExternalStrategyActual(
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantObservedResourceIndex string,
	wantCRPRolloutCompleted bool,
	wantSelectedClusters []string,
	wantObservedResourceIndexPerCluster []string,
	wantRolloutCompletedPerCluster []bool,
	wantClusterResourceOverrides map[string][]string,
	wantResourceOverrides map[string][]placementv1beta1.NamespacedName,
) func() error {
	crpName := fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())
	nsName := fmt.Sprintf(workNamespaceNameTemplate, GinkgoParallelProcess())
	cmName := fmt.Sprintf(appConfigMapNameTemplate, GinkgoParallelProcess())

	return func() error {
		crp := &placementv1beta1.ClusterResourcePlacement{}
		if err := hubClient.Get(ctx, types.NamespacedName{Name: crpName}, crp); err != nil {
			return err
		}

		reportDiff := crp.Spec.Strategy.ApplyStrategy != nil && crp.Spec.Strategy.ApplyStrategy.Type == placementv1beta1.ApplyStrategyTypeReportDiff

		var wantPlacementStatus []placementv1beta1.PerClusterPlacementStatus
		crpHasOverrides := false
		for i, name := range wantSelectedClusters {
			if !wantRolloutCompletedPerCluster[i] {
				// No observed resource index for this cluster, assume rollout is still pending.
				wantPlacementStatus = append(wantPlacementStatus, placementv1beta1.PerClusterPlacementStatus{
					ClusterName:           name,
					Conditions:            perClusterRolloutUnknownConditions(crp.Generation),
					ObservedResourceIndex: wantObservedResourceIndexPerCluster[i],
				})
			} else {
				wantResourceOverrides, hasRO := wantResourceOverrides[name]
				wantClusterResourceOverrides, hasCRO := wantClusterResourceOverrides[name]
				hasOverrides := (hasRO && len(wantResourceOverrides) > 0) || (hasCRO && len(wantClusterResourceOverrides) > 0)
				if hasOverrides {
					crpHasOverrides = true
				}
				if reportDiff {
					wantPlacementStatus = append(wantPlacementStatus, placementv1beta1.PerClusterPlacementStatus{
						ClusterName:                        name,
						Conditions:                         perClusterDiffReportedConditions(crp.Generation),
						ApplicableResourceOverrides:        wantResourceOverrides,
						ApplicableClusterResourceOverrides: wantClusterResourceOverrides,
						ObservedResourceIndex:              wantObservedResourceIndexPerCluster[i],
						DiffedPlacements: []placementv1beta1.DiffedResourcePlacement{
							{
								ResourceIdentifier: placementv1beta1.ResourceIdentifier{
									Version: "v1",
									Kind:    "Namespace",
									Name:    nsName,
								},
								ObservedDiffs: []placementv1beta1.PatchDetail{
									{
										Path:       "/",
										ValueInHub: "(the whole object)",
									},
								},
							},
							{
								ResourceIdentifier: placementv1beta1.ResourceIdentifier{
									Version:   "v1",
									Kind:      "ConfigMap",
									Name:      cmName,
									Namespace: nsName,
								},
								ObservedDiffs: []placementv1beta1.PatchDetail{
									{
										Path:       "/",
										ValueInHub: "(the whole object)",
									},
								},
							},
						},
					})
				} else {
					wantPlacementStatus = append(wantPlacementStatus, placementv1beta1.PerClusterPlacementStatus{
						ClusterName:                        name,
						Conditions:                         perClusterRolloutCompletedConditions(crp.Generation, true, hasOverrides),
						ApplicableResourceOverrides:        wantResourceOverrides,
						ApplicableClusterResourceOverrides: wantClusterResourceOverrides,
						ObservedResourceIndex:              wantObservedResourceIndexPerCluster[i],
					})
				}
			}
		}

		wantStatus := placementv1beta1.PlacementStatus{
			PerClusterPlacementStatuses: wantPlacementStatus,
			SelectedResources:           wantSelectedResourceIdentifiers,
			ObservedResourceIndex:       wantObservedResourceIndex,
		}
		if wantCRPRolloutCompleted {
			if reportDiff {
				wantStatus.Conditions = crpDiffReportedConditions(crp.Generation, crpHasOverrides)
			} else {
				wantStatus.Conditions = crpRolloutCompletedConditions(crp.Generation, crpHasOverrides)
			}
		} else {
			wantStatus.Conditions = crpRolloutPendingDueToExternalStrategyConditions(crp.Generation)
		}

		if diff := cmp.Diff(crp.Status, wantStatus, placementStatusCmpOptions...); diff != "" {
			return fmt.Errorf("CRP status diff (-got, +want): %s", diff)
		}
		return nil
	}
}

func customizedPlacementStatusUpdatedActual(
	placementKey types.NamespacedName,
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters, wantUnselectedClusters []string,
	wantObservedResourceIndex string,
	resourceIsTrackable bool,
) func() error {
	return func() error {
		placement, err := retrievePlacement(placementKey)
		if err != nil {
			return fmt.Errorf("failed to get placement %s: %w", placementKey, err)
		}

		wantStatus := buildWantPlacementStatus(placementKey, placement.GetGeneration(), wantSelectedResourceIdentifiers, wantSelectedClusters, wantUnselectedClusters, wantObservedResourceIndex, resourceIsTrackable)
		if diff := cmp.Diff(placement.GetPlacementStatus(), wantStatus, placementStatusCmpOptions...); diff != "" {
			return fmt.Errorf("Placement status diff (-got, +want): %s", diff)
		}
		return nil
	}
}

func buildWantPlacementStatus(
	placementKey types.NamespacedName,
	placementGeneration int64,
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters, wantUnselectedClusters []string,
	wantObservedResourceIndex string,
	resourceIsTrackable bool,
) *placementv1beta1.PlacementStatus {
	wantPlacementStatus := []placementv1beta1.PerClusterPlacementStatus{}
	for _, name := range wantSelectedClusters {
		wantPlacementStatus = append(wantPlacementStatus, placementv1beta1.PerClusterPlacementStatus{
			ClusterName:           name,
			ObservedResourceIndex: wantObservedResourceIndex,
			Conditions:            perClusterRolloutCompletedConditions(placementGeneration, resourceIsTrackable, false),
		})
	}
	for i := 0; i < len(wantUnselectedClusters); i++ {
		wantPlacementStatus = append(wantPlacementStatus, placementv1beta1.PerClusterPlacementStatus{
			Conditions: perClusterScheduleFailedConditions(placementGeneration),
		})
	}

	var wantPlacementConditions []metav1.Condition
	if len(wantSelectedClusters) > 0 {
		wantPlacementConditions = placementRolloutCompletedConditions(placementKey, placementGeneration, false)
	} else {
		// We don't set the remaining resource conditions.
		wantPlacementConditions = placementScheduledConditions(placementKey, placementGeneration)
	}

	if len(wantUnselectedClusters) > 0 {
		if len(wantSelectedClusters) > 0 {
			wantPlacementConditions = placementSchedulePartiallyFailedConditions(placementKey, placementGeneration)
		} else {
			// we don't set the remaining resource conditions if there is no clusters to select
			wantPlacementConditions = placementScheduleFailedConditions(placementKey, placementGeneration)
		}
	}

	// Note that the placement controller will only keep decisions regarding unselected clusters for a placement if:
	//
	// * The placement is of the PickN placement type and the required N count cannot be fulfilled; or
	// * The placement is of the PickFixed placement type and the list of target clusters specified cannot be fulfilled.
	wantStatus := &placementv1beta1.PlacementStatus{
		Conditions:                  wantPlacementConditions,
		PerClusterPlacementStatuses: wantPlacementStatus,
		SelectedResources:           wantSelectedResourceIdentifiers,
		ObservedResourceIndex:       wantObservedResourceIndex,
	}
	return wantStatus
}

func customizedCRPStatusUpdatedActualWithStatusSynced(
	placementKey types.NamespacedName,
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters, wantUnselectedClusters []string,
	wantObservedResourceIndex string,
	resourceIsTrackable bool,
	statusSyncedConditionStatus metav1.ConditionStatus,
	isResourceSelectorInvalid bool,
) func() error {
	return func() error {
		placement, err := retrievePlacement(placementKey)
		if err != nil {
			return fmt.Errorf("failed to get placement %s: %w", placementKey, err)
		}

		wantStatus := buildWantCRPStatusWithStatusSynced(placementKey, placement.GetGeneration(), wantSelectedResourceIdentifiers, wantSelectedClusters, wantUnselectedClusters, wantObservedResourceIndex, resourceIsTrackable, statusSyncedConditionStatus, isResourceSelectorInvalid)
		if diff := cmp.Diff(placement.GetPlacementStatus(), wantStatus, placementStatusCmpOptions...); diff != "" {
			return fmt.Errorf("Placement status diff (-got, +want): %s", diff)
		}
		return nil
	}
}

func buildWantCRPStatusWithStatusSynced(
	placementKey types.NamespacedName,
	placementGeneration int64,
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	wantSelectedClusters, wantUnselectedClusters []string,
	wantObservedResourceIndex string,
	resourceIsTrackable bool,
	statusSyncedConditionStatus metav1.ConditionStatus,
	isResourceSelectorInvalid bool,
) *placementv1beta1.PlacementStatus {
	if isResourceSelectorInvalid {
		// when namespace resource selector is invalid, the observed generation doesn't change for all condition expect Scheduled.
		placementGeneration = placementGeneration - 1
	}
	wantStatus := buildWantPlacementStatus(placementKey, placementGeneration, wantSelectedResourceIdentifiers, wantSelectedClusters, wantUnselectedClusters, wantObservedResourceIndex, resourceIsTrackable)

	var statusSyncedCondition metav1.Condition
	switch statusSyncedConditionStatus {
	case metav1.ConditionTrue:
		statusSyncedCondition = metav1.Condition{
			Type:               string(placementv1beta1.ClusterResourcePlacementStatusSyncedConditionType),
			Status:             metav1.ConditionTrue,
			Reason:             condition.StatusSyncSucceededReason,
			ObservedGeneration: placementGeneration,
		}
	case metav1.ConditionFalse:
		statusSyncedCondition = metav1.Condition{
			Type:               string(placementv1beta1.ClusterResourcePlacementStatusSyncedConditionType),
			Status:             metav1.ConditionFalse,
			Reason:             condition.StatusSyncFailedReason,
			ObservedGeneration: placementGeneration,
		}
	}
	wantStatus.Conditions = append(wantStatus.Conditions, statusSyncedCondition)

	if isResourceSelectorInvalid {
		for i := range wantStatus.Conditions {
			if wantStatus.Conditions[i].Type == string(placementv1beta1.ClusterResourcePlacementScheduledConditionType) {
				wantStatus.Conditions[i].Status = metav1.ConditionFalse
				wantStatus.Conditions[i].Reason = condition.InvalidResourceSelectorsReason
				// when namespace resource selector is invalid, the observed generation is only updated for Scheduled condition.
				wantStatus.Conditions[i].ObservedGeneration = placementGeneration + 1
			}
		}
	}
	return wantStatus
}

func safeRolloutWorkloadCRPStatusUpdatedActual(wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier, failedWorkloadResourceIdentifier placementv1beta1.ResourceIdentifier, wantSelectedClusters []string, wantObservedResourceIndex string, failedResourceObservedGeneration int64) func() error {
	crpKey := types.NamespacedName{Name: fmt.Sprintf(crpNameTemplate, GinkgoParallelProcess())}
	return safeRolloutWorkloadPlacementStatusUpdatedActual(crpKey, wantSelectedResourceIdentifiers, failedWorkloadResourceIdentifier, wantSelectedClusters, wantObservedResourceIndex, failedResourceObservedGeneration)
}

func safeRolloutWorkloadRPStatusUpdatedActual(wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier, failedWorkloadResourceIdentifier placementv1beta1.ResourceIdentifier, wantSelectedClusters []string, wantObservedResourceIndex string, failedResourceObservedGeneration int64) func() error {
	rpKey := types.NamespacedName{Name: fmt.Sprintf(rpNameTemplate, GinkgoParallelProcess()), Namespace: appNamespace().Name}
	return safeRolloutWorkloadPlacementStatusUpdatedActual(rpKey, wantSelectedResourceIdentifiers, failedWorkloadResourceIdentifier, wantSelectedClusters, wantObservedResourceIndex, failedResourceObservedGeneration)
}

func safeRolloutWorkloadPlacementStatusUpdatedActual(
	placementKey types.NamespacedName,
	wantSelectedResourceIdentifiers []placementv1beta1.ResourceIdentifier,
	failedWorkloadResourceIdentifier placementv1beta1.ResourceIdentifier,
	wantSelectedClusters []string,
	wantObservedResourceIndex string,
	failedResourceObservedGeneration int64,
) func() error {
	return func() error {
		placement, err := retrievePlacement(placementKey)
		if err != nil {
			return err
		}

		var wantPlacementStatus []placementv1beta1.PerClusterPlacementStatus
		// We only expect the deployment to not be available on one cluster.
		unavailableResourcePlacementStatus := placementv1beta1.PerClusterPlacementStatus{
			ObservedResourceIndex: wantObservedResourceIndex,
			Conditions: []metav1.Condition{
				{
					Type:               string(placementv1beta1.PerClusterScheduledConditionType),
					Status:             metav1.ConditionTrue,
					Reason:             condition.ScheduleSucceededReason,
					ObservedGeneration: placement.GetGeneration(),
				},
				{
					Type:               string(placementv1beta1.PerClusterRolloutStartedConditionType),
					Status:             metav1.ConditionTrue,
					Reason:             condition.RolloutStartedReason,
					ObservedGeneration: placement.GetGeneration(),
				},
				{
					Type:               string(placementv1beta1.PerClusterOverriddenConditionType),
					Status:             metav1.ConditionTrue,
					Reason:             condition.OverrideNotSpecifiedReason,
					ObservedGeneration: placement.GetGeneration(),
				},
				{
					Type:               string(placementv1beta1.PerClusterWorkSynchronizedConditionType),
					Status:             metav1.ConditionTrue,
					Reason:             condition.AllWorkSyncedReason,
					ObservedGeneration: placement.GetGeneration(),
				},
				{
					Type:               string(placementv1beta1.PerClusterAppliedConditionType),
					Status:             metav1.ConditionTrue,
					Reason:             condition.AllWorkAppliedReason,
					ObservedGeneration: placement.GetGeneration(),
				},
				{
					Type:               string(placementv1beta1.PerClusterAvailableConditionType),
					Status:             metav1.ConditionFalse,
					Reason:             condition.WorkNotAvailableReason,
					ObservedGeneration: placement.GetGeneration(),
				},
			},
			FailedPlacements: []placementv1beta1.FailedResourcePlacement{
				{
					ResourceIdentifier: failedWorkloadResourceIdentifier,
					Condition: metav1.Condition{
						Type:               string(placementv1beta1.PerClusterAvailableConditionType),
						Status:             metav1.ConditionFalse,
						Reason:             string(workapplier.AvailabilityResultTypeNotYetAvailable),
						ObservedGeneration: failedResourceObservedGeneration,
					},
				},
			},
		}
		wantPlacementStatus = append(wantPlacementStatus, unavailableResourcePlacementStatus)

		// For all the other connected member clusters rollout will be blocked.
		rolloutBlockedPlacementStatus := placementv1beta1.PerClusterPlacementStatus{
			ObservedResourceIndex: wantObservedResourceIndex,
			Conditions: []metav1.Condition{
				{
					Type:               string(placementv1beta1.PerClusterScheduledConditionType),
					Status:             metav1.ConditionTrue,
					Reason:             condition.ScheduleSucceededReason,
					ObservedGeneration: placement.GetGeneration(),
				},
				{
					Type:               string(placementv1beta1.PerClusterRolloutStartedConditionType),
					Status:             metav1.ConditionFalse,
					Reason:             condition.RolloutNotStartedYetReason,
					ObservedGeneration: placement.GetGeneration(),
				},
			},
		}

		for i := 0; i < len(wantSelectedClusters)-1; i++ {
			wantPlacementStatus = append(wantPlacementStatus, rolloutBlockedPlacementStatus)
		}

		wantStatus := &placementv1beta1.PlacementStatus{
			Conditions:                  placementRolloutStuckConditions(placementKey, placement.GetGeneration()),
			PerClusterPlacementStatuses: wantPlacementStatus,
			SelectedResources:           wantSelectedResourceIdentifiers,
			ObservedResourceIndex:       wantObservedResourceIndex,
		}

		if diff := cmp.Diff(placement.GetPlacementStatus(), wantStatus, safeRolloutPlacementStatusCmpOptions...); diff != "" {
			return fmt.Errorf("Placement status diff (-got, +want): %s", diff)
		}
		return nil
	}
}

func workNamespaceRemovedFromClusterActual(cluster *framework.Cluster) func() error {
	ns := appNamespace()
	return func() error {
		if err := cluster.KubeClient.Get(ctx, types.NamespacedName{Name: ns.Name}, &ns); !errors.IsNotFound(err) {
			if err == nil {
				// List all pods and configmaps in the namespace
				podList := &corev1.PodList{}
				if listErr := cluster.KubeClient.List(ctx, podList, client.InNamespace(ns.Name)); listErr != nil {
					return fmt.Errorf("work namespace %s still exists on cluster %s but failed to list pods: %w, ns deletion timestamp: %v, current timestamp: %v",
						ns.Name, cluster.ClusterName, listErr, ns.GetDeletionTimestamp(), time.Now())
				}
				configMapList := &corev1.ConfigMapList{}
				if listErr := cluster.KubeClient.List(ctx, configMapList, client.InNamespace(ns.Name)); listErr != nil {
					return fmt.Errorf("work namespace %s still exists on cluster %s but failed to list configmaps: %w, ns deletion timestamp: %v, current timestamp: %v",
						ns.Name, cluster.ClusterName, listErr, ns.GetDeletionTimestamp(), time.Now())
				}

				// Build resource status summary
				var resourceStatus strings.Builder
				resourceStatus.WriteString(fmt.Sprintf("\nList pods and configmaps in namespace %s:\n", ns.Name))
				for _, item := range podList.Items {
					resourceStatus.WriteString(fmt.Sprintf("- Pod: %s/%s\n", item.GetNamespace(), item.GetName()))
					resourceStatus.WriteString(fmt.Sprintf("  DeletionTimestamp: %v\n", item.GetDeletionTimestamp()))
					resourceStatus.WriteString(fmt.Sprintf("  Finalizers: %v\n", item.GetFinalizers()))
				}
				for _, item := range configMapList.Items {
					resourceStatus.WriteString(fmt.Sprintf("- ConfigMap: %s/%s\n", item.GetNamespace(), item.GetName()))
					resourceStatus.WriteString(fmt.Sprintf("  DeletionTimestamp: %v\n", item.GetDeletionTimestamp()))
					resourceStatus.WriteString(fmt.Sprintf("  Finalizers: %v\n", item.GetFinalizers()))
				}
				return fmt.Errorf("work namespace %s still exists on cluster %s: deletion timestamp: %v, current timestamp: %v, resources: %s",
					ns.Name, cluster.ClusterName, ns.GetDeletionTimestamp(), time.Now(), resourceStatus.String())
			}
			return fmt.Errorf("getting work namespace %s failed: %w", ns.Name, err)
		}
		return nil
	}
}

// namespacedResourcesRemovedFromClusterActual checks that resources in the specified namespace have been removed from the cluster.
// It checks if the placed configMap is removed by default, as this is tested in most of the test cases.
// For tests with additional resources placed, e.g. deployments, daemonSets, add those to placedResources.
func namespacedResourcesRemovedFromClusterActual(cluster *framework.Cluster, placedResources ...client.Object) func() error {
	cm := appConfigMap()
	placedResources = append(placedResources, &cm)
	return func() error {
		for _, resource := range placedResources {
			if err := cluster.KubeClient.Get(ctx, types.NamespacedName{Name: resource.GetName(), Namespace: appNamespace().Name}, resource); !errors.IsNotFound(err) {
				return fmt.Errorf("%s %s/%s still exists on cluster %s or get encountered an error: %w", resource.GetObjectKind().GroupVersionKind(), appNamespace().Name, resource.GetName(), cluster.ClusterName, err)
			}
		}
		return nil
	}
}

func allFinalizersExceptForCustomDeletionBlockerRemovedFromPlacementActual(placementKey types.NamespacedName) func() error {
	return func() error {
		placement, err := retrievePlacement(placementKey)
		if err != nil {
			if errors.IsNotFound(err) {
				return nil
			}
			return err
		}

		wantFinalizers := []string{customDeletionBlockerFinalizer}
		finalizer := placement.GetFinalizers()
		if diff := cmp.Diff(finalizer, wantFinalizers); diff != "" {
			return fmt.Errorf("Placement finalizers diff (-got, +want): %s", diff)
		}

		return nil
	}
}

func placementRemovedActual(placementKey types.NamespacedName) func() error {
	return func() error {
		if _, err := retrievePlacement(placementKey); !errors.IsNotFound(err) {
			return fmt.Errorf("Placement %s still exists or an unexpected error occurred: %w", placementKey, err)
		}

		return nil
	}
}

func crpEvictionRemovedActual(crpEvictionName string) func() error {
	return func() error {
		if err := hubClient.Get(ctx, types.NamespacedName{Name: crpEvictionName}, &placementv1beta1.ClusterResourcePlacementEviction{}); !errors.IsNotFound(err) {
			return fmt.Errorf("CRP eviction still exists or an unexpected error occurred: %w", err)
		}

		return nil
	}
}

func crpDisruptionBudgetRemovedActual(crpDisruptionBudgetName string) func() error {
	return func() error {
		if err := hubClient.Get(ctx, types.NamespacedName{Name: crpDisruptionBudgetName}, &placementv1beta1.ClusterResourcePlacementDisruptionBudget{}); !errors.IsNotFound(err) {
			return fmt.Errorf("CRP disruption budget still exists or an unexpected error occurred: %w", err)
		}
		return nil
	}
}

func validateCRPSnapshotRevisions(crpName string, wantPolicySnapshotRevision, wantResourceSnapshotRevision int) error {
	matchingLabels := client.MatchingLabels{placementv1beta1.PlacementTrackingLabel: crpName}

	snapshotList := &placementv1beta1.ClusterSchedulingPolicySnapshotList{}
	if err := hubClient.List(ctx, snapshotList, matchingLabels); err != nil {
		return err
	}
	if len(snapshotList.Items) != wantPolicySnapshotRevision {
		return fmt.Errorf("clusterSchedulingPolicySnapshotList got %v, want 1", len(snapshotList.Items))
	}
	resourceSnapshotList := &placementv1beta1.ClusterResourceSnapshotList{}
	if err := hubClient.List(ctx, resourceSnapshotList, matchingLabels); err != nil {
		return err
	}
	if len(resourceSnapshotList.Items) != wantResourceSnapshotRevision {
		return fmt.Errorf("clusterResourceSnapshotList got %v, want 2", len(snapshotList.Items))
	}
	return nil
}

func validateRPSnapshotRevisions(rpName, namespace string, wantPolicySnapshotRevision, wantResourceSnapshotRevision int) error {
	listOptions := []client.ListOption{
		client.InNamespace(namespace),
		client.MatchingLabels{placementv1beta1.PlacementTrackingLabel: rpName},
	}

	snapshotList := &placementv1beta1.SchedulingPolicySnapshotList{}
	if err := hubClient.List(ctx, snapshotList, listOptions...); err != nil {
		return err
	}
	if len(snapshotList.Items) != wantPolicySnapshotRevision {
		return fmt.Errorf("schedulingPolicySnapshotList got %v, want 1", len(snapshotList.Items))
	}
	resourceSnapshotList := &placementv1beta1.ResourceSnapshotList{}
	if err := hubClient.List(ctx, resourceSnapshotList, listOptions...); err != nil {
		return err
	}
	if len(resourceSnapshotList.Items) != wantResourceSnapshotRevision {
		return fmt.Errorf("resourceSnapshotList got %v, want 2", len(resourceSnapshotList.Items))
	}
	return nil
}

func updateRunClusterRolloutSucceedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.ClusterUpdatingConditionStarted),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ClusterUpdatingStartedReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.ClusterUpdatingConditionSucceeded),
			Status:             metav1.ConditionTrue,
			Reason:             condition.ClusterUpdatingSucceededReason,
			ObservedGeneration: generation,
		},
	}
}

func updateRunStageRolloutSucceedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.StageUpdatingConditionProgressing),
			Status:             metav1.ConditionFalse,
			Reason:             condition.StageUpdatingSucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.StageUpdatingConditionSucceeded),
			Status:             metav1.ConditionTrue,
			Reason:             condition.StageUpdatingSucceededReason,
			ObservedGeneration: generation,
		},
	}
}

func updateRunAfterStageTaskSucceedConditions(generation int64, taskType placementv1beta1.AfterStageTaskType) []metav1.Condition {
	if taskType == placementv1beta1.AfterStageTaskTypeApproval {
		return []metav1.Condition{
			{
				Type:               string(placementv1beta1.AfterStageTaskConditionApprovalRequestCreated),
				Status:             metav1.ConditionTrue,
				Reason:             condition.AfterStageTaskApprovalRequestCreatedReason,
				ObservedGeneration: generation,
			},
			{
				Type:               string(placementv1beta1.AfterStageTaskConditionApprovalRequestApproved),
				Status:             metav1.ConditionTrue,
				Reason:             condition.AfterStageTaskApprovalRequestApprovedReason,
				ObservedGeneration: generation,
			},
		}
	}
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.AfterStageTaskConditionWaitTimeElapsed),
			Status:             metav1.ConditionTrue,
			Reason:             condition.AfterStageTaskWaitTimeElapsedReason,
			ObservedGeneration: generation,
		},
	}
}

func updateRunSucceedConditions(generation int64) []metav1.Condition {
	return []metav1.Condition{
		{
			Type:               string(placementv1beta1.StagedUpdateRunConditionInitialized),
			Status:             metav1.ConditionTrue,
			Reason:             condition.UpdateRunInitializeSucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.StagedUpdateRunConditionProgressing),
			Status:             metav1.ConditionFalse,
			Reason:             condition.UpdateRunSucceededReason,
			ObservedGeneration: generation,
		},
		{
			Type:               string(placementv1beta1.StagedUpdateRunConditionSucceeded),
			Status:             metav1.ConditionTrue,
			Reason:             condition.UpdateRunSucceededReason,
			ObservedGeneration: generation,
		},
	}
}

func updateRunStatusSucceededActual(
	updateRunName string,
	wantPolicyIndex string,
	wantClusterCount int,
	wantApplyStrategy *placementv1beta1.ApplyStrategy,
	wantStrategySpec *placementv1beta1.StagedUpdateStrategySpec,
	wantSelectedClusters [][]string,
	wantUnscheduledClusters []string,
	wantCROs map[string][]string,
	wantROs map[string][]placementv1beta1.NamespacedName,
) func() error {
	return func() error {
		updateRun := &placementv1beta1.ClusterStagedUpdateRun{}
		if err := hubClient.Get(ctx, types.NamespacedName{Name: updateRunName}, updateRun); err != nil {
			return err
		}

		wantStatus := placementv1beta1.StagedUpdateRunStatus{
			PolicySnapshotIndexUsed:      wantPolicyIndex,
			PolicyObservedClusterCount:   wantClusterCount,
			ApplyStrategy:                wantApplyStrategy.DeepCopy(),
			StagedUpdateStrategySnapshot: wantStrategySpec,
		}
		stagesStatus := make([]placementv1beta1.StageUpdatingStatus, len(wantStrategySpec.Stages))
		for i, stage := range wantStrategySpec.Stages {
			stagesStatus[i].StageName = stage.Name
			stagesStatus[i].Clusters = make([]placementv1beta1.ClusterUpdatingStatus, len(wantSelectedClusters[i]))
			for j := range stagesStatus[i].Clusters {
				stagesStatus[i].Clusters[j].ClusterName = wantSelectedClusters[i][j]
				stagesStatus[i].Clusters[j].ClusterResourceOverrideSnapshots = wantCROs[wantSelectedClusters[i][j]]
				stagesStatus[i].Clusters[j].ResourceOverrideSnapshots = wantROs[wantSelectedClusters[i][j]]
				stagesStatus[i].Clusters[j].Conditions = updateRunClusterRolloutSucceedConditions(updateRun.Generation)
			}
			stagesStatus[i].AfterStageTaskStatus = make([]placementv1beta1.AfterStageTaskStatus, len(stage.AfterStageTasks))
			for j, task := range stage.AfterStageTasks {
				stagesStatus[i].AfterStageTaskStatus[j].Type = task.Type
				if task.Type == placementv1beta1.AfterStageTaskTypeApproval {
					stagesStatus[i].AfterStageTaskStatus[j].ApprovalRequestName = fmt.Sprintf(placementv1beta1.ApprovalTaskNameFmt, updateRun.Name, stage.Name)
				}
				stagesStatus[i].AfterStageTaskStatus[j].Conditions = updateRunAfterStageTaskSucceedConditions(updateRun.Generation, task.Type)
			}
			stagesStatus[i].Conditions = updateRunStageRolloutSucceedConditions(updateRun.Generation)
		}

		deleteStageStatus := &placementv1beta1.StageUpdatingStatus{
			StageName: "kubernetes-fleet.io/deleteStage",
		}
		deleteStageStatus.Clusters = make([]placementv1beta1.ClusterUpdatingStatus, len(wantUnscheduledClusters))
		for i := range deleteStageStatus.Clusters {
			deleteStageStatus.Clusters[i].ClusterName = wantUnscheduledClusters[i]
			deleteStageStatus.Clusters[i].Conditions = updateRunClusterRolloutSucceedConditions(updateRun.Generation)
		}
		deleteStageStatus.Conditions = updateRunStageRolloutSucceedConditions(updateRun.Generation)

		wantStatus.StagesStatus = stagesStatus
		wantStatus.DeletionStageStatus = deleteStageStatus
		wantStatus.Conditions = updateRunSucceedConditions(updateRun.Generation)
		if diff := cmp.Diff(updateRun.Status, wantStatus, updateRunStatusCmpOption...); diff != "" {
			return fmt.Errorf("UpdateRun status diff (-got, +want): %s", diff)
		}
		return nil
	}
}

func updateRunAndApprovalRequestsRemovedActual(updateRunName string) func() error {
	return func() error {
		if err := hubClient.Get(ctx, types.NamespacedName{Name: updateRunName}, &placementv1beta1.ClusterStagedUpdateRun{}); !errors.IsNotFound(err) {
			return fmt.Errorf("UpdateRun still exists or an unexpected error occurred: %w", err)
		}

		appReqList := &placementv1beta1.ClusterApprovalRequestList{}
		if err := hubClient.List(ctx, appReqList, client.MatchingLabels{
			placementv1beta1.TargetUpdateRunLabel: updateRunName,
		}); err != nil {
			return fmt.Errorf("failed to list ClusterApprovalRequests: %w", err)
		}
		if len(appReqList.Items) > 0 {
			return fmt.Errorf("ClusterApprovalRequests still exist: %v", appReqList.Items)
		}
		return nil
	}
}

func updateRunStrategyRemovedActual(strategyName string) func() error {
	return func() error {
		if err := hubClient.Get(ctx, types.NamespacedName{Name: strategyName}, &placementv1beta1.ClusterStagedUpdateStrategy{}); !errors.IsNotFound(err) {
			return fmt.Errorf("ClusterStagedUpdateStrategy still exists or an unexpected error occurred: %w", err)
		}
		return nil
	}
}

func bindingStateActual(
	crpName string,
	targetClusterName string,
	wantState placementv1beta1.BindingState,
) func() error {
	return func() error {
		matchingLabels := client.MatchingLabels{placementv1beta1.PlacementTrackingLabel: crpName}

		var foundBinding *placementv1beta1.ClusterResourceBinding
		bindingList := &placementv1beta1.ClusterResourceBindingList{}
		if err := hubClient.List(ctx, bindingList, matchingLabels); err != nil {
			return err
		}
		for i := range bindingList.Items {
			binding := bindingList.Items[i]
			if binding.Spec.TargetCluster == targetClusterName {
				if foundBinding != nil {
					return fmt.Errorf("multiple bindings found targeting cluster %s for CRP %s", targetClusterName, crpName)
				}
				foundBinding = &binding
			}
		}
		if foundBinding == nil {
			return fmt.Errorf("no binding found targeting cluster %s for CRP %s", targetClusterName, crpName)
		}
		if foundBinding.Spec.State != wantState {
			return fmt.Errorf("binding state for cluster %s is %s, want %s", targetClusterName, foundBinding.Spec.State, wantState)
		}
		return nil
	}
}
