package configchange

import (
	"context"
	"fmt"

	opv2 "github.com/pingcap/tidb-operator/api/v2/core/v1alpha1"
	opv1alpha1 "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/samber/lo"
	v1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/equality"
	"k8s.io/apimachinery/pkg/types"
	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/eks-provider/utils/tidb-operator/tidbcluster"
	"github.com/tidbcloud/eks-provider/utils/tidb-operator/v2utils"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	reconcilerr "github.com/tidbcloud/infra-provider/pkg/controllers/common/error"
	"github.com/tidbcloud/infra-provider/pkg/k8s"
	"github.com/tidbcloud/infra-provider/pkg/namer"
)

const (
	// RestartByConfigChangeAnnotation is an annotation key used for restarting tidb components.
	// in there, to avoid conflict with other service, such as cluster-controller, we use another key to instead of 'tidb.pingcap.com/restartedAt' annotated at
	// https://docs.pingcap.com/tidb-in-kubernetes/stable/restart-a-tidb-cluster#perform-a-graceful-rolling-restart-to-all-pods-in-a-component.
	RestartByConfigChangeAnnotation = "tidb.pingcap.com/restarted-by-configchange"
)

type rollingRestartApplier struct {
}

func newRollingRestartApplier() *rollingRestartApplier {
	return &rollingRestartApplier{}
}

func newRestartComponentStep(comp cloudv1.ComponentType) cloudv1.ConfigChangeStepName {
	return cloudv1.ConfigChangeStepName(fmt.Sprintf("RestartComponent/%s", comp))
}

func (r *rollingRestartApplier) InitSteps(ctx context.Context, cc *cloudv1.ConfigChange) {
	utilruntime.Must(cc.AddStep(ctx, cloudv1.CCStepUpdateCR2Cluster, cloudv1.CCStepStatusPending, ""))
	utilruntime.Must(cc.AddStep(ctx, cloudv1.CCStepUpdateConfig2CM, cloudv1.CCStepStatusPending, ""))
	for _, comp := range r.filterComponentTypes(cc) {
		if isCompAlwaysHotReload(comp) {
			utilruntime.Must(cc.AddStep(ctx, newHotReloadComponentStep(comp), cloudv1.CCStepStatusPending, ""))
			continue
		}
		utilruntime.Must(cc.AddStep(ctx, newRestartComponentStep(comp), cloudv1.CCStepStatusPending, ""))
	}
	utilruntime.Must(cc.AddStep(ctx, cloudv1.CCStepEnsureTCReady, cloudv1.CCStepStatusPending, ""))
}

func (r *rollingRestartApplier) RegisterStepAction(re *ConfigChangeReconciler) {
	for _, comp := range rollingRestartComponents {
		comp := comp
		re.RegisterAction(newRestartComponentStep(comp), func(ctx context.Context, rx *ReconcileContext) error {
			return r.rollingRestart(ctx, rx, []cloudv1.ComponentType{comp})
		})
	}
}

func (r *rollingRestartApplier) Apply(ctx context.Context, rx *ReconcileContext) (err error) {
	if err := r.rollingRestart(ctx, rx, r.filterComponentTypes(rx.ConfigChange)); err != nil {
		return fmt.Errorf("rolling restart: %w", err)
	}
	return nil
}

func (r *rollingRestartApplier) filterComponentTypes(cc *cloudv1.ConfigChange) []cloudv1.ComponentType {
	// should place `rollingRestartComponents` as the secondary parameter to ensure the order of result is sorted same as it.
	return lo.Intersect(cc.Status.ChangedComponents, rollingRestartComponents)
}

func (r *rollingRestartApplier) rollingRestart(ctx context.Context, rx *ReconcileContext, componentTypes []cloudv1.ComponentType) error {
	rx.Logger.Info("try to rolling update", "changedComponentTypes", componentTypes)
	cc := rx.ConfigChange
	remoteCli := rx.RemoteClient

	if rx.Cluster.FeatureEnabled(consts.ClusterFeatureUseTiDBOperatorV2) {
		return r.rollingRestartV2(ctx, rx, componentTypes)
	}

	tc := &opv1alpha1.TidbCluster{}
	if err := remoteCli.Get(ctx, types.NamespacedName{Namespace: rx.Cluster.PrefixedId(), Name: tcName}, tc); err != nil {
		return fmt.Errorf("get tc: %w", err)
	}

	for _, comp := range componentTypes {
		var done bool
		var err error
		switch comp {
		case cloudv1.ComponentTiDB:
			done, err = r.rollingRestartTiDB(ctx, rx, remoteCli, tc)
		default:
			done, err = r.rollingRestartOneComponent(ctx, rx, remoteCli, tc, comp, true)
		}
		if err != nil {
			return fmt.Errorf("rolling restart one component: %w", err)
		} else if !done {
			if err := rx.LocalClient.Status().Update(ctx, cc); err != nil {
				return fmt.Errorf("update configChange status: %w", err)
			}
			return reconcilerr.New(RequeueAfterWhenCCNotDone, nil)
		}
	}
	return nil
}

