package commonazure

import (
	"context"
	"fmt"
	"testing"
	"time"

	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/stretchr/testify/assert"
	aksv1beta1 "github.com/upbound/provider-azure/apis/containerservice/v1beta1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	agentv2 "github.com/tidbcloud/observability/agent-control-server/api/v2"
	"github.com/tidbcloud/observability/agent-control-server/pkg/config"
	"github.com/tidbcloud/observability/agent-control-server/pkg/consts"
	"github.com/tidbcloud/observability/agent-control-server/pkg/deploy"
	"github.com/tidbcloud/observability/agent-control-server/pkg/reconciler"
	"github.com/tidbcloud/observability/agent-control-server/pkg/testkits"
	"github.com/tidbcloud/observability/agent-control-server/pkg/util"
)

func TestSanitizeAKSNodePoolName(t *testing.T) {
	name1 := sanitizeAKSNodePoolName("tidbcloud-o11y-observability-very-long-name")
	name2 := sanitizeAKSNodePoolName("tidbcloud-o11y-observability-very-long-name")

	assert.Equal(t, name1, name2)
	assert.Len(t, name1, 12)
	assert.Equal(t, "n", string(name1[0]))
}

func TestPtrOrNil(t *testing.T) {
	assert.Nil(t, ptrOrNil(""))
	nonEmpty := ptrOrNil("subnet-id")
	if assert.NotNil(t, nonEmpty) {
		assert.Equal(t, "subnet-id", *nonEmpty)
	}
}

const azNodePoolClusterName = "aks-cluster"

func azNodeNameFunc(s string) string { return "np-" + s }

// azPoolName returns the actual on-cluster pool name, matching the sanitization done by NewNodePoolTask.
func azPoolName(spec string) string { return sanitizeAKSNodePoolName(azNodeNameFunc(spec)) }

func assertAzResultRequeueAfter(t *testing.T, res deploy.Result, d time.Duration) {
	t.Helper()
	requeueResult, errs, retriableErrs := deploy.MinRequeue([]deploy.Result{res})
	assert.NoError(t, errs)
	assert.Error(t, retriableErrs)
	assert.True(t, requeueResult.Requeue)
	assert.Equal(t, d, requeueResult.RequeueAfter)
}

func makeAKSCluster() *aksv1beta1.KubernetesCluster {
	c := &aksv1beta1.KubernetesCluster{
		ObjectMeta: metav1.ObjectMeta{Name: azNodePoolClusterName},
		Spec: aksv1beta1.KubernetesClusterSpec{
			ResourceSpec: xpv1.ResourceSpec{
				ProviderConfigReference: &xpv1.Reference{Name: "azure-provider"},
			},
		},
	}
	c.Status.AtProvider.CurrentKubernetesVersion = ptr.To("1.29.0")
	return c
}

func makeAzNodePoolAgent() *agentv2.KubernetesAgent {
	return &agentv2.KubernetesAgent{
		ObjectMeta: metav1.ObjectMeta{Name: "test-agent", Namespace: "default"},
	}
}

func newAzNodePoolTask(
	rctx *reconciler.Context[*agentv2.KubernetesAgent],
	specs config.CustomerAgentSpecs,
	nodeMatchLabels, nodeConfigLabels map[string]string,
	taints []corev1.Taint,
	getAKSConfig AKSConfigProvider,
) *NodePoolTask[*agentv2.KubernetesAgent] {
	if getAKSConfig == nil {
		getAKSConfig = func() (string, string, string, string, error) {
			return "sub-1", "rg-1", "subnet-1", "pod-subnet-1", nil
		}
	}
	return NewNodePoolTask(
		rctx,
		azNodePoolClusterName,
		azNodeNameFunc,
		nodeMatchLabels,
		nodeConfigLabels,
		specs,
		taints,
		getAKSConfig,
		func() deploy.ClusterMeta { return deploy.ClusterMeta{K8sClusterName: azNodePoolClusterName} },
	)
}

