package eks

import (
	"context"
	"fmt"
	"time"

	"github.com/aws/aws-sdk-go-v2/service/ssm"
	autoscalingv1beta1 "github.com/crossplane-contrib/provider-aws/apis/autoscaling/v1beta1"
	"github.com/crossplane-contrib/provider-aws/apis/eks/manualv1alpha1"
	"github.com/go-logr/logr"
	"github.com/hashicorp/go-multierror"
	opv1alpha1 "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/tools/record"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

	. "github.com/tidbcloud/eks-provider/controllers/upgradeoperation/common"
	"github.com/tidbcloud/eks-provider/controllers/utils/ticdcservicecli"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/cloud/aws"
	"github.com/tidbcloud/infra-provider/pkg/config"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/dataplane"
	"github.com/tidbcloud/infra-provider/pkg/extratidb"
	"github.com/tidbcloud/infra-provider/pkg/gitops"
	"github.com/tidbcloud/infra-provider/pkg/namer"
)

type upgrader struct {
	cfg           *config.RootConfig
	infraCli      client.Client
	provider      gitops.Provider
	extraTiDB     extratidb.Handler
	ssmClient     *ssm.Client
	eventRecorder record.EventRecorder
	cdcUpgrader   *CDCUpgrader
}

var _ Upgrader = &upgrader{}

func (u *upgrader) trackProgress(ctx context.Context, uo *cloudv1.UpgradeOperation, phase UpgradePhase, done, total int) error {
	return TrackProgressAndUpdate(ctx, u.infraCli, uo, phase, done, total, time.Now())
}

func NewUpgrader(cfg *config.RootConfig, infraCli client.Client, awsCli aws.Client, eventRecorder record.EventRecorder, ticdcCli ticdcservicecli.Client) Upgrader {
	return &upgrader{
		infraCli:      infraCli,
		cfg:           cfg,
		provider:      cfg,
		extraTiDB:     extratidb.NewAWSHandler(infraCli, awsCli),
		ssmClient:     awsCli.SSM(nil),
		eventRecorder: eventRecorder,
		cdcUpgrader:   NewCDCUpgrader(infraCli, ticdcCli),
	}
}

type upgradeInfo struct {
	desiredK8sVersion  string
	currentNodeVersion string

	logger            *logr.Logger
	envProvider       dataplane.EnvProvider
	cluster           *cloudv1.Cluster
	scaleOutExtraTiDB bool
}