func (r *rollingRestartApplier) rollingRestartV2(ctx context.Context, rx *ReconcileContext, componentTypes []cloudv1.ComponentType) error {
	rx.Logger.Info("try to rolling update for tidb operator v2", "changedComponentTypes", componentTypes)
	tc, err := newTCGetter(ctx, rx.Cluster, rx.RemoteClient)
	if err != nil {
		return fmt.Errorf("get tc: %w", err)
	}

	for _, comp := range componentTypes {
		var (
			done bool
		)
		switch comp {
		case cloudv1.ComponentTiDB:
			// Because we may have multiple TiDB groups, need to handle them separately.
			v2getter, ok := tc.(*tidbcluster.V2Getter)
			if !ok {
				return fmt.Errorf("tc is not a v2 getter")
			}
			var tidbGroups []*opv2.TiDBGroup
			tidbGroups = append(tidbGroups, v2getter.TiDBGroup)
			tidbGroups = append(tidbGroups, v2getter.ExtraGroups...)
			done, err = r.rollingRestartTiDBGroups(ctx, rx, tidbGroups)
		default:
			done, err = r.rollingRestartOneComponentGroup(ctx, rx, comp, tc, true)
		}
		if err != nil {
			return fmt.Errorf("rolling restart component group: %w", err)
		}
		if !done {
			if err := rx.LocalClient.Status().Update(ctx, rx.ConfigChange); err != nil {
				return fmt.Errorf("update configChange status: %w", err)
			}
			return reconcilerr.New(RequeueAfterWhenCCNotDone, nil)
		}
	}
	return nil
}

// rollingRestartTiDB is the rolling restarter for TiDB component. The TiDB component may be composed of multiple TCs, such as extra group.
func (r *rollingRestartApplier) rollingRestartTiDB(ctx context.Context, rx *ReconcileContext, remoteCli client.Client, defaultTC *opv1alpha1.TidbCluster) (done bool, _ error) {
	cc := rx.ConfigChange
	stepName := newRestartComponentStep(cloudv1.ComponentTiDB)
	doneInAll := true
	extraGroupName2Done := map[string]bool{}

	name2extraGroupTC, err := getExtraTCsOfCluster(ctx, rx)
	if err != nil {
		return false, err
	}
	for name, extraGroupTC := range name2extraGroupTC {
		// don't update status/step when rolling restarting extra group.
		doneInOne, err := r.rollingRestartOneComponent(ctx, rx, remoteCli, extraGroupTC, cloudv1.ComponentTiDB, false)
		if err != nil {
			return false, fmt.Errorf("rolling restart one extragroup tidb: %w", err)
		}
		extraGroupName2Done[name] = doneInOne
		if !doneInOne {
			// continue to rolling restart other extraGroups even if this one is not done.
			doneInAll = false
		}
	}
	if !doneInAll {
		msg := fmt.Sprintf("some extraGroups don't finished, %v", extraGroupName2Done)
		cc.UpdateStep(ctx, stepName, cloudv1.CCStepStatusRunning, msg)
		return false, nil
	}

	// NOTE: currently, we don't expose the detailed progress of extra groups, but use the tidb of default TC's progress
	// as the tidb component's progress.
	return r.rollingRestartOneComponent(ctx, rx, remoteCli, defaultTC, cloudv1.ComponentTiDB, true)
}