func TestNodePoolTask_BasicMeta(t *testing.T) {
	cli := testkits.BuildClient()
	rctx := &reconciler.Context[*agentv2.KubernetesAgent]{
		Agent: makeAzNodePoolAgent(),
		Local: cli,
	}
	task := newAzNodePoolTask(rctx, nil, nil, nil, nil, nil)
	assert.Equal(t, "NodePool", task.Name())
	assert.Equal(t, azNodePoolClusterName, task.ClusterMeta().K8sClusterName)
}

func TestNodePoolTask_Create(t *testing.T) {
	nodeMatchLabels := map[string]string{"app": "agent"}
	nodeConfigLabels := map[string]string{"role": "agent"}
	taints := []corev1.Taint{{Key: "dedicated", Value: "o11y", Effect: corev1.TaintEffectNoSchedule}}

	tests := []struct {
		name           string
		mutateAgent    func(*agentv2.KubernetesAgent)
		setupResources func(client.Client)
		getAKSConfig   AKSConfigProvider
		errMsg         string
		wantRequeue    time.Duration
		verify         func(*testing.T, client.Client, *agentv2.KubernetesAgent)
	}{
		{
			name:        "desired spec not found",
			mutateAgent: func(a *agentv2.KubernetesAgent) { util.SetAnnotation(a, consts.AnnotationKeyO11yDesiredSpec, "nope") },
			errMsg:      "desired spec not found: nope",
		},
		{
			name: "aks config provider error",
			getAKSConfig: func() (string, string, string, string, error) {
				return "", "", "", "", fmt.Errorf("provider boom")
			},
			errMsg: "provider boom",
		},
		{
			name:   "aks cluster not found",
			errMsg: "failed to get AKS cluster aks-cluster",
		},
		{
			name: "orchestrator version empty",
			setupResources: func(c client.Client) {
				cluster := makeAKSCluster()
				cluster.Status.AtProvider.CurrentKubernetesVersion = nil
				assert.NoError(t, c.Create(context.Background(), cluster))
			},
			errMsg: "orchestrator version is empty",
		},
		{
			name: "annotation version overrides cluster observed version",
			mutateAgent: func(a *agentv2.KubernetesAgent) {
				util.SetAnnotation(a, consts.AnnotationKeyO11yNodeVersion, "1.30.1")
			},
			setupResources: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), makeAKSCluster()))
			},
			verify: func(t *testing.T, c client.Client, a *agentv2.KubernetesAgent) {
				var np aksv1beta1.KubernetesClusterNodePool
				assert.NoError(t, c.Get(context.Background(), client.ObjectKey{Name: azPoolName("default")}, &np))
				assert.Equal(t, "1.30.1", *np.Spec.ForProvider.OrchestratorVersion)
				// Annotation is not overwritten when already set.
				assert.Equal(t, "1.30.1", util.GetAnnotation(a, consts.AnnotationKeyO11yNodeVersion))
			},
		},
		{
			name: "empty annotation falls back to cluster observed version and writes back",
			setupResources: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), makeAKSCluster()))
			},
			verify: func(t *testing.T, c client.Client, a *agentv2.KubernetesAgent) {
				var np aksv1beta1.KubernetesClusterNodePool
				assert.NoError(t, c.Get(context.Background(), client.ObjectKey{Name: azPoolName("default")}, &np))
				assert.Equal(t, "1.29.0", *np.Spec.ForProvider.OrchestratorVersion)
				// Annotation is populated from the cluster observed version on first reconcile.
				assert.Equal(t, "1.29.0", util.GetAnnotation(a, consts.AnnotationKeyO11yNodeVersion))
			},
		},
		{
			name: "providerConfigReference missing",
			setupResources: func(c client.Client) {
				cluster := makeAKSCluster()
				cluster.Spec.ProviderConfigReference = nil
				assert.NoError(t, c.Create(context.Background(), cluster))
			},
			errMsg: "providerConfigReference is empty",
		},
		{
			name: "invalid min size annotation",
			mutateAgent: func(a *agentv2.KubernetesAgent) {
				util.SetAnnotation(a, consts.AnnotationKeyO11yNodeMinSize, "bad")
			},
			setupResources: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), makeAKSCluster()))
			},
			errMsg: "invalid key 'o11y.cloud.pingcap.com/node-min-size'",
		},
		{
			name: "success default spec",
			setupResources: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), makeAKSCluster()))
			},
			verify: func(t *testing.T, c client.Client, a *agentv2.KubernetesAgent) {
				var np aksv1beta1.KubernetesClusterNodePool
				assert.NoError(t, c.Get(context.Background(), client.ObjectKey{Name: azPoolName("default")}, &np))
				assert.Equal(t, "Standard_D2s_v3", *np.Spec.ForProvider.VMSize)
				assert.Equal(t, "User", *np.Spec.ForProvider.Mode)
				assert.True(t, *np.Spec.ForProvider.EnableAutoScaling)
				assert.Equal(t, "1.29.0", *np.Spec.ForProvider.OrchestratorVersion)
				assert.Equal(t, "subnet-1", *np.Spec.ForProvider.VnetSubnetID)
				assert.Equal(t, "pod-subnet-1", *np.Spec.ForProvider.PodSubnetID)
				assert.Equal(t, azPoolName("default"), *np.Spec.ForProvider.NodeLabels["Name"])
				assert.Equal(t, "agent", *np.Spec.ForProvider.NodeLabels["role"])
				assert.Equal(t, "agent", np.Labels["app"])
				if assert.Len(t, np.Spec.ForProvider.NodeTaints, 1) {
					assert.Equal(t, "dedicated=o11y:NoSchedule", *np.Spec.ForProvider.NodeTaints[0])
				}
				assert.Equal(t, "azure-provider", np.Spec.ProviderConfigReference.Name)
			},
		},
		{
			name: "success without subnets",
			getAKSConfig: func() (string, string, string, string, error) {
				return "sub-1", "rg-1", "", "", nil
			},
			setupResources: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), makeAKSCluster()))
			},
			verify: func(t *testing.T, c client.Client, a *agentv2.KubernetesAgent) {
				var np aksv1beta1.KubernetesClusterNodePool
				assert.NoError(t, c.Get(context.Background(), client.ObjectKey{Name: azPoolName("default")}, &np))
				assert.Nil(t, np.Spec.ForProvider.VnetSubnetID)
				assert.Nil(t, np.Spec.ForProvider.PodSubnetID)
			},
		},
		{
			name: "pending scale - previous spec empty",
			mutateAgent: func(a *agentv2.KubernetesAgent) {
				util.SetAnnotation(a, consts.AnnotationKeyO11ySpecScaleStatus, consts.SpecScaleStatusPendingScale)
			},
			setupResources: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), makeAKSCluster()))
			},
			errMsg: "unexpected empty previous spec name",
		},
		{
			name: "pending scale - previous spec not found",
			mutateAgent: func(a *agentv2.KubernetesAgent) {
				util.SetAnnotation(a, consts.AnnotationKeyO11ySpecScaleStatus, consts.SpecScaleStatusPendingScale)
				util.SetAnnotation(a, consts.AnnotationKeyO11yPreviousSpec, "nope")
			},
			setupResources: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), makeAKSCluster()))
			},
			errMsg: "previous spec not found: nope",
		},
		{
			name: "pending scale - success creates both pools",
			mutateAgent: func(a *agentv2.KubernetesAgent) {
				util.SetAnnotation(a, consts.AnnotationKeyO11ySpecScaleStatus, consts.SpecScaleStatusPendingScale)
				util.SetAnnotation(a, consts.AnnotationKeyO11yDesiredSpec, "large")
				util.SetAnnotation(a, consts.AnnotationKeyO11yPreviousSpec, "default")
			},
			setupResources: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), makeAKSCluster()))
			},
			verify: func(t *testing.T, c client.Client, a *agentv2.KubernetesAgent) {
				var npL aksv1beta1.KubernetesClusterNodePool
				assert.NoError(t, c.Get(context.Background(), client.ObjectKey{Name: azPoolName("large")}, &npL))
				assert.Equal(t, "Standard_D4s_v3", *npL.Spec.ForProvider.VMSize)
				var npD aksv1beta1.KubernetesClusterNodePool
				assert.NoError(t, c.Get(context.Background(), client.ObjectKey{Name: azPoolName("default")}, &npD))
				assert.Equal(t, "Standard_D2s_v3", *npD.Spec.ForProvider.VMSize)
			},
		},
		{
			name: "resource updated - previous spec empty",
			mutateAgent: func(a *agentv2.KubernetesAgent) {
				util.SetAnnotation(a, consts.AnnotationKeyO11ySpecScaleStatus, consts.SpecScaleStatusResourceUpdated)
			},
			setupResources: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), makeAKSCluster()))
			},
			errMsg: "unexpected empty previous spec name",
		},
		{
			name: "resource updated - success",
			mutateAgent: func(a *agentv2.KubernetesAgent) {
				util.SetAnnotation(a, consts.AnnotationKeyO11ySpecScaleStatus, consts.SpecScaleStatusResourceUpdated)
				util.SetAnnotation(a, consts.AnnotationKeyO11yDesiredSpec, "large")
				util.SetAnnotation(a, consts.AnnotationKeyO11yPreviousSpec, "default")
			},
			setupResources: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), makeAKSCluster()))
				// previous pool is absent → deletion succeeds, check confirms gone.
			},
			verify: func(t *testing.T, c client.Client, a *agentv2.KubernetesAgent) {
				assert.Equal(t, consts.SpecScaleStatusNormal, util.GetAnnotation(a, consts.AnnotationKeyO11ySpecScaleStatus))
			},
		},
		{
			name: "resource updated - previous pool still present",
			mutateAgent: func(a *agentv2.KubernetesAgent) {
				util.SetAnnotation(a, consts.AnnotationKeyO11ySpecScaleStatus, consts.SpecScaleStatusResourceUpdated)
				util.SetAnnotation(a, consts.AnnotationKeyO11yDesiredSpec, "large")
				util.SetAnnotation(a, consts.AnnotationKeyO11yPreviousSpec, "default")
			},
			setupResources: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), makeAKSCluster()))
				// Pre-create pool with a finalizer so Delete won't actually remove it.
				prev := &aksv1beta1.KubernetesClusterNodePool{
					ObjectMeta: metav1.ObjectMeta{
						Name:       azPoolName("default"),
						Finalizers: []string{"keep.me/finalizer"},
					},
				}
				assert.NoError(t, c.Create(context.Background(), prev))
			},
			errMsg:      "is still present",
			wantRequeue: time.Minute,
		},
		{
			name: "resource updated - previous pool stalled pending create",
			mutateAgent: func(a *agentv2.KubernetesAgent) {
				util.SetAnnotation(a, consts.AnnotationKeyO11ySpecScaleStatus, consts.SpecScaleStatusResourceUpdated)
				util.SetAnnotation(a, consts.AnnotationKeyO11yDesiredSpec, "large")
				util.SetAnnotation(a, consts.AnnotationKeyO11yPreviousSpec, "default")
			},
			setupResources: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), makeAKSCluster()))
				prev := &aksv1beta1.KubernetesClusterNodePool{
					ObjectMeta: metav1.ObjectMeta{
						Name:       azPoolName("default"),
						Finalizers: []string{"keep.me/finalizer"},
					},
				}
				meta.SetExternalCreatePending(prev, time.Now().Add(-11*time.Minute))
				meta.SetExternalCreateFailed(prev, time.Now().Add(-12*time.Minute))
				assert.NoError(t, c.Create(context.Background(), prev))
			},
			errMsg:      "is still present",
			wantRequeue: time.Minute,
			verify: func(t *testing.T, c client.Client, a *agentv2.KubernetesAgent) {
				var np aksv1beta1.KubernetesClusterNodePool
				assert.NoError(t, c.Get(context.Background(), client.ObjectKey{Name: azPoolName("default")}, &np))
				assert.Zero(t, meta.GetExternalCreatePending(&np))
				assert.False(t, meta.GetExternalCreateFailed(&np).IsZero())
			},
		},
	}

	specs := config.CustomerAgentSpecs{
		{Name: "default", InstanceType: "Standard_D2s_v3"},
		{Name: "large", InstanceType: "Standard_D4s_v3"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			agent := makeAzNodePoolAgent()
			if tt.mutateAgent != nil {
				tt.mutateAgent(agent)
			}
			cli := testkits.BuildClient(agent)
			if tt.setupResources != nil {
				tt.setupResources(cli)
			}
			rctx := &reconciler.Context[*agentv2.KubernetesAgent]{
				Agent:  agent,
				Local:  cli,
				Config: &config.RootConfig{},
			}
			task := newAzNodePoolTask(rctx, specs, nodeMatchLabels, nodeConfigLabels, taints, tt.getAKSConfig)
			res := task.Create(context.Background())

			if tt.errMsg != "" {
				assert.Error(t, res.Error())
				assert.Contains(t, res.Error().Error(), tt.errMsg)
				if tt.wantRequeue > 0 {
					assertAzResultRequeueAfter(t, res, tt.wantRequeue)
				}
			} else {
				assert.NoError(t, res.Error())
				assert.Equal(t, deploy.Done(), res)
			}

			if tt.verify != nil {
				tt.verify(t, cli, agent)
			}
		})
	}
}