func (u *upgrader) UpgradeTiDBNodes(ctx context.Context, uo *cloudv1.UpgradeOperation, envProvider dataplane.EnvProvider, cluster *cloudv1.Cluster) (ctrl.Result, error) {
	logger := log.FromContext(ctx).WithValues("cluster", cluster.Name)
	logger.Info("UpgradeTiDBNodes")

	// Get current node version for TiDB nodes.
	currentNodeVersion, err := GetTiDBNodeVersion(cluster, consts.ProviderTypeEKS)
	if err != nil {
		logger.Error(err, "unable to get TiDB node versions from the cluster CR")
		return ctrl.Result{}, err
	}

	// Get the EKS client.
	eksCli := envProvider.GetClient()

	// Set up upgrade information.
	desiredCPVersion := uo.Status.CurrentStep.Version
	info := &upgradeInfo{
		desiredK8sVersion:  desiredCPVersion,
		currentNodeVersion: currentNodeVersion,
		cluster:            cluster,
		envProvider:        envProvider,
		logger:             &logger,
		scaleOutExtraTiDB:  uo.GetCurrentStepOption(ScaleOutExtraTiDBAnnotation) != "false",
	}

	oldSelfManagedNGs, newSelfManagedNGs, asgExtraGroupMap, err := getTiDBClusterSelfManagedNodeGroups(ctx, u.infraCli, info, cluster)
	if err != nil {
		return ctrl.Result{}, err
	}
	logger.Info("tidb node groups", "old self-managed", GetCRNameList(oldSelfManagedNGs), "new self-managed", GetCRNameList(newSelfManagedNGs))

	if allTiDBNodesUpgraded(info) {
		// Delete the extra tidb's launch template and node group.
		if err := u.extraTiDB.DeleteNodes(ctx, eksCli, cluster); err != nil {
			logger.Error(err, "unable to delete the extra tidb's launch template and nodegroup")
			return ctrl.Result{}, err
		}

		if err := u.deleteOldVersionTiDBNodes(ctx, oldSelfManagedNGs); err != nil {
			logger.Error(err, "failed to delete the old version nodes")
			return ctrl.Result{}, err
		}
		LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "Delete the old version nodes", nil)

		logger.Info("the tidb node groups are already upgraded")
		return ctrl.Result{}, FinishUpgrading(ctx, u.infraCli, uo)
	}

	logger.Info("start to upgrade the tidb node groups", "scaleOutExtraTiDB", info.scaleOutExtraTiDB)

	if cluster.Status.Phase == cloudv1.ClusterPhaseSuspend {
		logger.Info("the cluster is in suspend phase")
		return u.handleSuspendCluster(ctx, info)
	}

	// Phase 1: create new version node groups for all TCs (default + extra groups).
	LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "Begin to create new version node groups for all TCs (default + extra groups)", nil)
	// Create new version node groups
	numOldNGs, numNewNGs := len(oldSelfManagedNGs), len(newSelfManagedNGs)
	if numNewNGs < numOldNGs {
		// Create a managed node group for the extra tidb.
		// `PrepareNodes` need to decide which zone to create the extra tidb node based on the current tidb nodes.
		// So we need to create the extra tidb node before creating the new version node groups.
		if info.scaleOutExtraTiDB && numNewNGs == 0 {
			extraTiDBNodegroupName, err := u.extraTiDB.PrepareNodesV2(ctx, eksCli, u.provider, envProvider, cluster)
			if err != nil {
				logger.Error(err, "failed to prepare nodes for the extra tidb")
				return ctrl.Result{}, err
			}
			LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, fmt.Sprintf("Created the extra tidb node group %s", extraTiDBNodegroupName), nil)

			// Check if the extra tidb node group is ready.
			extraTiDBNodeReady, err := u.extraTiDB.AreAllNodesReady(ctx, eksCli, cluster)
			if err != nil {
				logger.Error(err, "failed to check if the extra tidb nodegroup is ready")
				return ctrl.Result{}, err
			}

			if extraTiDBNodeReady {
				// Update tc to scale out an extra tidb.
				var tc opv1alpha1.TidbCluster
				if err := eksCli.Get(ctx, types.NamespacedName{Name: consts.ClusterPrefix, Namespace: cluster.PrefixedId()}, &tc); err != nil {
					logger.Error(err, "unable to get the tidbcluster CR")
					return ctrl.Result{}, err
				}
				tc.Spec.TiDB.Replicas = int32(cluster.Spec.Cluster.TiDB.Replicas) + 1
				if err := eksCli.Update(ctx, &tc); err != nil {
					logger.Error(err, "unable to update the tidbcluster CR")
					return ctrl.Result{}, err
				}
				LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "Scale out an extra tidb", nil)
				_ = u.trackProgress(ctx, uo, PhaseExtraTiDBScaleOut, 1, 1)
			} else {
				logger.Info("the extra tidb node group is not ready yet")
				_ = u.trackProgress(ctx, uo, PhaseExtraTiDBScaleOut, 0, 1)
				return ctrl.Result{Requeue: true}, nil
			}
		}

		// If there is no errors, need requeue to get new version node groups in the next reconcile.
		// Otherwise, `Requeue` will be ignored.
		e := u.createNewVersionTiDBNodes(ctx, info, oldSelfManagedNGs, asgExtraGroupMap)
		_ = u.trackProgress(ctx, uo, PhaseCreateNodePools, numNewNGs, numOldNGs)
		return ctrl.Result{Requeue: e == nil}, e
	}

	ngReady, ngTotal := countReadyNodeGroups(newSelfManagedNGs, info.desiredK8sVersion)
	_ = u.trackProgress(ctx, uo, PhaseWaitNodesReady, ngReady, ngTotal)
	if ngReady < ngTotal {
		logger.Info("new version node groups are not ready")
		return ctrl.Result{RequeueAfter: 1 * time.Minute}, nil
	}
	LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "All new version nodes are ready", nil)

	// Phase 2: cordon all the old node groups' nodes, avoid pod being scheduled to old nodes.
	LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "Begin to cordon the old node groups' nodes", nil)
	if err := cordonTiDBNodesByNodeGroups(ctx, eksCli, oldSelfManagedNGs); err != nil {
		logger.Error(err, "unable to cordon the old node groups' nodes")
		return ctrl.Result{}, err
	}
	LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "Cordon the old node groups' nodes successfully", nil)

	// Phase 3: get all TCs (default + extra groups).
	LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "Begin to get all TCs (default + extra groups)", nil)
	var (
		allTCs    []*opv1alpha1.TidbCluster
		defaultTC opv1alpha1.TidbCluster
	)
	if err = eksCli.Get(ctx, types.NamespacedName{Name: consts.ClusterPrefix, Namespace: cluster.PrefixedId()}, &defaultTC); err != nil {
		logger.Error(err, "unable to get the tidbcluster CR")
		return ctrl.Result{}, err
	}
	allTCs = append(allTCs, &defaultTC)

	for _, extraGroup := range cluster.GetExtraGroups() {
		if extraGroup.Deleted() {
			logger.Info("skip zero replicas extra group", "groupName", extraGroup.Name)
			continue
		}
		extraGroupNamer := namer.NewNamer().
			ForTiDBGroup(cluster, extraGroup.Spec, "").
			WithComponent(string(cloudv1.ComponentTiDB))
		tcName := extraGroupNamer.GetTiDBName()
		extraTC := &opv1alpha1.TidbCluster{}
		if err := eksCli.Get(ctx, types.NamespacedName{Namespace: cluster.PrefixedId(), Name: tcName}, extraTC); err != nil {
			logger.Error(err, "unable to get the extra tidbcluster CR", "groupName", tcName)
			return ctrl.Result{}, err
		}
		allTCs = append(allTCs, extraTC)
	}

	// Phase 4-6: restart components in order defined by DefaultRestartPhases.
	// Each phase: mutate restart annotations → wait for pod migration to new nodes → wait for ready.
	for _, ph := range DefaultRestartPhases {
		res, done, phErr := u.restartComponentsAndWait(ctx, eksCli, uo, info, allTCs, newSelfManagedNGs, ph)
		if !done {
			return res, phErr
		}
	}

	// Phase 7: wait until all pods have migrated to the new node groups and all TCs are ready.
	LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "Begin to wait until ALL components are restarted + ready and pods migrated to new nodes", nil)
	var extraTiDBNodegroupName string
	if info.scaleOutExtraTiDB {
		_extraTiDBNodegroupName, err := u.extraTiDB.PrepareNodesV2(ctx, eksCli, u.provider, envProvider, cluster)
		if err != nil {
			logger.Error(err, "failed to prepare nodes for the extra tidb")
			return ctrl.Result{}, err
		}
		extraTiDBNodegroupName = _extraTiDBNodegroupName
	}
	onNew, podTotal, ngNameToPod, err := countTiDBPodsOnNewNodeGroups(ctx, eksCli, cluster.PrefixedId(), newSelfManagedNGs, extraTiDBNodegroupName)
	if err != nil {
		logger.Error(err, "unable to check if all tidb component pods are running on the new node groups' nodes")
		return ctrl.Result{}, err
	}
	_ = u.trackProgress(ctx, uo, PhaseWaitAllPodsMigrated, onNew, podTotal)
	logger.Info("current nodegroup and pod topology", "onNew", onNew, "podTotal", podTotal, "ngNameToPod", ngNameToPod)
	if onNew < podTotal || podTotal == 0 {
		LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "Not all tidb component pods are running on the new version nodes", nil)
		return ctrl.Result{RequeueAfter: 2 * time.Minute}, nil
	}
	// Check readiness of all TCs (default + extra groups) before proceeding to scale-in.
	tcReady, tcTotal := CountTCsReady(allTCs, &defaultTC)
	_ = u.trackProgress(ctx, uo, PhaseWaitTCReady, tcReady, tcTotal)
	if tcReady < tcTotal || tcTotal == 0 {
		LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "Not all tidb clusters are ready", nil)
		return ctrl.Result{RequeueAfter: 2 * time.Minute}, nil
	}
	LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "All tidb component pods are running on the new node groups' nodes and tidb cluster is ready", nil)

	// Phase 8: scale in the extra tidb pod that is on the extra tidb node
	LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "Begin to scale in the extra tidb pod that is on the extra tidb node", nil)
	if info.scaleOutExtraTiDB {
		if extraTiDBNodegroupName == "" {
			return ctrl.Result{}, fmt.Errorf("extra tidb node group name is empty")
		}
		extraTiDBPod, ok := ngNameToPod[extraTiDBNodegroupName]
		if !ok || len(extraTiDBPod) == 0 {
			return ctrl.Result{}, fmt.Errorf("no pod is running on the extra tidb node group")
		}
		if err := extratidb.MutateTCToScaleInExtraTiDBPod(&defaultTC, extraTiDBPod[0], int32(info.cluster.Spec.Cluster.TiDB.Replicas)); err != nil {
			logger.Error(err, "unable to update the tidbcluster CR to scale in the extra tidb pod")
			return ctrl.Result{}, err
		}
		if err := eksCli.Update(ctx, &defaultTC); err != nil {
			logger.Error(err, "unable to update the tidbcluster CR")
			return ctrl.Result{}, err
		}
		LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, fmt.Sprintf("Scale in the extra tidb pod: %s", extraTiDBPod[0]), nil)
	}

	if err = UpdateClusterStatus(ctx, u.infraCli, cluster, desiredCPVersion); err != nil {
		logger.Error(err, "unable to update cluster status")
		return ctrl.Result{}, err
	}
	LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "Updated the node version in cluster CR's status", nil)
	return ctrl.Result{RequeueAfter: time.Second}, nil
}