func (r *rollingRestartApplier) rollingRestartTiDBGroups(ctx context.Context, rx *ReconcileContext, tidbGroups []*opv2.TiDBGroup) (done bool, _ error) {
	logger := rx.Logger.WithValues("component", cloudv1.ComponentTiDB)
	cc := rx.ConfigChange
	stepName := newRestartComponentStep(cloudv1.ComponentTiDB)
	annoValue := cc.Name
	doneInAll := true
	tidbGroupName2Done := map[string]bool{}

	for _, dbg := range tidbGroups {
		if dbg == nil || dbg.Spec.Replicas == nil || *dbg.Spec.Replicas == 0 {
			continue
		}

		manuallyRestartRequired := true
		if rx.LastConfigRevision.IsComponentImageChanged(rx.TargetConfigRevision, cloudv1.ComponentTiDB) {
			rx.Logger.Info("component image changed, skip manually restart component")
			manuallyRestartRequired = false
		} else if dbg.Spec.Template.Annotations[RestartByConfigChangeAnnotation] == annoValue {
			rx.Logger.Info("the restart annotation is already set")
			manuallyRestartRequired = false
		}

		if manuallyRestartRequired {
			if dbg.Spec.Template.Annotations == nil {
				dbg.Spec.Template.Annotations = map[string]string{}
			}
			dbg.Spec.Template.Annotations[RestartByConfigChangeAnnotation] = annoValue
			logger.Info("annotate group to restart the component")
			cc.UpdateStep(ctx, stepName, cloudv1.CCStepStatusRunning, fmt.Sprintf("tidb group %s is annotated to restart", dbg.Name))
			if err := rx.RemoteClient.Update(ctx, dbg); err != nil {
				logger.Error(err, "unable to update the group CR")
				return false, fmt.Errorf("update group CR: %w", err)
			}
			return false, nil
		}

		readyAndSynced := v2utils.ReadyAndSynced(dbg.Status.Conditions) && v2utils.IsUpToDate(dbg.Generation, dbg.Status.CommonStatus)
		isUptoDate := dbg.Annotations[cloudv1.ConfigRevisionIdKey] == rx.TargetConfigRevision.Name
		doneInOne := readyAndSynced && isUptoDate
		tidbGroupName2Done[dbg.Name] = doneInOne
		logger.Info("debug info", "replicas", *dbg.Spec.Replicas, "readyAndSynced", readyAndSynced, "upToDate", isUptoDate)
		if !doneInOne {
			// continue to rolling restart other extraGroups even if this one is not done.
			doneInAll = false
		}
	}

	if !doneInAll {
		msg := fmt.Sprintf("some tidbGroups don't finished, %v", tidbGroupName2Done)
		cc.UpdateStep(ctx, stepName, cloudv1.CCStepStatusRunning, msg)
		return false, nil
	}

	compStatus := cc.ReadOnlyComponentStatus(cloudv1.ComponentTiDB)
	compStatus.Done = true
	msg := fmt.Sprintf("all tidbgroups finished: %v", tidbGroupName2Done)
	if err := cc.UpdateComponentStatus(ctx, cloudv1.ComponentTiDB, &compStatus); err != nil {
		return false, fmt.Errorf("update component status: %w", err)
	}
	cc.UpdateStep(ctx, stepName, cloudv1.CCStepStatusCompleted, msg)
	return true, nil
}