func TestNodePoolTask_PostCreate(t *testing.T) {
	nodeMatchLabels := map[string]string{"app": "agent"}
	nodeConfigLabels := map[string]string{"role": "agent"}
	specs := config.CustomerAgentSpecs{{Name: "default", InstanceType: "Standard_D2s_v3"}}

	readyPool := func() *aksv1beta1.KubernetesClusterNodePool {
		np := &aksv1beta1.KubernetesClusterNodePool{
			ObjectMeta: metav1.ObjectMeta{Name: azPoolName("default")},
		}
		np.Status.SetConditions(xpv1.Available())
		return np
	}
	notReadyPool := func() *aksv1beta1.KubernetesClusterNodePool {
		np := &aksv1beta1.KubernetesClusterNodePool{
			ObjectMeta: metav1.ObjectMeta{Name: azPoolName("default")},
		}
		np.Status.SetConditions(xpv1.Creating())
		return np
	}

	tests := []struct {
		name        string
		mutateAgent func(*agentv2.KubernetesAgent)
		setup       func(client.Client)
		errMsg      string
		wantRequeue time.Duration
		verify      func(*testing.T, *agentv2.KubernetesAgent)
		verifyCli   func(*testing.T, client.Client)
	}{
		{
			name:        "missing pool",
			errMsg:      "failed to get node pool",
			wantRequeue: time.Minute,
		},
		{
			name: "not ready",
			setup: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), notReadyPool()))
			},
			errMsg:      "is not ready",
			wantRequeue: time.Minute,
		},
		{
			name: "not ready, clear stalled pending create",
			setup: func(c client.Client) {
				np := notReadyPool()
				meta.SetExternalCreatePending(np, time.Now().Add(-11*time.Minute))
				meta.SetExternalCreateFailed(np, time.Now().Add(-12*time.Minute))
				assert.NoError(t, c.Create(context.Background(), np))
			},
			errMsg:      "is not ready",
			wantRequeue: time.Minute,
			verifyCli: func(t *testing.T, c client.Client) {
				var np aksv1beta1.KubernetesClusterNodePool
				assert.NoError(t, c.Get(context.Background(), client.ObjectKey{Name: azPoolName("default")}, &np))
				assert.Zero(t, meta.GetExternalCreatePending(&np))
				assert.False(t, meta.GetExternalCreateFailed(&np).IsZero())
			},
		},
		{
			name: "ready - no scale annotation",
			setup: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), readyPool()))
			},
		},
		{
			name: "ready - pending scale transitions",
			mutateAgent: func(a *agentv2.KubernetesAgent) {
				util.SetAnnotation(a, consts.AnnotationKeyO11ySpecScaleStatus, consts.SpecScaleStatusPendingScale)
			},
			setup: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), readyPool()))
			},
			verify: func(t *testing.T, a *agentv2.KubernetesAgent) {
				assert.Equal(t, consts.SpecScaleStatusNewNodeGroupSynced, util.GetAnnotation(a, consts.AnnotationKeyO11ySpecScaleStatus))
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			agent := makeAzNodePoolAgent()
			if tt.mutateAgent != nil {
				tt.mutateAgent(agent)
			}
			cli := testkits.BuildClient(agent)
			if tt.setup != nil {
				tt.setup(cli)
			}
			rctx := &reconciler.Context[*agentv2.KubernetesAgent]{Agent: agent, Local: cli, Config: &config.RootConfig{}}
			task := newAzNodePoolTask(rctx, specs, nodeMatchLabels, nodeConfigLabels, nil, nil)
			res := task.PostCreate(context.Background())
			if tt.errMsg != "" {
				assert.Error(t, res.Error())
				assert.Contains(t, res.Error().Error(), tt.errMsg)
				if tt.wantRequeue > 0 {
					assertAzResultRequeueAfter(t, res, tt.wantRequeue)
				}
			} else {
				assert.NoError(t, res.Error())
				assert.Equal(t, deploy.Done(), res)
			}
			if tt.verify != nil {
				tt.verify(t, agent)
			}
			if tt.verifyCli != nil {
				tt.verifyCli(t, cli)
			}
		})
	}
}

