package common_test

import (
	"context"
	"encoding/base64"
	"reflect"
	"strings"
	"testing"
	"time"

	"cloud.google.com/go/container/apiv1/containerpb"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/go-logr/logr/testr"
	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"
	grpcscodes "google.golang.org/grpc/codes"
	grpcstatus "google.golang.org/grpc/status"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"

	eksv1beta1 "github.com/crossplane-contrib/provider-aws/apis/eks/v1beta1"
	gkev1beta2 "github.com/crossplane-contrib/provider-gcp/apis/container/v1beta2"
	clusterctlpb "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	regionalpb "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"github.com/tidbcloud/infra-provider/pkg/deploy"
	"github.com/tidbcloud/infra-provider/pkg/util"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/approute/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/approute/task/common"
	"github.com/tidbcloud/infra-provider/regional-server/test/service/mockclustercli"
	"github.com/tidbcloud/infra-provider/regional-server/test/service/mockgcpcontainer"
	"github.com/tidbcloud/infra-provider/regional-server/test/testdata"
	aksv1beta1 "github.com/upbound/provider-azure/apis/containerservice/v1beta1"
)

func TestClusterPBConverion(t *testing.T) {
	defaultAppRoute := new(v1alpha1.APPRoute)
	bs, err := testdata.LoadDefaultAPPRouteYAML()
	require.NoError(t, err)
	require.NoError(t, yaml.Unmarshal(bs, defaultAppRoute))
	{
		rpbCluster, err := common.GetClusterFromAPPRoute(defaultAppRoute)
		require.NoError(t, err)
		ccpbCluster, err := common.RpbClusterToCcpbCluster(rpbCluster)
		require.NoError(t, err)
		require.Equal(t, rpbCluster.Id, ccpbCluster.Id)
		require.Equal(t, rpbCluster.ProjectId, ccpbCluster.ProjectId)
		require.Equal(t, rpbCluster.TenantId, ccpbCluster.TenantId)
		require.GreaterOrEqual(t, len(ccpbCluster.ComponentNodeSpecs), len(rpbCluster.ComponentNodeSpecs))
		require.Equal(t, len(rpbCluster.ComponentConfigurations), len(ccpbCluster.ComponentConfigurations))

		require.Equal(t, len(rpbCluster.ClusterVersion.GetClusterVersion()), len(ccpbCluster.ClusterVersion.GetClusterVersion()))
		require.Equal(t, rpbCluster.ClusterVersion.GetClusterVersion(), ccpbCluster.ClusterVersion.GetClusterVersion())
		require.Equal(t, len(rpbCluster.ClusterVersion.GetComponentVersions()), len(ccpbCluster.ClusterVersion.GetComponentVersions()))

		require.Equal(t, rpbCluster.SecurityConfigurations.GetCmekAwsMasterKeyArn(), ccpbCluster.SecurityConfigurations.GetCmekAwsMasterKeyArn())
		require.Equal(t, rpbCluster.SecurityConfigurations.GetTidbAuthTokenJwks(), ccpbCluster.SecurityConfigurations.GetTidbAuthTokenJwks())

		require.Equal(t, rpbCluster.ClusterIdentifierHash, ccpbCluster.ClusterIdentifierHash)
		require.Equal(t, rpbCluster.Port, ccpbCluster.Port)
		require.Equal(t, rpbCluster.InitSqlStatements, ccpbCluster.InitSqlStatements)
		require.Equal(t, clusterctlpb.Cluster_CLUSTER_PHASE_UNSPECIFIED, ccpbCluster.ClusterPhase)
	}
	{
		approute := defaultAppRoute.DeepCopy()
		rpbCluster, err := common.GetClusterFromAPPRoute(approute)
		require.NoError(t, err)
		rpbCluster.SecurityConfigurations.Cmek = &regionalpb.Cluster_SecurityConfigurations_CmekGcpMasterKeyName{
			CmekGcpMasterKeyName: "projects/foo/locations/us-west1/keyRings/foo/cryptoKeys/foo",
		}
		rpbCluster.SecurityConfigurations.AuditLogConfiguration.Storage = &regionalpb.AuditLogConfiguration_Gcs{
			Gcs: &regionalpb.AuditLogConfiguration_GCS{},
		}
		ccpbCluster, err := common.RpbClusterToCcpbCluster(rpbCluster)
		require.NoError(t, err)

		require.Equal(t, rpbCluster.SecurityConfigurations.GetCmekGcpMasterKeyName(), ccpbCluster.SecurityConfigurations.GetCmekGcpMasterKeyName())
		require.Equal(t, rpbCluster.SecurityConfigurations.GetTidbAuthTokenJwks(), ccpbCluster.SecurityConfigurations.GetTidbAuthTokenJwks())
	}
}