// UpgradeCDCNodes delegates to the cloud-agnostic CDCUpgrader. See common.CDCUpgrader.Upgrade
// for the implementation details.
func (u *upgrader) UpgradeCDCNodes(ctx context.Context, uo *cloudv1.UpgradeOperation, envProvider dataplane.EnvProvider, cluster *cloudv1.Cluster) (ctrl.Result, error) {
	return u.cdcUpgrader.Upgrade(ctx, uo, envProvider, cluster)
}

func (u *upgrader) UpgradeDMNodes(ctx context.Context, uo *cloudv1.UpgradeOperation, _ dataplane.EnvProvider, cluster *cloudv1.Cluster) (ctrl.Result, error) {
	logger := log.FromContext(ctx)
	logger.Info("UpgradeDMNodes")

	var list manualv1alpha1.NodeGroupList
	if err := u.infraCli.List(ctx, &list, client.MatchingLabelsSelector{
		Selector: labels.Set{
			"cluster":   cluster.Name,
			"component": "dm",
		}.AsSelector(),
	}); err != nil && !apierrors.IsNotFound(err) {
		logger.Error(err, "unable to list the dm nodegroups")
		return ctrl.Result{}, err
	}

	return u.upgradeManagedNodeGroups(ctx, uo, &list, cloudv1.DMNodes)
}