func TestNodePoolTask_Delete(t *testing.T) {
	nodeMatchLabels := map[string]string{"app": "agent"}
	nodeConfigLabels := map[string]string{"role": "agent"}

	t.Run("deletes default and labeled pools", func(t *testing.T) {
		agent := makeAzNodePoolAgent()
		cli := testkits.BuildClient(agent)

		// default-named pool
		assert.NoError(t, cli.Create(context.Background(), &aksv1beta1.KubernetesClusterNodePool{
			ObjectMeta: metav1.ObjectMeta{Name: azPoolName("default")},
		}))
		// labeled pool
		assert.NoError(t, cli.Create(context.Background(), &aksv1beta1.KubernetesClusterNodePool{
			ObjectMeta: metav1.ObjectMeta{Name: "labeled", Labels: nodeMatchLabels},
		}))
		// other pool that should remain
		assert.NoError(t, cli.Create(context.Background(), &aksv1beta1.KubernetesClusterNodePool{
			ObjectMeta: metav1.ObjectMeta{Name: "keep"},
		}))

		rctx := &reconciler.Context[*agentv2.KubernetesAgent]{Agent: agent, Local: cli}
		task := newAzNodePoolTask(rctx, nil, nodeMatchLabels, nodeConfigLabels, nil, nil)
		res := task.Delete(context.Background())
		assert.NoError(t, res.Error())
		assert.Equal(t, deploy.Done(), res)

		// default and labeled are gone.
		err := cli.Get(context.Background(), client.ObjectKey{Name: azPoolName("default")}, &aksv1beta1.KubernetesClusterNodePool{})
		assert.True(t, client.IgnoreNotFound(err) == nil && err != nil)
		err = cli.Get(context.Background(), client.ObjectKey{Name: "labeled"}, &aksv1beta1.KubernetesClusterNodePool{})
		assert.True(t, client.IgnoreNotFound(err) == nil && err != nil)
		// keep still present
		assert.NoError(t, cli.Get(context.Background(), client.ObjectKey{Name: "keep"}, &aksv1beta1.KubernetesClusterNodePool{}))
	})

	t.Run("noop when no labels", func(t *testing.T) {
		agent := makeAzNodePoolAgent()
		cli := testkits.BuildClient(agent)
		rctx := &reconciler.Context[*agentv2.KubernetesAgent]{Agent: agent, Local: cli}
		task := newAzNodePoolTask(rctx, nil, nil, nil, nil, nil)
		res := task.Delete(context.Background())
		assert.NoError(t, res.Error())
	})
}