func TestClusterAWS(t *testing.T) {
	s := runtime.NewScheme()
	require.NoError(t, v1alpha1.AddToScheme(s))
	require.NoError(t, eksv1beta1.SchemeBuilder.AddToScheme(s))
	logger := testr.New(t)
	ctrl := gomock.NewController(t)
	clusterCli := mockclustercli.NewMockClusterClient(ctrl)
	defaultAPPBox := new(v1alpha1.APPBox)
	defaultAppRoute := new(v1alpha1.APPRoute)
	defaultVPC := new(v1alpha1.VPC)
	{
		bs, err := testdata.LoadDefaultAPPBoxYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultAPPBox))
	}
	{
		bs, err := testdata.LoadDefaultAPPRouteYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultAppRoute))
	}
	{
		bs, err := testdata.LoadDefaultVPCYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultVPC))
	}
	{
		// normal case should not return any error.
		approute := defaultAppRoute.DeepCopy()
		approute.Status.Phase = v1alpha1.APPRoutePhasePending
		appbox := defaultAPPBox.DeepCopy()
		appbox.Status.Phase = v1alpha1.APPBoxPhaseActive
		eks := new(eksv1beta1.Cluster)
		eks.Name = "foo"
		eks.Status.AtProvider.CertificateAuthorityData = "Y2EtZGF0YQ=="
		eks.Status.AtProvider.Endpoint = "http://localhost"
		eks.Status.AtProvider.ResourcesVpcConfig.VpcID = "vpc-foo"
		eks.Status.AtProvider.ResourcesVpcConfig.ClusterSecurityGroupID = "eks-sg-foo"
		meta.SetExternalName(eks, "foo")
		vpc := defaultVPC.DeepCopy()
		vpc.Status.SecurityGroup = v1alpha1.SecurityGroup{
			Name:            "foo",
			SecurityGroupID: "sg-foo",
		}
		vpc.Status.NodeSubnets = []v1alpha1.Subnet{{Name: "node-subnet-1"}}
		vpc.Status.PublicSubnets = []v1alpha1.Subnet{{Name: "public-subnet-1"}}
		vpc.Status.PrivateSubnets = []v1alpha1.Subnet{{Name: "private-subnet-1"}}
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(approute, appbox, eks, vpc).WithStatusSubresource(approute, appbox, eks, vpc).Build()
		c, err := (&task.Context{
			Scheme:     s,
			Local:      kubeClient,
			Logger:     logger,
			APPBox:     appbox,
			APPRoute:   approute,
			BeginAt:    util.Pointer(time.Now()),
			ClusterCli: clusterCli,
		}).Default()
		require.NoError(t, err)
		task := common.NewCluster(c)
		{
			clusterCli.EXPECT().CreateCluster(
				gomock.Any(),
				gomock.Cond(func(arg any) bool {
					var checkk8sInfo = func(k8sInfo *clusterctlpb.Kubernetes) bool {
						if k8sInfo.Name != "foo" {
							return false
						}
						if k8sInfo.AccountId != "foo" {
							return false
						}
						if k8sInfo.Vpc != "vpc-foo" {
							return false
						}
						if k8sInfo.Version != "1.27" {
							return false
						}
						if k8sInfo.Endpoint != "http://localhost" {
							return false
						}
						if k8sInfo.Ca != "ca-data" {
							return false
						}
						return true
					}
					var checkAWS = func(awsParam *clusterctlpb.Kubernetes_Aws) bool {
						if awsParam.SecurityGroup != "eks-sg-foo" {
							return false
						}
						if len(awsParam.NodeSubnets) != 1 {
							return false
						}
						if len(awsParam.PublicLoadbalancerSubnets) != 1 {
							return false
						}
						if len(awsParam.PrivateLoadbalancerSubnets) != 1 {
							return false
						}
						return true
					}
					req, ok := arg.(*clusterctlpb.CreateClusterRequest)
					if !ok {
						return false
					}
					k8sInfo := req.Cluster.Kubernetes
					if k8sInfo == nil {
						t.Log("kubernetes is nil")
						return false
					}
					awsParam := k8sInfo.GetAws()
					if awsParam == nil {
						t.Log("aws parameter is nil")
						return false
					}
					if !checkk8sInfo(k8sInfo) {
						t.Log("kubernetes is not correct")
						return false
					}
					if !checkAWS(awsParam) {
						t.Log("aws parameter is not correct")
						return false
					}
					return true
				})).
				Return(new(clusterctlpb.Cluster), nil).
				MinTimes(1)
			d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
			results := d.Deploy(context.TODO())
			require.Contains(t, task.APPRoute.GetFinalizers(), v1alpha1.Finalizer)
			require.NoError(t, results[0].Error())
			require.True(t, isAllDone(results), "deploy results: %v", results)
			res, _, _ := deploy.MinRequeue(results)
			require.Equal(t, res.RequeueAfter, time.Duration(0))
			// ↓ update approute cr
			approute.Status.Phase = v1alpha1.APPRoutePhaseActive
			require.NoError(t, kubeClient.Status().Update(context.TODO(), approute))
			require.NoError(t, kubeClient.Update(context.TODO(), approute))
			// ↑ update approute cr
			ctrl.Satisfied()
		}
		{
			clusterCli.EXPECT().DeleteCluster(
				gomock.Any(),
				gomock.Cond(func(arg any) bool {
					req, ok := arg.(*clusterctlpb.DeleteClusterRequest)
					if !ok {
						return false
					}
					if req.ClusterId != "foo" {
						return false
					}
					return true
				})).
				Return(new(clusterctlpb.Cluster), nil).
				MinTimes(1)
			clusterCli.EXPECT().GetCluster(gomock.Any(), gomock.Any()).Return(nil, grpcstatus.New(grpcscodes.NotFound, "not found").Err()).MinTimes(1)
			d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
			results := d.Destroy(context.TODO())
			require.Contains(t, task.APPRoute.GetFinalizers(), v1alpha1.Finalizer)
			require.NoError(t, results[0].Error())
			require.True(t, isAllDone(results), "deploy results: %v", results)
			res, _, _ := deploy.MinRequeue(results)
			require.Equal(t, res.RequeueAfter, time.Duration(0))
			ctrl.Satisfied()
		}
	}
	{
		// appbox status is not active
		approute := defaultAppRoute.DeepCopy()
		approute.Status.Phase = v1alpha1.APPRoutePhasePending
		appbox := defaultAPPBox.DeepCopy()
		appbox.Status.Phase = v1alpha1.APPBoxPhaseDeleting
		eks := new(eksv1beta1.Cluster)
		eks.Name = "foo"
		eks.Status.AtProvider.CertificateAuthorityData = "Y2EtZGF0YQ=="
		eks.Status.AtProvider.Endpoint = "http://localhost"
		eks.Status.AtProvider.ResourcesVpcConfig.VpcID = "vpc-foo"
		vpc := defaultVPC.DeepCopy()
		vpc.Status.SecurityGroup = v1alpha1.SecurityGroup{
			Name:            "foo",
			SecurityGroupID: "sg-foo",
		}
		vpc.Status.NodeSubnets = []v1alpha1.Subnet{{Name: "node-subnet-1"}}
		vpc.Status.PublicSubnets = []v1alpha1.Subnet{{Name: "public-subnet-1"}}
		vpc.Status.PrivateSubnets = []v1alpha1.Subnet{{Name: "private-subnet-1"}}
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(approute, appbox, eks).WithStatusSubresource(approute, appbox, eks).Build()
		c, err := (&task.Context{
			Scheme:     s,
			Local:      kubeClient,
			Logger:     logger,
			APPBox:     appbox,
			APPRoute:   approute,
			BeginAt:    util.Pointer(time.Now()),
			ClusterCli: clusterCli,
		}).Default()
		require.NoError(t, err)
		task := common.NewCluster(c)
		clusterCli.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).Times(0)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
		results := d.Deploy(context.TODO())
		require.Contains(t, task.APPRoute.GetFinalizers(), v1alpha1.Finalizer)
		require.False(t, isAllDone(results), "deploy results: %v", results)
		require.Error(t, results[0].Error())
		res, err, _ := deploy.MinRequeue(results)
		require.Condition(t, func() (success bool) {
			// will reconcile again
			return err != nil || res.Requeue
		})
		ctrl.Satisfied()
	}
	{
		// vpc not existed
		approute := defaultAppRoute.DeepCopy()
		approute.Status.Phase = v1alpha1.APPRoutePhasePending
		appbox := defaultAPPBox.DeepCopy()
		appbox.Status.Phase = v1alpha1.APPBoxPhaseActive
		eks := new(eksv1beta1.Cluster)
		eks.Name = "foo"
		eks.Status.AtProvider.CertificateAuthorityData = "Y2EtZGF0YQ=="
		eks.Status.AtProvider.Endpoint = "http://localhost"
		eks.Status.AtProvider.ResourcesVpcConfig.VpcID = "vpc-foo"
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(approute, appbox, eks).WithStatusSubresource(approute, appbox, eks).Build()
		c, err := (&task.Context{
			Scheme:     s,
			Local:      kubeClient,
			Logger:     logger,
			APPBox:     appbox,
			APPRoute:   approute,
			BeginAt:    util.Pointer(time.Now()),
			ClusterCli: clusterCli,
		}).Default()
		require.NoError(t, err)
		task := common.NewCluster(c)
		clusterCli.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).Times(0)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
		results := d.Deploy(context.TODO())
		require.Contains(t, task.APPRoute.GetFinalizers(), v1alpha1.Finalizer)
		require.False(t, isAllDone(results), "deploy results: %v", results)
		require.Error(t, results[0].Error())
		res, err, _ := deploy.MinRequeue(results)
		require.Condition(t, func() (success bool) {
			// will reconcile again
			return err != nil || res.Requeue
		})
		ctrl.Satisfied()
	}
}