func (u *upgrader) handleSuspendCluster(ctx context.Context, info *upgradeInfo) (ctrl.Result, error) {
	// Update the cluster status to let cluster controller create the new version self-managed node groups.
	return ctrl.Result{Requeue: true}, UpdateClusterStatus(ctx, u.infraCli, info.cluster, info.desiredK8sVersion)
}

func (u *upgrader) createNewVersionTiDBNodes(ctx context.Context, info *upgradeInfo, oldSelfManagedNGs []*autoscalingv1beta1.AutoScalingGroup, asgExtraGroupMap map[string]string) error {
	if err := createNewSelfManagedNodeGroups(ctx, u.infraCli, u.ssmClient, u.provider, info, oldSelfManagedNGs, asgExtraGroupMap); err != nil {
		info.logger.Error(err, "failed to create new version self-managed node groups")
		return err
	}
	info.logger.Info("created new version self-managed node groups")
	return nil
}

func (u *upgrader) deleteOldVersionTiDBNodes(ctx context.Context, oldSelfManagedNGs []*autoscalingv1beta1.AutoScalingGroup) error {
	var errs *multierror.Error
	for _, ng := range oldSelfManagedNGs {
		errs = multierror.Append(errs, deleteNodeGroupAndLaunchTemplate(ctx, u.infraCli, ng, ng.Name))
	}
	return errs.ErrorOrNil()
}