// rollingRestartOneComponent is the default component rolling restarter
func (r *rollingRestartApplier) rollingRestartOneComponent(
	ctx context.Context,
	rx *ReconcileContext,
	remoteCli client.Client,
	tc *opv1alpha1.TidbCluster,
	comp cloudv1.ComponentType,
	updateStatus bool) (done bool, _ error) {
	logger := rx.Logger
	cc := rx.ConfigChange
	stepName := newRestartComponentStep(comp)
	annoValue := rx.ConfigChange.Name

	compSpec, ok := getComponentSpecFromTC(tc, comp)
	if !ok {
		logger.Info("component not found in tc", "component", comp)
		return true, markComponentAndStepDoneForComponentNotDeployed(ctx, cc, comp, newRestartComponentStep(comp))
	}
	compStatusInTC := tc.ComponentStatus(componentType2MemberType(comp))
	if compStatusInTC == nil {
		logger.Info("component status not found in tc.status, but found in tc.spec", "component", comp)
		return false, nil
	}

	// If the component's image has changed, all pods of this component will be re-deployed,
	// and we don't need to restart the component manually.
	manuallyRestartRequired := true
	if rx.LastConfigRevision.IsComponentImageChanged(rx.TargetConfigRevision, comp) {
		rx.Logger.Info("component image changed, skip manually restart component", "component", comp)
		manuallyRestartRequired = false
	}

	// restart component if need //
	if manuallyRestartRequired && mutateTCForRestartingForOneComponent(ctx, compSpec, annoValue) {
		logger.Info("annotate tc to restart the component", "component", comp)

		cc.UpdateStep(ctx, stepName, cloudv1.CCStepStatusRunning, fmt.Sprintf("tc.%s is annotated to restart", comp))
		if err := k8s.Apply(ctx, remoteCli, tc); err != nil {
			logger.Error(err, "unable to update the tidbcluster CR")
			return false, fmt.Errorf("apply tc: %w", err)
		}

		return false, nil
	}

	// observe status from sts //
	sts := &v1.StatefulSet{}
	if err := getAdvanceStatefulSet(ctx, remoteCli, types.NamespacedName{Namespace: tc.Namespace, Name: memberName(tc.Name, getDeploymentComponent(comp))}, sts); err != nil {
		return false, fmt.Errorf("get sts(%s): %w", getDeploymentComponent(comp), err)
	}
	if !isSTSUpToDate(ctx, sts, cc, manuallyRestartRequired) {
		msg := fmt.Sprintf("sts is not up-to-date, wait to next time, restartAnno:(cur=%s, target=%s), observedGen:(cur=%d, target=%d)",
			sts.Spec.Template.Annotations[RestartByConfigChangeAnnotation], annoValue,
			sts.Status.ObservedGeneration, sts.Generation)
		logger.Info(msg)
		cc.UpdateStep(ctx, stepName, cloudv1.CCStepStatusRunning, msg)

		return false, nil
	}

	done = sts.Status.UpdatedReplicas == ptr.Deref(sts.Spec.Replicas, 0) &&
		compStatusInTC.GetPhase() == opv1alpha1.NormalPhase
	compStatus := cc.ReadOnlyComponentStatus(comp)
	compStatus.UpdatedReplicas = sts.Status.UpdatedReplicas
	compStatus.Done = done
	stepStatus := cloudv1.CCStepStatusRunning
	if done {
		stepStatus = cloudv1.CCStepStatusCompleted
	}
	msg := fmt.Sprintf("updatedReplicas: %d, readyReplicas: %d, phase: %s",
		sts.Status.UpdatedReplicas, sts.Status.ReadyReplicas, compStatusInTC.GetPhase())
	if updateStatus {
		if err := cc.UpdateComponentStatus(ctx, comp, &compStatus); err != nil {
			return false, fmt.Errorf("update component status: %w", err)
		}
		cc.UpdateStep(ctx, stepName, stepStatus, msg)
	}

	return done, nil
}