func TestNodePoolTask_PostDelete(t *testing.T) {
	nodeMatchLabels := map[string]string{"app": "agent"}

	tests := []struct {
		name        string
		setup       func(client.Client)
		errMsg      string
		wantRequeue time.Duration
		verifyCli   func(*testing.T, client.Client)
	}{
		{
			name: "success - nothing present",
		},
		{
			name: "default pool still present",
			setup: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), &aksv1beta1.KubernetesClusterNodePool{
					ObjectMeta: metav1.ObjectMeta{Name: azPoolName("default")},
				}))
			},
			errMsg:      "is still present",
			wantRequeue: time.Minute,
		},
		{
			name: "default pool still present, clear stalled pending create",
			setup: func(c client.Client) {
				np := &aksv1beta1.KubernetesClusterNodePool{
					ObjectMeta: metav1.ObjectMeta{Name: azPoolName("default")},
				}
				meta.SetExternalCreatePending(np, time.Now().Add(-11*time.Minute))
				meta.SetExternalCreateFailed(np, time.Now().Add(-12*time.Minute))
				assert.NoError(t, c.Create(context.Background(), np))
			},
			errMsg:      "is still present",
			wantRequeue: time.Minute,
			verifyCli: func(t *testing.T, c client.Client) {
				var np aksv1beta1.KubernetesClusterNodePool
				assert.NoError(t, c.Get(context.Background(), client.ObjectKey{Name: azPoolName("default")}, &np))
				assert.Zero(t, meta.GetExternalCreatePending(&np))
				assert.False(t, meta.GetExternalCreateFailed(&np).IsZero())
			},
		},
		{
			name: "labeled pool still present",
			setup: func(c client.Client) {
				assert.NoError(t, c.Create(context.Background(), &aksv1beta1.KubernetesClusterNodePool{
					ObjectMeta: metav1.ObjectMeta{Name: "labeled", Labels: nodeMatchLabels},
				}))
			},
			errMsg:      "are still present",
			wantRequeue: time.Minute,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			agent := makeAzNodePoolAgent()
			cli := testkits.BuildClient(agent)
			if tt.setup != nil {
				tt.setup(cli)
			}
			rctx := &reconciler.Context[*agentv2.KubernetesAgent]{Agent: agent, Local: cli}
			task := newAzNodePoolTask(rctx, nil, nodeMatchLabels, nil, nil, nil)
			res := task.PostDelete(context.Background())
			if tt.errMsg != "" {
				assert.Error(t, res.Error())
				assert.Contains(t, res.Error().Error(), tt.errMsg)
				if tt.wantRequeue > 0 {
					assertAzResultRequeueAfter(t, res, tt.wantRequeue)
				}
			} else {
				assert.NoError(t, res.Error())
				assert.Equal(t, deploy.Done(), res)
			}
			if tt.verifyCli != nil {
				tt.verifyCli(t, cli)
			}
		})
	}
}

func TestNodePoolTask_PostDelete_NoLabels(t *testing.T) {
	agent := makeAzNodePoolAgent()
	cli := testkits.BuildClient(agent)
	rctx := &reconciler.Context[*agentv2.KubernetesAgent]{Agent: agent, Local: cli}
	task := newAzNodePoolTask(rctx, nil, nil, nil, nil, nil)
	res := task.PostDelete(context.Background())
	assert.NoError(t, res.Error())
}