// restartComponentsAndWait handles one restart phase for all TCs:
//  1. Mutate the restart annotation for ph.Components on every TC; if any TC changed, persist and requeue.
//  2. For non-TiDB components: verify all pods have migrated to new node groups.
//     (TiDB pod migration is verified by areAllTiDBPodsOnNewNodeGroups in the tail, which also
//     accounts for the temporary extra-TiDB node created during upgrade.)
//  3. Verify every TC reports the component as restarted and ready.
//
// Returns (result, done=false, err) when the caller should return immediately, or (_, done=true, nil)
// when the phase is complete and the caller may proceed to the next phase.
func (u *upgrader) restartComponentsAndWait(
	ctx context.Context,
	eksCli client.Client,
	uo *cloudv1.UpgradeOperation,
	info *upgradeInfo,
	allTCs []*opv1alpha1.TidbCluster,
	newNGs []*autoscalingv1beta1.AutoScalingGroup,
	ph RestartPhase,
) (result ctrl.Result, done bool, err error) {
	logger := log.FromContext(ctx)

	LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent,
		fmt.Sprintf("Begin restart phase %q for ALL TCs", ph.Name), nil)

	// Step 1: write restart annotations. Update only TCs that actually changed.
	updatedAny := false
	for _, tc := range allTCs {
		if MutateTCForRestartingComponents(tc, info.desiredK8sVersion, ph.Components...) {
			updatedAny = true
			logger.Info("updated tidbcluster CR for restart phase", "tc", tc.Name, "phase", ph.Name)
			if err = eksCli.Update(ctx, tc); err != nil {
				logger.Error(err, "unable to update tidbcluster CR", "tc", tc.Name)
				return ctrl.Result{}, false, err
			}
		}
	}
	if updatedAny {
		_ = u.trackProgress(ctx, uo, RestartPhaseID(ph), len(allTCs), len(allTCs))
		return ctrl.Result{RequeueAfter: 1 * time.Minute}, false, uo.UpdateReason(ctx, u.infraCli, ph.Reason)
	}

	// Step 2: verify pod migration to new node groups (skipped for TiDB — handled in the aggregate tail check).
	migrated, migrateTotal, migratable, migrateErr := CountRestartPhaseMigration(logger, info.cluster.PrefixedId(), allTCs, ph,
		func(namespace, component string, tc *opv1alpha1.TidbCluster) (int, int, error) {
			return countComponentPodsOnNewNodeGroups(ctx, eksCli, namespace, component, newNGs, tc)
		})
	if migrateErr != nil {
		logger.Error(migrateErr, "unable to check pod migration", "phase", ph.Name)
		return ctrl.Result{}, false, migrateErr
	}
	_ = u.trackProgress(ctx, uo, RestartMigratePhaseID(ph), migrated, migrateTotal)
	if migratable && (migrateTotal == 0 || migrated < migrateTotal) {
		LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent,
			fmt.Sprintf("Waiting for %s pods to be on new version nodes for all TCs", ph.Name), nil)
		return ctrl.Result{RequeueAfter: 1 * time.Minute}, false, nil
	}

	// Step 3: verify restart completed and component is ready.
	readyCount, readyTotal := CountRestartPhaseReady(logger, allTCs, info.desiredK8sVersion, ph)
	_ = u.trackProgress(ctx, uo, RestartReadyPhaseID(ph), readyCount, readyTotal)
	if readyCount < readyTotal {
		LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent,
			fmt.Sprintf("Waiting for %s restart to complete for all TCs", ph.Name), nil)
		return ctrl.Result{RequeueAfter: 1 * time.Minute}, false, nil
	}

	LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent,
		fmt.Sprintf("Restart phase %q complete for all TCs", ph.Name), nil)
	return ctrl.Result{}, true, nil
}