// rollingRestartOneComponentGroup tries to perform a rolling restart for one component group.
func (r *rollingRestartApplier) rollingRestartOneComponentGroup(
	ctx context.Context,
	rx *ReconcileContext,
	comp cloudv1.ComponentType,
	tc tidbcluster.Getter,
	updateStatus bool,
) (done bool, _ error) {
	logger := rx.Logger.WithValues("component", comp)
	cc := rx.ConfigChange
	annoValue := cc.Name
	stepName := newRestartComponentStep(comp)
	replicas := tc.ComponentReplicas(comp)

	if replicas == 0 {
		return true, markComponentAndStepDoneForComponentNotDeployed(ctx, cc, comp, newRestartComponentStep(comp))
	}

	// If the component's image has changed, all pods of this component will be re-deployed,
	// and we don't need to restart the component manually.
	manuallyRestartRequired := true
	if rx.LastConfigRevision.IsComponentImageChanged(rx.TargetConfigRevision, comp) {
		rx.Logger.Info("component image changed, skip manually restart component")
		manuallyRestartRequired = false
	} else if tc.ComponentAnnotations(comp)[RestartByConfigChangeAnnotation] == annoValue {
		rx.Logger.Info("the restart annotation is already set")
		manuallyRestartRequired = false
	}

	// FIXME(Huaxi): add a generic interface in tidb operator v2, so that we can reduce the code duplication.
	v2getter, ok := tc.(*tidbcluster.V2Getter)
	if !ok {
		return false, fmt.Errorf("tc is not a v2 getter")
	}
	var (
		needUpdateGroup  client.Object
		curConfigRevison string
	)
	switch comp {
	case cloudv1.ComponentPD:
		pdg := v2getter.PDGroup
		curConfigRevison = pdg.Annotations[cloudv1.ConfigRevisionIdKey]
		if manuallyRestartRequired {
			if pdg.Spec.Template.Annotations == nil {
				pdg.Spec.Template.Annotations = map[string]string{}
			}
			pdg.Spec.Template.Annotations[RestartByConfigChangeAnnotation] = annoValue
			needUpdateGroup = pdg
		}
	case cloudv1.ComponentTiKV:
		kvg := v2getter.TiKVGroup
		curConfigRevison = kvg.Annotations[cloudv1.ConfigRevisionIdKey]
		if manuallyRestartRequired {
			if kvg.Spec.Template.Annotations == nil {
				kvg.Spec.Template.Annotations = map[string]string{}
			}
			kvg.Spec.Template.Annotations[RestartByConfigChangeAnnotation] = annoValue
			needUpdateGroup = kvg
		}
	case cloudv1.ComponentTiFlash, cloudv1.ComponentTiFlashLearner:
		flash := v2getter.TiFlashGroup
		curConfigRevison = flash.Annotations[cloudv1.ConfigRevisionIdKey]
		if manuallyRestartRequired {
			if flash.Spec.Template.Annotations == nil {
				flash.Spec.Template.Annotations = map[string]string{}
			}
			flash.Spec.Template.Annotations[RestartByConfigChangeAnnotation] = annoValue
			needUpdateGroup = flash
		}
	}

	if manuallyRestartRequired && needUpdateGroup != nil {
		logger.Info("annotate group to restart the component")
		cc.UpdateStep(ctx, stepName, cloudv1.CCStepStatusRunning, fmt.Sprintf("%s group %s is annotated to restart", comp, needUpdateGroup.GetName()))
		if err := rx.RemoteClient.Update(ctx, needUpdateGroup); err != nil {
			logger.Error(err, "unable to update the group CR")
			return false, fmt.Errorf("update group CR: %w", err)
		}
		return false, nil
	}

	updatedReplicas, readyReplicas, readyAndSynced := tc.ComponentUpdatedReplicas(comp), tc.ComponentReadyReplicas(comp), tc.ComponentAllInstancesReady(comp)
	isUpToDate := curConfigRevison == rx.TargetConfigRevision.Name
	done = updatedReplicas == replicas && readyAndSynced && isUpToDate
	logger.Info("debug info", "replicas", replicas, "updatedReplicas", updatedReplicas, "readyAndSynced", readyAndSynced, "upToDate", isUpToDate)
	compStatus := rx.ConfigChange.ReadOnlyComponentStatus(comp)
	compStatus.UpdatedReplicas = updatedReplicas
	compStatus.Done = done
	stepStatus := cloudv1.CCStepStatusRunning
	if done {
		stepStatus = cloudv1.CCStepStatusCompleted
	}
	msg := fmt.Sprintf("updatedReplicas: %d, readyReplicas: %d", updatedReplicas, readyReplicas)
	if updateStatus {
		if err := rx.ConfigChange.UpdateComponentStatus(ctx, comp, &compStatus); err != nil {
			return false, fmt.Errorf("update component status: %w", err)
		}
		rx.ConfigChange.UpdateStep(ctx, stepName, stepStatus, msg)
	}

	return done, nil
}

func ensureTCsReady(ctx context.Context, rx *ReconcileContext) error {
	cc := rx.ConfigChange
	remoteCli := rx.RemoteClient
	var (
		msg       string
		completed bool
	)
	if rx.Cluster.FeatureEnabled(consts.ClusterFeatureUseTiDBOperatorV2) {
		tcGetter, err := newTCGetter(ctx, rx.Cluster, remoteCli)
		if err != nil {
			return fmt.Errorf("failed to get tc: %w", err)
		}
		completed = tcGetter.IsReady()
	} else {
		name2tc, err := getExtraTCsOfCluster(ctx, rx)
		if err != nil {
			return err
		}
		defaultTC := &opv1alpha1.TidbCluster{}
		if err := remoteCli.Get(ctx, types.NamespacedName{Namespace: rx.Cluster.PrefixedId(), Name: tcName}, defaultTC); err != nil {
			return fmt.Errorf("get default tc: %w", err)
		}
		name2tc[defaultTC.Name] = defaultTC

		var nonReadyTCNames []string
		for _, tc := range name2tc {
			if !isTCReady(tc) {
				nonReadyTCNames = append(nonReadyTCNames, tc.Name)
			}
		}
		completed = len(nonReadyTCNames) == 0
		msg = fmt.Sprintf("nonReadyTCNames: %v", nonReadyTCNames)
	}

	stepStatus := cloudv1.CCStepStatusRunning
	if completed {
		stepStatus = cloudv1.CCStepStatusCompleted
	}
	cc.UpdateStep(ctx, cloudv1.CCStepEnsureTCReady, stepStatus, msg)
	return nil
}