func TestClusterGCP(t *testing.T) {
	s := runtime.NewScheme()
	require.NoError(t, v1alpha1.AddToScheme(s))
	require.NoError(t, gkev1beta2.SchemeBuilder.AddToScheme(s))
	logger := testr.New(t)
	ctrl := gomock.NewController(t)
	clusterCli := mockclustercli.NewMockClusterClient(ctrl)
	gcpContainerCli := mockgcpcontainer.NewMockClusterGetter(ctrl)
	defaultAPPBox := new(v1alpha1.APPBox)
	defaultAppRoute := new(v1alpha1.APPRoute)
	defaultVPC := new(v1alpha1.VPC)
	{
		bs, err := testdata.LoadDefaultAPPBoxYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultAPPBox))
	}
	{
		bs, err := testdata.LoadDefaultAPPRouteYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultAppRoute))
	}
	{
		bs, err := testdata.LoadDefaultVPCYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultVPC))
	}
	{
		// normal case should not return any error.
		approute := defaultAppRoute.DeepCopy()
		approute.Status.Phase = v1alpha1.APPRoutePhasePending
		appbox := defaultAPPBox.DeepCopy()
		appbox.Labels["cloud.pingcap.com/region"] = "us-west1"
		appbox.Spec.Provider = "gke"
		appbox.Spec.Region = "us-west1"
		appbox.Status.Phase = v1alpha1.APPBoxPhaseActive
		gke := new(gkev1beta2.Cluster)
		gke.Name = "foo"
		gke.Spec.ForProvider.Locations = []string{"us-west1-a", "us-west1-b", "us-west1-c"}
		gke.Status.AtProvider.Endpoint = "127.0.0.1"
		gke.Status.AtProvider.Location = "us-west1"
		gke.Status.AtProvider.NetworkConfig = &gkev1beta2.NetworkConfigStatus{
			Network: "vpc-foo",
		}
		meta.SetExternalName(gke, "foo")
		vpc := defaultVPC.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(approute, appbox, gke, vpc).WithStatusSubresource(approute, appbox, gke, vpc).Build()
		c, err := (&task.Context{
			Scheme:          s,
			Local:           kubeClient,
			Logger:          logger,
			APPBox:          appbox,
			APPRoute:        approute,
			BeginAt:         util.Pointer(time.Now()),
			ClusterCli:      clusterCli,
			GCPContainerCli: gcpContainerCli,
		}).Default()
		require.NoError(t, err)
		task := common.NewCluster(c)
		{
			gcpContainerCli.EXPECT().GetCluster(
				gomock.Any(),
				gomock.Cond(func(arg any) bool {
					req, ok := arg.(*containerpb.GetClusterRequest)
					if !ok {
						return false
					}
					if req.GetName() != "projects/foo/locations/us-west1/clusters/foo" {
						return false
					}
					return true
				}),
			).Return(&containerpb.Cluster{
				Name: "projects/foo/locations/us-west1/clusters/foo",
				MasterAuth: &containerpb.MasterAuth{
					ClusterCaCertificate: "Y2EtZGF0YQ==",
				},
			}, nil).Times(1)
			clusterCli.EXPECT().CreateCluster(
				gomock.Any(),
				gomock.Cond(func(arg any) bool {
					var checkk8sInfo = func(k8sInfo *clusterctlpb.Kubernetes) bool {
						if k8sInfo.Name != "foo" {
							return false
						}
						if k8sInfo.AccountId != "foo" {
							return false
						}
						if k8sInfo.Vpc != "vpc-foo" {
							return false
						}
						if k8sInfo.Version != "1.27" {
							return false
						}
						if k8sInfo.Endpoint != "https://127.0.0.1" {
							return false
						}
						if k8sInfo.Ca != "ca-data" {
							return false
						}
						return true
					}
					var checkGCP = func(gcpParam *clusterctlpb.Kubernetes_Gcp) bool {
						return strings.Join(gcpParam.NodeZones, ",") == "us-west1-a,us-west1-b,us-west1-c"
					}
					req, ok := arg.(*clusterctlpb.CreateClusterRequest)
					if !ok {
						return false
					}
					k8sInfo := req.Cluster.Kubernetes
					if k8sInfo == nil {
						t.Log("kubernetes is nil")
						return false
					}
					gcpParam := k8sInfo.GetGcp()
					if gcpParam == nil {
						t.Log("gcp parameter is nil")
						return false
					}
					if !checkk8sInfo(k8sInfo) {
						t.Logf("kubernetes is not correct; got %#v", k8sInfo)
						return false
					}
					if !checkGCP(gcpParam) {
						t.Logf("gcp parameter is not correct; got %#v", gcpParam)
						return false
					}
					return true
				})).
				Return(new(clusterctlpb.Cluster), nil).
				MinTimes(1)
			d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
			results := d.Deploy(context.TODO())
			require.Contains(t, task.APPRoute.GetFinalizers(), v1alpha1.Finalizer)
			require.NoError(t, results[0].Error())
			require.True(t, isAllDone(results), "deploy results: %v", results)
			res, _, _ := deploy.MinRequeue(results)
			require.Equal(t, res.RequeueAfter, time.Duration(0))
			// ↓ update approute cr
			approute.Status.Phase = v1alpha1.APPRoutePhaseActive
			require.NoError(t, kubeClient.Status().Update(context.TODO(), approute))
			require.NoError(t, kubeClient.Update(context.TODO(), approute))
			// ↑ update approute cr
			ctrl.Satisfied()
		}
		{
			clusterCli.EXPECT().DeleteCluster(
				gomock.Any(),
				gomock.Cond(func(arg any) bool {
					req, ok := arg.(*clusterctlpb.DeleteClusterRequest)
					if !ok {
						return false
					}
					if req.ClusterId != "foo" {
						return false
					}
					return true
				})).
				Return(new(clusterctlpb.Cluster), nil).
				MinTimes(1)
			clusterCli.EXPECT().GetCluster(gomock.Any(), gomock.Any()).Return(nil, grpcstatus.New(grpcscodes.NotFound, "not found").Err()).MinTimes(1)
			d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
			results := d.Destroy(context.TODO())
			require.Contains(t, task.APPRoute.GetFinalizers(), v1alpha1.Finalizer)
			require.NoError(t, results[0].Error())
			require.True(t, isAllDone(results), "deploy results: %v", results)
			res, _, _ := deploy.MinRequeue(results)
			require.Equal(t, res.RequeueAfter, time.Duration(0))
			ctrl.Satisfied()
		}
	}
	{
		// call gcp container service failed
		approute := defaultAppRoute.DeepCopy()
		approute.Status.Phase = v1alpha1.APPRoutePhasePending
		appbox := defaultAPPBox.DeepCopy()
		appbox.Labels["cloud.pingcap.com/region"] = "us-west1"
		appbox.Spec.Provider = "gke"
		appbox.Spec.Region = "us-west1"
		appbox.Status.Phase = v1alpha1.APPBoxPhaseActive
		gke := new(gkev1beta2.Cluster)
		gke.Name = "foo"
		gke.Spec.ForProvider.Locations = []string{"us-west1-a", "us-west1-b", "us-west1-c"}
		gke.Status.AtProvider.Endpoint = "127.0.0.1"
		gke.Status.AtProvider.Location = "us-west1"
		gke.Status.AtProvider.NetworkConfig = &gkev1beta2.NetworkConfigStatus{
			Network: "vpc-foo",
		}
		meta.SetExternalName(gke, "foo")
		vpc := defaultVPC.DeepCopy()
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(approute, appbox, gke, vpc).WithStatusSubresource(approute, appbox, gke, vpc).Build()
		c, err := (&task.Context{
			Scheme:          s,
			Local:           kubeClient,
			Logger:          logger,
			APPBox:          appbox,
			APPRoute:        approute,
			BeginAt:         util.Pointer(time.Now()),
			ClusterCli:      clusterCli,
			GCPContainerCli: gcpContainerCli,
		}).Default()
		require.NoError(t, err)
		task := common.NewCluster(c)
		gcpContainerCli.EXPECT().
			GetCluster(gomock.Any(), gomock.Any()).
			Return(nil, grpcstatus.Error(grpcscodes.Internal, "internal error")).
			Times(1)
		clusterCli.EXPECT().CreateCluster(gomock.Any(), gomock.Any()).Times(0)
		d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
		results := d.Deploy(context.TODO())
		require.Contains(t, task.APPRoute.GetFinalizers(), v1alpha1.Finalizer)
		require.False(t, isAllDone(results), "deploy results: %v", results)
		require.Error(t, results[0].Error())
		res, err, _ := deploy.MinRequeue(results)
		require.Condition(t, func() (success bool) {
			// will reconcile again
			return err != nil || res.Requeue
		})
		ctrl.Satisfied()
	}
}