func (u *upgrader) upgradeManagedNodeGroups(ctx context.Context, uo *cloudv1.UpgradeOperation, ngs *manualv1alpha1.NodeGroupList, nodeType cloudv1.UpgradeOperationTarget) (ctrl.Result, error) {
	logger := log.FromContext(ctx)
	logger.Info("upgradeManagedNodeGroups", "nodeType", nodeType)

	if len(ngs.Items) == 0 {
		logger.Info("no node groups", "nodeType", nodeType)
		return ctrl.Result{}, FinishUpgrading(ctx, u.infraCli, uo)
	}

	desiredVersion := uo.Status.CurrentStep.Version
	done := 0
	for i := range ngs.Items {
		ng := &ngs.Items[i]
		logger.Info("node group", "nodeType", nodeType, "name", ng.Name, "spec version", ng.Spec.ForProvider.Version, "status version", ng.Status.AtProvider.Version, "status", ng.Status.AtProvider.Status)

		switch ng.Status.AtProvider.Status {
		case manualv1alpha1.NodeGroupStatusUpdating:
			LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, "The node group is updating, wait for next reconcile", nil)

			// The version may be modified back by other controllers, so update it again.
			if err := updateNodeGroupVersion(ctx, u.infraCli, ng, desiredVersion); err != nil {
				logger.Error(err, "unable to upgrade the node group CR")
				return ctrl.Result{}, err
			}

		case manualv1alpha1.NodeGroupStatusActive:
			if ng.Status.AtProvider.Version == desiredVersion {
				if IsCrossplaneCRReadyAndSynced(ng.Status.Conditions) {
					done++
					LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, fmt.Sprintf("The node group %s is already upgraded", ng.Name), nil)
				} else {
					if err := updateNodeGroupVersion(ctx, u.infraCli, ng, desiredVersion); err != nil {
						logger.Error(err, "unable to upgrade the node group CR")
						return ctrl.Result{}, err
					}
				}
			} else {
				logger.Info("start to upgrade the node group")

				if err := updateNodeGroupVersion(ctx, u.infraCli, ng, desiredVersion); err != nil {
					logger.Error(err, "unable to upgrade the node group CR")
					return ctrl.Result{}, err
				}
			}
		default:
			logger.Info("the node group is in unexpected status", "status", ng.Status.AtProvider.Status)
		}
	}
	_ = u.trackProgress(ctx, uo, PhaseUpgradeNodePools, done, len(ngs.Items))

	if done == len(ngs.Items) {
		LogAndEmitEvent(logger, u.eventRecorder, uo, NormalEvent, fmt.Sprintf("All node groups of %s are upgraded", nodeType), nil)
		return ctrl.Result{}, FinishUpgrading(ctx, u.infraCli, uo)
	}

	return ctrl.Result{Requeue: true}, nil
}