func getExtraTCsOfCluster(ctx context.Context, rx *ReconcileContext) (name2tc map[string]*opv1alpha1.TidbCluster, _ error) {
	logger := rx.Logger
	remoteCli := rx.RemoteClient

	name2tc = make(map[string]*opv1alpha1.TidbCluster)

	extraGroups := rx.Cluster.GetExtraGroups()
	for _, extraGroup := range extraGroups {
		if extraGroup.Spec.TiDB.Replicas == 0 {
			logger.Info("filter out zero replicas extra group", "groupName", extraGroup.Name)
			continue
		}
		// todo(chenfei): check status of extra group?

		extraGroupNamer := namer.NewNamer().
			WithTiDBGroupName(extraGroup.Name).
			WithComponent(string(cloudv1.ComponentTiDB))
		extraGroupTCName := extraGroupNamer.GetTiDBName()
		extraGroupTC := &opv1alpha1.TidbCluster{}
		if err := remoteCli.Get(ctx, types.NamespacedName{Namespace: rx.Cluster.PrefixedId(), Name: extraGroupTCName}, extraGroupTC); err != nil {
			return nil, fmt.Errorf("get extragroup tc, group=%s, tc=%s: %w", extraGroup.Name, extraGroupTCName, err)
		}

		name2tc[extraGroupTC.Name] = extraGroupTC
	}

	return name2tc, nil
}

func isTCReady(tc *opv1alpha1.TidbCluster) bool {
	ready := false
	for _, cond := range tc.Status.Conditions {
		if cond.Type == opv1alpha1.TidbClusterReady {
			ready = cond.Status == corev1.ConditionTrue
		}
	}

	return ready
}

func isSTSUpToDate(ctx context.Context, sts *v1.StatefulSet, cc *cloudv1.ConfigChange, manuallyRestartRequired bool) bool {
	restartAnnotationUpToDate := ensureSTSTemplateHasAnnotation(sts, RestartByConfigChangeAnnotation, cc.Name)
	observedStatusUpToDate := ensureSTSObservedGenerationUpToDate(sts)

	if manuallyRestartRequired {
		return restartAnnotationUpToDate && observedStatusUpToDate
	} else {
		return observedStatusUpToDate
	}
}

// mutateTCForRestartingForOneComponent add annotations to component's spec to restarts the component gracefully.
// According to the doc, need to add an annotation like:
//
// `tidb.pingcap.com/restartedAt: {any valid value for annotation}`
//
// To avoid the annotation being modified multiple times, we use the target ConfigRevision.name as the value.
func mutateTCForRestartingForOneComponent(ctx context.Context, compSpec *opv1alpha1.ComponentSpec, annoValue string) (changed bool) {
	cloned := compSpec.DeepCopy()

	setRestartAnnotation(compSpec, annoValue)
	return !equality.Semantic.DeepEqual(compSpec, cloned)
}

func setRestartAnnotation(compSpec *opv1alpha1.ComponentSpec, value string) {
	if compSpec.Annotations == nil {
		compSpec.Annotations = map[string]string{}
	}
	compSpec.Annotations[RestartByConfigChangeAnnotation] = value
}

func ensureSTSTemplateHasAnnotation(sts *v1.StatefulSet, key, value string) bool {
	return sts.Spec.Template.Annotations[key] == value
}

// ensureSTSObservedGenerationUpToDate ensure the status of sts is up-to-date by check .status.observedGeneration against
// with .metadata.generation
func ensureSTSObservedGenerationUpToDate(sts *v1.StatefulSet) bool {
	return sts.Generation == sts.Status.ObservedGeneration
}

// getDeploymentComponent returns the name of component in deployment, such as Cluster, TC, STS, etc
func getDeploymentComponent(comp cloudv1.ComponentType) cloudv1.ComponentType {
	switch comp {
	case cloudv1.ComponentTiFlash, cloudv1.ComponentTiFlashLearner:
		return cloudv1.ComponentTiFlash
	default:
		return comp
	}
}

// isCompAlwaysHotReload return true if the component always use hotReload to apply config
// For now, TiProxy always use hotReload, refer to https://github.com/pingcap/tidb-operator/blob/master/pkg/manager/member/tiproxy_member_manager.go#L191-L191
func isCompAlwaysHotReload(comp cloudv1.ComponentType) bool {
	switch comp {
	case cloudv1.ComponentTiProxy:
		return true
	default:
		return false
	}
}