func TestClusterAzure(t *testing.T) {
	s := runtime.NewScheme()
	require.NoError(t, v1alpha1.AddToScheme(s))
	require.NoError(t, aksv1beta1.SchemeBuilder.AddToScheme(s))
	require.NoError(t, corev1.AddToScheme(s))
	logger := testr.New(t)
	ctrl := gomock.NewController(t)
	clusterCli := mockclustercli.NewMockClusterClient(ctrl)
	defaultAPPBox := new(v1alpha1.APPBox)
	defaultAppRoute := new(v1alpha1.APPRoute)
	defaultVPC := new(v1alpha1.VPC)
	{
		bs, err := testdata.LoadAKSVPCYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultVPC))
	}
	{
		bs, err := testdata.LoadAKSAPPBoxYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultAPPBox))
	}
	{
		bs, err := testdata.LoadDefaultAPPRouteYAML()
		require.NoError(t, err)
		require.NoError(t, yaml.Unmarshal(bs, defaultAppRoute))
	}
	{
		// normal case should not return any error.
		approute := defaultAppRoute.DeepCopy()
		approute.Status.Phase = v1alpha1.APPRoutePhasePending
		appbox := defaultAPPBox.DeepCopy()
		appbox.Labels["cloud.pingcap.com/region"] = "eastus"
		appbox.Status.Phase = v1alpha1.APPBoxPhaseActive
		appbox.Status.KubernetesClusterName = "aks-foo"
		appbox.Status.KubeConfigSecretRef = &corev1.SecretReference{
			Name:      "secret-foo",
			Namespace: "default",
		}
		aks := &aksv1beta1.KubernetesCluster{
			ObjectMeta: metav1.ObjectMeta{
				Name: "aks-foo",
				Annotations: map[string]string{
					"crossplane.io/external-name": "aks-foo",
				},
			},
			Status: aksv1beta1.KubernetesClusterStatus{
				AtProvider: aksv1beta1.KubernetesClusterObservation{
					Fqdn: ptr.To("https://foo.eastus.cloudapp.azure.com"),
				},
			},
		}
		vpc := defaultVPC.DeepCopy()
		resourceID := "/subscriptions/foo/resourceGroups/foo/providers/Microsoft.Network/virtualNetworks/vnet-foo"
		vpc.Status.VPC = v1alpha1.VPCRes{
			VPCID:      "vnet-foo",
			Name:       "vnet-foo",
			ResourceID: resourceID,
		}
		vpc.Status.NodeSubnets = []v1alpha1.Subnet{{SubnetID: "node-subnet-1"}}
		vpc.Status.PodSubnets = []v1alpha1.Subnet{{SubnetID: "pod-subnet-1"}}
		kubeClient := fake.NewClientBuilder().WithScheme(s).WithObjects(approute, appbox, aks, vpc).WithStatusSubresource(approute, appbox, aks, vpc).Build()
		c, err := (&task.Context{
			Config: config.RootConfig{
				ControllerConfig: config.ControllerConfig{
					Provider: "aks",
					RegionConfig: config.RegionConfig{
						Name:  "eastus",
						Zones: []string{"eastus-1", "eastus-2", "eastus-3"},
					},
				},
			},
			Scheme:     s,
			Local:      kubeClient,
			Logger:     logger,
			APPBox:     appbox,
			APPRoute:   approute,
			BeginAt:    util.Pointer(time.Now()),
			ClusterCli: clusterCli,
		}).Default()
		require.NoError(t, err)
		task := common.NewCluster(c)
		{
			kubeClient.Create(context.TODO(), &corev1.Secret{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "secret-foo",
					Namespace: "default",
				},
				Data: map[string][]byte{
					"kubeconfig": []byte("kubeconfig-data"),
				},
			})
			clusterCli.EXPECT().CreateCluster(
				gomock.Any(),
				gomock.Cond(func(arg any) bool {
					var checkk8sInfo = func(k8sInfo *clusterctlpb.Kubernetes) bool {
						if k8sInfo.Name != "aks-foo" {
							t.Log("k8sInfo.Name is not correct")
							return false
						}
						if k8sInfo.AccountId != "subscription-id-1" {
							t.Log("k8sInfo.AccountId is not correct")
							return false
						}
						if k8sInfo.Vpc != resourceID {
							t.Log("k8sInfo.Vpc is not correct")
							return false
						}
						if k8sInfo.Version != "1.30" {
							t.Log("k8sInfo.Version is not correct")
							return false
						}
						if k8sInfo.Endpoint != "https://foo.eastus.cloudapp.azure.com" {
							t.Log("k8sInfo.Endpoint is not correct")
							return false
						}
						if k8sInfo.Kubeconfig != base64.StdEncoding.EncodeToString([]byte("kubeconfig-data")) {
							t.Log("k8sInfo.Kubeconfig is not correct")
							return false
						}
						return true
					}
					var checkAzure = func(azureParam *clusterctlpb.Kubernetes_Azure) bool {
						if !reflect.DeepEqual(azureParam.NodeZones, []string{"eastus-1", "eastus-2", "eastus-3"}) {
							t.Log("azureParam.NodeZones is not correct")
							return false
						}
						if !reflect.DeepEqual(azureParam.NodeSubnets, []*clusterctlpb.Kubernetes_AzureSubnet{{Id: "node-subnet-1"}}) {
							t.Log("azureParam.NodeSubnets is not correct")
							return false
						}
						if !reflect.DeepEqual(azureParam.PodSubnets, []*clusterctlpb.Kubernetes_AzureSubnet{{Id: "pod-subnet-1"}}) {
							t.Log("azureParam.PodSubnets is not correct")
							return false
						}
						return true
					}
					req, ok := arg.(*clusterctlpb.CreateClusterRequest)
					if !ok {
						return false
					}
					k8sInfo := req.Cluster.Kubernetes
					if k8sInfo == nil {
						t.Log("kubernetes is nil")
						return false
					}
					azureParam := k8sInfo.GetAzure()
					if azureParam == nil {
						t.Log("azure parameter is nil")
						return false
					}
					if !checkk8sInfo(k8sInfo) {
						t.Logf("kubernetes is not correct; got %#v", k8sInfo)
						return false
					}
					if !checkAzure(azureParam) {
						t.Logf("azure parameter is not correct; got %#v", azureParam)
						return false
					}
					return true
				})).Return(new(clusterctlpb.Cluster), nil).MinTimes(1)
			d := deploy.NewDeployer([]deploy.Task{task}, nil, logger)
			results := d.Deploy(context.TODO())
			require.Contains(t, task.APPRoute.GetFinalizers(), v1alpha1.Finalizer)
			require.NoError(t, results[0].Error())
			res, _, _ := deploy.MinRequeue(results)
			require.Equal(t, res.RequeueAfter, time.Duration(0))
			// ↓ update approute cr
			approute.Status.Phase = v1alpha1.APPRoutePhaseActive
			require.NoError(t, kubeClient.Status().Update(context.TODO(), approute))
			require.NoError(t, kubeClient.Update(context.TODO(), approute))
			// ↑ update approute cr
			ctrl.Satisfied()
		}
	}
}
