package noderebalance

import (
	"context"
	"fmt"
	"sort"
	"testing"

	. "github.com/onsi/gomega"
	"github.com/tidbcloud/aws-shared-provider/pkg/scheme"
	"github.com/tidbcloud/aws-shared-provider/pkg/types"
	"github.com/tidbcloud/aws-shared-provider/pkg/utils/labels"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	apitypes "k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/record"
	"k8s.io/utils/ptr"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/controller-runtime/pkg/client/interceptor"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

// normalizeTaintsForComparison normalizes taints for comparison by:
// 1. Creating a copy to avoid mutating the original
// 2. Normalizing dynamic values (e.g., timestamps) where expected value is empty
// 3. Sorting by Key+Effect for consistent comparison
func normalizeTaintsForComparison(actual, expected []corev1.Taint) []corev1.Taint {
	normalized := make([]corev1.Taint, len(actual))
	copy(normalized, actual)

	// Create a map of expected taints by key+effect for quick lookup
	expectedMap := make(map[string]corev1.Taint)
	for _, taint := range expected {
		key := taint.Key + ":" + string(taint.Effect)
		expectedMap[key] = taint
	}

	// Normalize dynamic values (e.g., timestamps) where expected value is empty
	for i := range normalized {
		key := normalized[i].Key + ":" + string(normalized[i].Effect)
		if expectedTaint, ok := expectedMap[key]; ok && expectedTaint.Value == "" {
			normalized[i].Value = ""
		}
	}

	// Sort by Key+Effect for consistent comparison
	sort.Slice(normalized, func(i, j int) bool {
		if normalized[i].Key != normalized[j].Key {
			return normalized[i].Key < normalized[j].Key
		}
		return normalized[i].Effect < normalized[j].Effect
	})

	return normalized
}

// sortTaints sorts taints by Key+Effect for consistent comparison
func sortTaints(taints []corev1.Taint) []corev1.Taint {
	sorted := make([]corev1.Taint, len(taints))
	copy(sorted, taints)
	sort.Slice(sorted, func(i, j int) bool {
		if sorted[i].Key != sorted[j].Key {
			return sorted[i].Key < sorted[j].Key
		}
		return sorted[i].Effect < sorted[j].Effect
	})
	return sorted
}

func TestReconcile(t *testing.T) {
	now := ptr.To(metav1.Now())
	cases := []struct {
		name string

		currentNode  client.Object
		otherNodes   []client.Object
		pods         []client.Object
		evictErrFunc func(obj client.Object) error
		wantResult   reconcile.Result
		wantNodes    []*corev1.Node
		wantPods     []string
	}{
		{
			name: "test no rebalanceSignalNodes",
			currentNode: &corev1.Node{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node-terminating",
					Labels: map[string]string{
						labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
						corev1.LabelTopologyZone:           "us-east-1a",
					},
					DeletionTimestamp: now,
					Finalizers:        []string{"fake-node-finalizer"},
				},
				Spec: corev1.NodeSpec{
					Taints: []corev1.Taint{{
						Key:    types.RebalanceSignalTaintKey,
						Value:  fmt.Sprint(now.Unix()),
						Effect: corev1.TaintEffectNoSchedule,
					}},
				},
			},
			otherNodes: []client.Object{
				&corev1.Node{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node2",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{{
							Key:    types.PreventCAScaleUpTaintKey,
							Value:  fmt.Sprint(now.Unix()),
							Effect: corev1.TaintEffectNoSchedule,
						}},
					},
				},
				&corev1.Node{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node-other-zone",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1b",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{{
							Key:    types.PreventCAScaleUpTaintKey,
							Value:  fmt.Sprint(now.Unix()),
							Effect: corev1.TaintEffectNoSchedule,
						}},
					},
				},
			},
			wantNodes: []*corev1.Node{
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node-terminating",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
						DeletionTimestamp: now,
						Finalizers:        []string{types.TidbPoolFinalizer},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{{
							Key:    types.RebalanceSignalTaintKey,
							Value:  fmt.Sprint(now.Unix()),
							Effect: corev1.TaintEffectNoSchedule,
						}},
					},
				},
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node2",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
				},
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node-other-zone",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1b",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{{
							Key:    types.PreventCAScaleUpTaintKey,
							Value:  fmt.Sprint(now.Unix()),
							Effect: corev1.TaintEffectNoSchedule,
						}},
					},
				},
			},
		},
		{
			name: "test rebalanceSignalNodes more than one",
			currentNode: &corev1.Node{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
					Labels: map[string]string{
						labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
						corev1.LabelTopologyZone:           "us-east-1a",
					},
				},
				Spec: corev1.NodeSpec{
					Taints: []corev1.Taint{{
						Key:    types.RebalanceSignalTaintKey,
						Value:  fmt.Sprint(now.Unix()),
						Effect: corev1.TaintEffectNoSchedule,
					}},
				},
			},
			otherNodes: []client.Object{
				&corev1.Node{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node2",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
				},
				&corev1.Node{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node3",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{{
							Key:    types.RebalanceSignalTaintKey,
							Value:  fmt.Sprint(now.Unix()),
							Effect: corev1.TaintEffectNoSchedule,
						}},
					},
				},
			},
			pods: []client.Object{
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "tidb-server-1", Namespace: "tidb-pool"},
					Spec:       corev1.PodSpec{NodeName: "node1"},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "tidb-server-2", Namespace: "vip-tidb-pool"},
					Spec:       corev1.PodSpec{NodeName: "node2"},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "tidb-server-3", Namespace: "tidb-pool"},
					Spec:       corev1.PodSpec{NodeName: "node3"},
				},
			},
			wantNodes: []*corev1.Node{
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node1",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{
							{
								Key:    types.RebalanceSignalTaintKey,
								Value:  fmt.Sprint(now.Unix()),
								Effect: corev1.TaintEffectNoSchedule,
							},
							{
								Key:    types.PreventCAScaleUpTaintKey,
								Effect: corev1.TaintEffectNoSchedule,
							},
						},
					},
				},
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node2",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{{
							Key:    types.PreventCAScaleUpTaintKey,
							Effect: corev1.TaintEffectNoSchedule,
						}},
					},
				},
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node3",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{
							{
								Key:    types.RebalanceSignalTaintKey,
								Value:  fmt.Sprint(now.Unix()),
								Effect: corev1.TaintEffectNoSchedule,
							},
							{
								Key:    types.PreventCAScaleUpTaintKey,
								Effect: corev1.TaintEffectNoSchedule,
							},
						},
					},
				},
			},
			wantPods: []string{"tidb-server-2", "tidb-server-3"},
		},
		{
			name: "test rebalanceSignalNodes only one, less than maxRebalanceSignalTime, no PreventCAScaleUpTaintKey",
			currentNode: &corev1.Node{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
					Labels: map[string]string{
						labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
						corev1.LabelTopologyZone:           "us-east-1a",
					},
				},
			},
			otherNodes: []client.Object{
				&corev1.Node{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node2",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{{
							Key:    types.RebalanceSignalTaintKey,
							Value:  fmt.Sprint(now.Unix()),
							Effect: corev1.TaintEffectNoSchedule,
						}},
					},
				},
			},
			wantNodes: []*corev1.Node{
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node1",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
				},
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node2",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{{
							Key:    types.RebalanceSignalTaintKey,
							Value:  fmt.Sprint(now.Unix()),
							Effect: corev1.TaintEffectNoSchedule,
						}},
					},
				},
			},
		},
		{
			name: "test rebalanceSignalNodes only one, less than maxRebalanceSignalTime, have PreventCAScaleUpTaintKey",
			currentNode: &corev1.Node{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
					Labels: map[string]string{
						labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
						corev1.LabelTopologyZone:           "us-east-1a",
					},
				},
			},
			otherNodes: []client.Object{
				&corev1.Node{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node2",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{
							{
								Key:    types.RebalanceSignalTaintKey,
								Value:  fmt.Sprint(now.Unix()),
								Effect: corev1.TaintEffectNoSchedule,
							},
							{
								Key:    types.PreventCAScaleUpTaintKey,
								Value:  fmt.Sprint(now.Unix()),
								Effect: corev1.TaintEffectNoSchedule,
							},
						},
					},
				},
			},
			wantNodes: []*corev1.Node{
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node1",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{{
							Key:    types.PreventCAScaleUpTaintKey,
							Effect: corev1.TaintEffectNoSchedule,
						}},
					},
				},
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node2",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{
							{
								Key:    types.RebalanceSignalTaintKey,
								Value:  fmt.Sprint(now.Unix()),
								Effect: corev1.TaintEffectNoSchedule,
							},
							{
								Key:    types.PreventCAScaleUpTaintKey,
								Value:  fmt.Sprint(now.Unix()),
								Effect: corev1.TaintEffectNoSchedule,
							},
						},
					},
				},
			},
		},
		{
			name: "test rebalanceSignalNodes only one, more than maxRebalanceSignalTime",
			currentNode: &corev1.Node{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
					Labels: map[string]string{
						labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
						corev1.LabelTopologyZone:           "us-east-1a",
					},
				},
				Spec: corev1.NodeSpec{
					Taints: []corev1.Taint{{
						Key:    types.RebalanceSignalTaintKey,
						Value:  fmt.Sprint(now.Unix() - 2*maxRebalanceSignalTime),
						Effect: corev1.TaintEffectNoSchedule,
					}},
				},
			},
			otherNodes: []client.Object{
				&corev1.Node{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node2",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
				},
				&corev1.Node{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node3",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{{
							Key:    types.RebalanceSignalTaintKey,
							Value:  fmt.Sprint(now.Unix() - 2*maxRebalanceSignalTime),
							Effect: corev1.TaintEffectNoSchedule,
						}},
					},
				},
			},
			pods: []client.Object{
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "tidb-server-1", Namespace: "tidb-pool"},
					Spec:       corev1.PodSpec{NodeName: "node1"},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "tidb-server-2", Namespace: "vip-tidb-pool"},
					Spec:       corev1.PodSpec{NodeName: "node2"},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "tidb-server-3", Namespace: "tidb-pool"},
					Spec:       corev1.PodSpec{NodeName: "node3"},
				},
			},
			wantNodes: []*corev1.Node{
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node1",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{
							{
								Key:    types.RebalanceSignalTaintKey,
								Value:  fmt.Sprint(now.Unix() - 2*maxRebalanceSignalTime),
								Effect: corev1.TaintEffectNoSchedule,
							},
							{
								Key:    types.PreventCAScaleUpTaintKey,
								Effect: corev1.TaintEffectNoSchedule,
							},
						},
					},
				},
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node2",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{{
							Key:    types.PreventCAScaleUpTaintKey,
							Effect: corev1.TaintEffectNoSchedule,
						}},
					},
				},
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node3",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{
							{
								Key:    types.RebalanceSignalTaintKey,
								Value:  fmt.Sprint(now.Unix() - 2*maxRebalanceSignalTime),
								Effect: corev1.TaintEffectNoSchedule,
							},
							{
								Key:    types.PreventCAScaleUpTaintKey,
								Effect: corev1.TaintEffectNoSchedule,
							},
						},
					},
				},
			},
			wantPods: []string{"tidb-server-2", "tidb-server-3"},
		},
		{
			name: "test evict retry error",
			currentNode: &corev1.Node{
				ObjectMeta: metav1.ObjectMeta{
					Name: "node1",
					Labels: map[string]string{
						labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
						corev1.LabelTopologyZone:           "us-east-1a",
					},
				},
				Spec: corev1.NodeSpec{
					Taints: []corev1.Taint{{
						Key:    types.RebalanceSignalTaintKey,
						Value:  fmt.Sprint(now.Unix() - 2*maxRebalanceSignalTime),
						Effect: corev1.TaintEffectNoSchedule,
					}},
				},
			},
			pods: []client.Object{
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "tidb-server-a", Namespace: "tidb-pool"},
					Spec:       corev1.PodSpec{NodeName: "node1"},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{
						Name:              "tidb-server-b-terminating",
						Namespace:         "tidb-pool",
						DeletionTimestamp: now,
						Finalizers:        []string{types.TidbPoolFinalizer},
					},
					Spec: corev1.PodSpec{NodeName: "node1"},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "tidb-server-evict-too-many", Namespace: "tidb-pool"},
					Spec:       corev1.PodSpec{NodeName: "node1"},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "tidb-server-c-ds",
						Namespace: "vip-tidb-pool",
						OwnerReferences: []metav1.OwnerReference{{
							Controller: ptr.To(true),
							Kind:       "DaemonSet",
							UID:        "pod-fake-uid",
						}},
					},
					Spec: corev1.PodSpec{NodeName: "node1"},
				},
				&corev1.Pod{
					ObjectMeta: metav1.ObjectMeta{Name: "tidb-server-d", Namespace: "vip-tidb-pool"},
					Spec:       corev1.PodSpec{NodeName: "node1"},
				},
			},
			evictErrFunc: func(obj client.Object) error {
				if obj.GetName() == "tidb-server-evict-too-many" {
					return apierrors.NewTooManyRequestsError("inject to ut")
				}
				return nil
			},
			wantResult: reconcile.Result{Requeue: true, RequeueAfter: retryTooManyRequestsTime},
			wantNodes: []*corev1.Node{
				{
					ObjectMeta: metav1.ObjectMeta{
						Name: "node1",
						Labels: map[string]string{
							labels.ServerlessNodeGroupLabelKey: "tidb-xxx",
							corev1.LabelTopologyZone:           "us-east-1a",
						},
					},
					Spec: corev1.NodeSpec{
						Taints: []corev1.Taint{
							{
								Key:    types.RebalanceSignalTaintKey,
								Value:  fmt.Sprint(now.Unix() - 2*maxRebalanceSignalTime),
								Effect: corev1.TaintEffectNoSchedule,
							},
							{
								Key:    types.PreventCAScaleUpTaintKey,
								Effect: corev1.TaintEffectNoSchedule,
							},
						},
					},
				},
			},
			wantPods: []string{"tidb-server-b-terminating", "tidb-server-evict-too-many", "tidb-server-c-ds"},
		},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			allNodes := append([]client.Object{c.currentNode}, c.otherNodes...)
			r := newFakeReconciler(append(allNodes, c.pods...), c.evictErrFunc)
			req := ctrl.Request{
				NamespacedName: apitypes.NamespacedName{
					Name: c.currentNode.GetName(),
				},
			}
			result, err := r.Reconcile(context.Background(), req)
			g.Expect(err).To(Succeed())
			g.Expect(result).To(Equal(c.wantResult))

			for _, wantNode := range c.wantNodes {
				node := &corev1.Node{}
				g.Expect(r.Client.Get(context.Background(), client.ObjectKeyFromObject(wantNode), node)).To(Succeed())

				// Normalize taints for comparison (handles dynamic values like timestamps)
				// This creates normalized copies that can be compared without order dependency
				normalizedActualTaints := normalizeTaintsForComparison(node.Spec.Taints, wantNode.Spec.Taints)
				normalizedExpectedTaints := sortTaints(wantNode.Spec.Taints)

				g.Expect(node.Labels).To(Equal(wantNode.Labels), "nodename: %s", wantNode.Name)
				g.Expect(node.Annotations).To(Equal(wantNode.Annotations), "nodename: %s", wantNode.Name)

				// Compare Spec with normalized taints
				nodeSpecCopy := node.Spec.DeepCopy()
				nodeSpecCopy.Taints = normalizedActualTaints
				wantNodeSpecCopy := wantNode.Spec.DeepCopy()
				wantNodeSpecCopy.Taints = normalizedExpectedTaints
				g.Expect(*nodeSpecCopy).To(Equal(*wantNodeSpecCopy), "nodename: %s", wantNode.Name)
			}
			podList := &corev1.PodList{}
			g.Expect(r.Client.List(context.Background(), podList)).To(Succeed())
			pods := make([]string, 0, len(podList.Items))
			for _, pod := range podList.Items {
				pods = append(pods, pod.Name)
			}
			g.Expect(pods).To(ConsistOf(c.wantPods))
		})
	}
}

func newFakeReconciler(objs []client.Object, evictionErrFunc func(obj client.Object) error) *Reconciler {
	fakeClient := fake.NewClientBuilder().
		WithScheme(scheme.Scheme).
		WithObjects(objs...).
		WithIndex(&corev1.Pod{}, "spec.nodeName", func(obj client.Object) []string {
			pod := obj.(*corev1.Pod)
			return []string{pod.Spec.NodeName}
		}).
		WithInterceptorFuncs(interceptor.Funcs{SubResourceCreate: func(ctx context.Context,
			client client.Client,
			subResourceName string,
			obj client.Object,
			subResource client.Object,
			opts ...client.SubResourceCreateOption,
		) error {
			if evictionErrFunc == nil || subResourceName != "eviction" {
				return client.SubResource(subResourceName).Create(ctx, obj, subResource, opts...)
			}
			evictionErr := evictionErrFunc(obj)
			if evictionErr == nil {
				return client.SubResource(subResourceName).Create(ctx, obj, subResource, opts...)
			}
			return evictionErr
		}}).
		Build()

	return &Reconciler{
		Client:   fakeClient,
		Recorder: record.NewFakeRecorder(4096),
	}
}
