package server

import (
	"context"
	"sort"

	route53v1alpha1 "github.com/crossplane-contrib/provider-aws/apis/route53/v1alpha1"
	xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
	"github.com/crossplane/crossplane-runtime/pkg/meta"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	azurenetworkv1beta1 "github.com/upbound/provider-azure/apis/network/v1beta1"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/client-go/util/retry"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	internalutil "github.com/tidbcloud/cluster-controller/server/internal/util"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	"github.com/tidbcloud/infra-provider/api/tidbcloud/common"
	"github.com/tidbcloud/infra-provider/pkg/consts"
)

type TidbGroupServer struct {
	apiv1.UnimplementedTidbGroupServiceServer

	KubeClient    client.Client
	clusterReader internalutil.ClusterReader
}

func isDefaultLoadBalancer(loadBalancerID string, cluster *cloudv1.Cluster) (isDefault bool) {
	id := getDefaultLoadBalancer(cluster)
	if id == loadBalancerID {
		return true
	}

	return false
}

func getDefaultLoadBalancer(cluster *cloudv1.Cluster) string {
	if id, ok := cluster.Annotations[consts.DefaultLoadBalancerIDAnnotationKey]; ok {
		return id
	}
	return ""
}

func (s *TidbGroupServer) DeleteTidbGroup(ctx context.Context, req *apiv1.DeleteTidbGroupRequest) (*apiv1.TidbGroup, error) {
	cluster, err := s.getCluster(ctx, req.ClusterId)
	if err != nil {
		return nil, err
	}
	isDefault := isDefaultLoadBalancer(req.TidbGroupId, cluster)
	if isDefault {
		return nil, status.Errorf(codes.InvalidArgument, "default tidb group can not be deleted")
	}

	// extra tidb group
	for _, extraGroup := range cluster.Status.ExtraEndpoints {
		if extraGroup.Name == req.TidbGroupId {
			clusterCopy := cluster.DeepCopy()
			for i, group := range clusterCopy.Spec.ExtraGroups {
				if group.Name == req.TidbGroupId {
					clusterCopy.Spec.ExtraGroups[i].TiDB.Replicas = 0
					clusterCopy.Spec.ExtraGroups[i].TiProxy.Replicas = 0
					if err := s.KubeClient.Update(ctx, clusterCopy); err != nil {
						tilog.Error(ctx, "Failed to delete tidbgroup", zap.Error(err), zap.Any("req", req))
						return nil, status.Errorf(codes.Internal, "Failed to delete tidbgroup %s: %s", req.TidbGroupId, err)
					}
					return &apiv1.TidbGroup{
						Id:        req.TidbGroupId,
						ClusterId: req.ClusterId,
					}, nil
				}
			}
		}
	}

	return nil, status.Errorf(codes.NotFound, "not found tidb group %s", req.TidbGroupId)
}

func (s *TidbGroupServer) UpdateTidbGroup(ctx context.Context, req *apiv1.UpdateTidbGroupRequest) (*apiv1.TidbGroup, error) {
	logger := tilog.WithContextS(ctx)
	cluster, err := s.getCluster(ctx, req.ClusterId)
	if err != nil {
		return nil, err
	}

	idDefault := isDefaultLoadBalancer(req.TidbGroupId, cluster)
	if idDefault {
		cluster.Spec.SqlEndpoints.Private = &cloudv1.Service{
			Enabled: ptr.To(req.TidbGroup.EnablePrivateLb),
		}
		if req.TidbGroup.PrivateLb != nil {
			cluster.Spec.SqlEndpoints.Private.Port = ptr.To(req.TidbGroup.PrivateLb.Port)
		}
		cluster.Spec.SqlEndpoints.Public = cloudv1.Service{
			Enabled: ptr.To(req.TidbGroup.EnablePublicLb),
		}
		if req.TidbGroup.PublicLb != nil {
			cluster.Spec.SqlEndpoints.Public.Port = ptr.To(req.TidbGroup.PublicLb.Port)
			cluster.Spec.SqlEndpoints.Public.LoadBalancerSourceRanges = ipAllowListToSourceRanges(req.TidbGroup.PublicLb.IpAllowList)
		}

		// DNS Identity update for default group
		if req.TidbGroup.ClusterIdentifierHash != "" {
			err := s.updateDNSIdentity(
				ctx,
				logger,
				cluster,
				req.TidbGroupId,
				req.TidbGroup.ClusterIdentifierHash,
				cluster.Spec.SqlEndpoints.DNS.Identity,
				cluster.Spec.SqlEndpoints.DNS.Disabled,
				func() (*bool, *bool, error) {
					publicSynced := s.checkIfDefaultLoadBalancerSynced(ctx, cluster, false)
					privateSynced := s.checkIfDefaultLoadBalancerSynced(ctx, cluster, true)
					return publicSynced, privateSynced, nil
				},
			)
			if err != nil {
				return nil, err
			}
		}
		if req.TidbGroup.DisableDns {
			logger.Info("disable dns for tidb group ", req.TidbGroupId)
			cluster.Spec.SqlEndpoints.DNS.Disabled = true
		} else {
			logger.Info("enable dns for tidb group ", req.TidbGroupId)
			cluster.Spec.SqlEndpoints.DNS.Disabled = false
		}

		if err := s.KubeClient.Update(ctx, cluster); err != nil {
			tilog.Error(ctx, "Failed to update tidbgroup", zap.Error(err), zap.Any("req", req))
			return nil, status.Errorf(codes.Internal, "Failed to update tidbgroup %s: %s", req.TidbGroup.Id, err)
		}
		return req.TidbGroup, nil
	} else {
		for i, group := range cluster.Spec.ExtraGroups {
			if group.Name == req.TidbGroupId {
				// private lb
				if cluster.Spec.ExtraGroups[i].SqlEndpoints.Private == nil {
					cluster.Spec.ExtraGroups[i].SqlEndpoints.Private = &cloudv1.Service{}
				}
				cluster.Spec.ExtraGroups[i].SqlEndpoints.Private.Enabled = ptr.To(req.TidbGroup.EnablePrivateLb)

				if req.TidbGroup.PrivateLb != nil {
					cluster.Spec.ExtraGroups[i].SqlEndpoints.Private.Port = ptr.To(req.TidbGroup.PrivateLb.Port)
				}
				// public lb
				cluster.Spec.ExtraGroups[i].SqlEndpoints.Public.Enabled = ptr.To(req.TidbGroup.EnablePublicLb)

				if req.TidbGroup.PublicLb != nil {
					cluster.Spec.ExtraGroups[i].SqlEndpoints.Public.Port = ptr.To(req.TidbGroup.PublicLb.Port)
					cluster.Spec.ExtraGroups[i].SqlEndpoints.Public.LoadBalancerSourceRanges = ipAllowListToSourceRanges(req.TidbGroup.PublicLb.IpAllowList)
				}
				// DNS Identity update for extra group
				if req.TidbGroup.ClusterIdentifierHash != "" {
					extraGroupStatus, ok := cluster.GetExtraGroupStatus(req.TidbGroupId)
					if !ok {
						logger.Warn("Extra group status NotFound", zap.String("tidbGroup", req.TidbGroupId))
						return nil, status.Errorf(codes.FailedPrecondition, "Extra group status NotFound")
					}

					err := s.updateDNSIdentity(
						ctx,
						logger,
						cluster,
						req.TidbGroupId,
						req.TidbGroup.ClusterIdentifierHash,
						cluster.Spec.ExtraGroups[i].SqlEndpoints.DNS.Identity,
						cluster.Spec.ExtraGroups[i].SqlEndpoints.DNS.Disabled,
						func() (*bool, *bool, error) {
							publicSynced := s.checkIfExtraGroupLoadBalancerSynced(ctx, cluster, extraGroupStatus, &group, false)
							privateSynced := s.checkIfExtraGroupLoadBalancerSynced(ctx, cluster, extraGroupStatus, &group, true)
							return publicSynced, privateSynced, nil
						},
					)
					if err != nil {
						return nil, err
					}
				}
				if req.TidbGroup.DisableDns {
					logger.Info("disable dns for tidb group ", req.TidbGroupId)
					cluster.Spec.ExtraGroups[i].SqlEndpoints.DNS.Disabled = true
				} else {
					logger.Info("enable dns for tidb group ", req.TidbGroupId)
					cluster.Spec.ExtraGroups[i].SqlEndpoints.DNS.Disabled = false
				}

				if err := s.KubeClient.Update(ctx, cluster); err != nil {
					tilog.Error(ctx, "Failed to update tidbgroup", zap.Error(err), zap.Any("req", req))
					return nil, status.Errorf(codes.Internal, "Failed to update tidbgroup %s: %s", req.TidbGroupId, err)
				}
				return req.TidbGroup, nil
			}
		}
	}

	return nil, status.Errorf(codes.NotFound, "not found tidb group %s", req.TidbGroupId)
}

func (s *TidbGroupServer) SetPrivateLinkIPAddressType(
	ctx context.Context,
	req *apiv1.SetPrivateLinkIPAddressTypeRequest,
) (*emptypb.Empty, error) {
	requested, updateRequested, err := privateLinkIPAddressTypeUpdateFromAPI(req.GetRequestedIpAddressType())
	if err != nil {
		return nil, err
	}
	nlb, updateNLB, err := privateLinkIPAddressTypeUpdateFromAPI(req.GetNlbIpAddressType())
	if err != nil {
		return nil, err
	}
	if !updateRequested && !updateNLB {
		return nil, status.Error(codes.InvalidArgument, "at least one PrivateLink IP address type target must be specified")
	}
	err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
		cluster, err := s.getCluster(ctx, req.GetClusterId())
		if err != nil {
			return err
		}
		if cluster.Spec.Kubernetes.Provider != consts.ProviderTypeEKS {
			return status.Errorf(codes.InvalidArgument, "PrivateLink IP address type is only supported for EKS")
		}
		target, ok := cluster.GetSQLEndpoints(req.GetTidbGroupId())
		if !ok {
			return status.Errorf(codes.NotFound, "not found tidb group %s", req.GetTidbGroupId())
		}
		changed := false
		if updateRequested && target.PrivateLinkIPAddressType != requested {
			target.PrivateLinkIPAddressType = requested
			changed = true
		}
		if updateNLB && target.PrivateLinkNLBIPAddressType != nlb {
			target.PrivateLinkNLBIPAddressType = nlb
			changed = true
		}
		if !changed {
			return nil
		}
		return s.KubeClient.Update(ctx, cluster)
	})
	if err != nil {
		return nil, err
	}

	return &emptypb.Empty{}, nil
}

func privateLinkIPAddressTypeUpdateFromAPI(
	addressType apiv1.PrivateLinkIPAddressType,
) (cloudv1.PrivateLinkIPAddressType, bool, error) {
	switch addressType {
	case apiv1.PrivateLinkIPAddressType_PRIVATE_LINK_IP_ADDRESS_TYPE_UNSPECIFIED:
		return "", false, nil
	case apiv1.PrivateLinkIPAddressType_PRIVATE_LINK_IP_ADDRESS_TYPE_IPV4:
		return cloudv1.PrivateLinkIPAddressTypeIPv4, true, nil
	case apiv1.PrivateLinkIPAddressType_PRIVATE_LINK_IP_ADDRESS_TYPE_DUALSTACK:
		return cloudv1.PrivateLinkIPAddressTypeDualstack, true, nil
	default:
		return "", false, status.Errorf(codes.InvalidArgument, "unsupported PrivateLink IP address type %q", addressType)
	}
}

func (s *TidbGroupServer) ListTidbGroups(ctx context.Context, req *apiv1.ListTidbGroupsRequest) (res *apiv1.ListTidbGroupsResponse, err error) {
	res = &apiv1.ListTidbGroupsResponse{}
	cluster, err := s.getCluster(ctx, req.ClusterId)
	if err != nil {
		return nil, err
	}

	var tgs []*apiv1.TidbGroup
	// default tidb group
	defaultID := getDefaultLoadBalancer(cluster)
	if defaultID != "" {
		defaultTiDB := &apiv1.TidbGroup{
			Id:        defaultID,
			ClusterId: req.ClusterId,
			IsDefault: true,
		}
		defaultTiDB.ClusterIdentifierHash = cluster.Spec.SqlEndpoints.DNS.Identity

		if cluster.Status.DB != nil && cluster.Status.DB.TiDB != nil {
			defaultTiDB.Count = uint32(cluster.Status.DB.TiDB.UpdatedReplicas)
			if len(cluster.Status.DB.TiDB.Members) > 0 {
				defaultTiDB.InstanceType = cluster.Status.DB.TiDB.Members[0].InstanceType
			}
			defaultTiDB.Tidb = &apiv1.Cluster_NodeSpec{
				ComponentType:     common.ComponentType_TIDB,
				InstanceType:      defaultTiDB.InstanceType,
				Count:             defaultTiDB.Count,
				Storage:           nil, // tidb has no storage
				AdditionalStorage: nil, // tidb has no storage
			}
		}
		if cluster.Status.DB != nil && cluster.Status.DB.TiProxy != nil {
			defaultTiDB.Tiproxy = &apiv1.Cluster_NodeSpec{
				ComponentType:     common.ComponentType_TIPROXY,
				InstanceType:      "", // fill later
				Count:             uint32(cluster.Status.DB.TiProxy.UpdatedReplicas),
				Storage:           nil, // tiproxy has no storage
				AdditionalStorage: nil, // tiproxy has no storage
			}
			if len(cluster.Status.DB.TiProxy.Members) > 0 {
				defaultTiDB.Tiproxy.InstanceType = cluster.Status.DB.TiProxy.Members[0].InstanceType
			}
		}

		defaultTiDB.EnablePublicLb = cluster.Spec.SqlEndpoints.Public.Enabled != nil && *cluster.Spec.SqlEndpoints.Public.Enabled
		defaultTiDB.PublicLb = &apiv1.LoadBalancer{
			IpAllowList: sourceRangesToIpAllowList(cluster.Spec.SqlEndpoints.Public.LoadBalancerSourceRanges),
			Synced:      s.checkIfDefaultLoadBalancerSynced(ctx, cluster, true),
		}
		if cluster.Spec.SqlEndpoints.Public.Port != nil {
			defaultTiDB.PublicLb.Port = *cluster.Spec.SqlEndpoints.Public.Port
		}
		if cluster.Status.SqlEndpoints.PublicEndpoint != nil {
			defaultTiDB.PublicLb.Host = cluster.Status.SqlEndpoints.PublicEndpoint.Host
			defaultTiDB.PublicLb.CloudResourceId = cluster.Status.SqlEndpoints.PublicEndpoint.ResourceID
		}

		defaultTiDB.EnablePrivateLb = cluster.Spec.SqlEndpoints.Private != nil && cluster.Spec.SqlEndpoints.Private.Enabled != nil && *cluster.Spec.SqlEndpoints.Private.Enabled
		defaultTiDB.PrivateLb = &apiv1.LoadBalancer{}
		if cluster.Spec.SqlEndpoints.Private != nil && cluster.Spec.SqlEndpoints.Private.Port != nil {
			defaultTiDB.PrivateLb.Port = *cluster.Spec.SqlEndpoints.Private.Port
		}
		if cluster.Status.SqlEndpoints.PrivateEndpoint != nil {
			defaultTiDB.PrivateLb.Host = cluster.Status.SqlEndpoints.PrivateEndpoint.Host
			defaultTiDB.PrivateLb.Synced = s.checkIfDefaultLoadBalancerSynced(ctx, cluster, false)
			defaultTiDB.PrivateLb.CloudResourceId = cluster.Status.SqlEndpoints.PrivateEndpoint.ResourceID
		}
		defaultTiDB.DisableDns = cluster.Spec.SqlEndpoints.DNS.Disabled
		tgs = append(tgs, defaultTiDB)
	}

	// extra tidb group
	for _, extraGroup := range cluster.Status.ExtraEndpoints {
		var private *apiv1.LoadBalancer
		var public *apiv1.LoadBalancer
		var updatedReplicas uint32
		var instanceType string
		var tiproxyReplicas uint32
		var tiproxyInstanceType string
		extraGroupSpec := getExtraGroupSpec(cluster, extraGroup.Name)
		// deleted extra group shoule be ignored
		if extraGroupSpec == nil {
			continue
		}

		private = &apiv1.LoadBalancer{
			Synced: s.checkIfExtraGroupLoadBalancerSynced(ctx, cluster, &extraGroup, extraGroupSpec, false),
		}
		if extraGroupSpec.SqlEndpoints.Private != nil && extraGroupSpec.SqlEndpoints.Private.Port != nil {
			private.Port = *extraGroupSpec.SqlEndpoints.Private.Port
		}
		if extraGroup.SqlEndpointsStatus.PrivateEndpoint != nil {
			private.Host = extraGroup.SqlEndpointsStatus.PrivateEndpoint.Host
			private.CloudResourceId = extraGroup.SqlEndpointsStatus.PrivateEndpoint.ResourceID
		}

		public = &apiv1.LoadBalancer{
			IpAllowList: sourceRangesToIpAllowList(extraGroupSpec.SqlEndpoints.Public.LoadBalancerSourceRanges),
			Synced:      s.checkIfExtraGroupLoadBalancerSynced(ctx, cluster, &extraGroup, extraGroupSpec, true),
		}
		if extraGroupSpec.SqlEndpoints.Public.Port != nil {
			public.Port = *extraGroupSpec.SqlEndpoints.Public.Port
		}
		if extraGroup.SqlEndpointsStatus.PublicEndpoint != nil {
			public.Host = extraGroup.SqlEndpointsStatus.PublicEndpoint.Host
			public.CloudResourceId = extraGroup.SqlEndpointsStatus.PublicEndpoint.ResourceID
		}
		if extraGroup.TiDB != nil {
			updatedReplicas = uint32(extraGroup.TiDB.UpdatedReplicas)
			if len(extraGroup.TiDB.Members) > 0 {
				instanceType = extraGroup.TiDB.Members[0].InstanceType
			}
		}
		if extraGroup.TiProxy != nil {
			tiproxyReplicas = uint32(extraGroup.TiProxy.UpdatedReplicas)
			if len(extraGroup.TiProxy.Members) > 0 {
				tiproxyInstanceType = extraGroup.TiProxy.Members[0].InstanceType
			}
		}

		identity := extraGroupSpec.SqlEndpoints.DNS.Identity
		tg := &apiv1.TidbGroup{
			Id:                    extraGroup.Name,
			ClusterId:             req.ClusterId,
			Count:                 updatedReplicas,
			IsDefault:             false,
			PrivateLb:             private,
			EnablePublicLb:        extraGroupSpec.SqlEndpoints.Public.Enabled != nil && *extraGroupSpec.SqlEndpoints.Public.Enabled,
			EnablePrivateLb:       extraGroupSpec.SqlEndpoints.Private != nil && extraGroupSpec.SqlEndpoints.Private.Enabled != nil && *extraGroupSpec.SqlEndpoints.Private.Enabled,
			PublicLb:              public,
			InstanceType:          instanceType,
			ClusterIdentifierHash: identity,
			Tidb: &apiv1.Cluster_NodeSpec{
				ComponentType:     common.ComponentType_TIDB,
				InstanceType:      instanceType,
				Count:             updatedReplicas,
				Storage:           nil, // tidb has no storage
				AdditionalStorage: nil, // tidb has no storage
			},
			DisableDns: extraGroupSpec.SqlEndpoints.DNS.Disabled,
		}
		if extraGroup.TiProxy != nil {
			tg.Tiproxy = &apiv1.Cluster_NodeSpec{
				ComponentType:     common.ComponentType_TIPROXY,
				InstanceType:      tiproxyInstanceType,
				Count:             tiproxyReplicas,
				Storage:           nil, // tiproxy has no storage
				AdditionalStorage: nil, // tiproxy has no storage
			}
		}
		tgs = append(tgs, tg)
	}

	sort.Sort(tidbGroups(tgs))
	res.TidbGroups = tgs
	return res, nil
}

// updateDNSIdentity updates DNS identity with validation and sync checks.
// It handles both default and extra group DNS identity updates.
// For default group, pass groupIndex as -1; for extra groups, pass the actual index.
func (s *TidbGroupServer) updateDNSIdentity(
	ctx context.Context,
	logger *zap.SugaredLogger,
	cluster *cloudv1.Cluster,
	tidbGroupId string,
	requestedIdentity string,
	currentIdentity string,
	currentDnsDisabled bool,
	checkSyncFunc func() (*bool, *bool, error),
) error {
	if requestedIdentity == currentIdentity {
		logger.Infow("DNS Identity already matches requested value, skipping update",
			"tidbGroup", tidbGroupId,
			"identity", currentIdentity)
		return nil
	}

	if !currentDnsDisabled {
		logger.Warnw("Cannot update DNS Identity: DNS must be disabled first",
			"tidbGroup", tidbGroupId,
			"currentIdentity", currentIdentity,
			"requestedIdentity", requestedIdentity)
		return status.Errorf(codes.FailedPrecondition,
			"DNS must be disabled before changing Identity. Current: %s, Requested: %s",
			currentIdentity, requestedIdentity)
	}

	publicSynced, privateSynced, err := checkSyncFunc()
	if err != nil {
		return err
	}

	if publicSynced == nil || privateSynced == nil {
		logger.Errorw("Failed to check LoadBalancer sync status",
			"tidbGroup", tidbGroupId,
			"publicSynced", publicSynced,
			"privateSynced", privateSynced)
		return status.Errorf(codes.Internal,
			"Failed to check LoadBalancer sync status")
	}

	if !*publicSynced || !*privateSynced {
		logger.Warnw("Cannot update DNS Identity: LoadBalancers not fully synced",
			"tidbGroup", tidbGroupId,
			"publicSynced", *publicSynced,
			"privateSynced", *privateSynced)
		return status.Errorf(codes.FailedPrecondition,
			"LoadBalancers must be fully synced before changing DNS Identity")
	}

	// Update DNS identity based on group type
	if isDefaultLoadBalancer(tidbGroupId, cluster) {
		// Default group
		cluster.Spec.SqlEndpoints.DNS.Identity = requestedIdentity
	} else {
		// Extra group
		for i, group := range cluster.Spec.ExtraGroups {
			if group.Name == tidbGroupId {
				cluster.Spec.ExtraGroups[i].SqlEndpoints.DNS.Identity = requestedIdentity
			}
		}
	}

	logger.Infow("Successfully updated DNS Identity",
		"tidbGroup", tidbGroupId,
		"oldIdentity", currentIdentity,
		"newIdentity", requestedIdentity)

	return nil
}

func (s *TidbGroupServer) getCluster(ctx context.Context, id string) (*cloudv1.Cluster, error) {
	cluster, err := s.clusterReader.Get(ctx, s.KubeClient, id)
	if err != nil {
		tilog.Error(ctx, "Failed to get cluster", zap.Error(err), zap.String("cluster", id))
		if apierrors.IsNotFound(err) {
			tilog.Info(ctx, "Cluster not found", zap.String("cluster", id))
			return nil, status.Errorf(codes.NotFound, "Cluster %s not found", id)
		}

		tilog.Info(ctx, "Failed to get cluster", zap.String("cluster", id))
		return nil, status.Errorf(codes.Internal, "Failed to get cluster %s: %s", id, err)
	}
	return cluster, nil
}

func (s *TidbGroupServer) GetTidbGroup(ctx context.Context, req *apiv1.GetTidbGroupRequest) (tg *apiv1.TidbGroup, err error) {
	tg = &apiv1.TidbGroup{}
	cluster, err := s.getCluster(ctx, req.ClusterId)
	if err != nil {
		return nil, err
	}

	isDefault := isDefaultLoadBalancer(req.TidbGroupId, cluster)
	found := false

	if isDefault {
		tg.ClusterId = req.ClusterId
		tg.Id = req.TidbGroupId
		tg.IsDefault = true
		tg.ClusterIdentifierHash = cluster.Spec.SqlEndpoints.DNS.Identity

		tg.EnablePublicLb = cluster.Spec.SqlEndpoints.Public.Enabled != nil && *cluster.Spec.SqlEndpoints.Public.Enabled
		tg.PublicLb = &apiv1.LoadBalancer{
			IpAllowList: sourceRangesToIpAllowList(cluster.Spec.SqlEndpoints.Public.LoadBalancerSourceRanges),
			Synced:      s.checkIfDefaultLoadBalancerSynced(ctx, cluster, true),
		}
		if cluster.Spec.SqlEndpoints.Public.Port != nil {
			tg.PublicLb.Port = *cluster.Spec.SqlEndpoints.Public.Port
		}
		if cluster.Status.SqlEndpoints.PublicEndpoint != nil {
			tg.PublicLb.Host = cluster.Status.SqlEndpoints.PublicEndpoint.Host
			tg.PublicLb.CloudResourceId = cluster.Status.SqlEndpoints.PublicEndpoint.ResourceID
		}

		tg.EnablePrivateLb = cluster.Spec.SqlEndpoints.Private != nil && cluster.Spec.SqlEndpoints.Private.Enabled != nil && *cluster.Spec.SqlEndpoints.Private.Enabled
		tg.PrivateLb = &apiv1.LoadBalancer{}
		if cluster.Spec.SqlEndpoints.Private != nil && cluster.Spec.SqlEndpoints.Private.Port != nil {
			tg.PrivateLb.Port = *cluster.Spec.SqlEndpoints.Private.Port
		}
		if cluster.Status.SqlEndpoints.PrivateEndpoint != nil {
			tg.PrivateLb.Host = cluster.Status.SqlEndpoints.PrivateEndpoint.Host
			tg.PrivateLb.Synced = s.checkIfDefaultLoadBalancerSynced(ctx, cluster, false)
			tg.PrivateLb.CloudResourceId = cluster.Status.SqlEndpoints.PrivateEndpoint.ResourceID
		}
		if cluster.Status.DB != nil && cluster.Status.DB.TiDB != nil {
			tg.Count = uint32(cluster.Status.DB.TiDB.UpdatedReplicas)
			if len(cluster.Status.DB.TiDB.Members) > 0 {
				tg.InstanceType = cluster.Status.DB.TiDB.Members[0].InstanceType
			}
			tg.Tidb = &apiv1.Cluster_NodeSpec{
				ComponentType:     common.ComponentType_TIDB,
				InstanceType:      tg.InstanceType,
				Count:             tg.Count,
				Storage:           nil, // tidb has no storage
				AdditionalStorage: nil, // tidb has no storage
			}
		}
		if cluster.Status.DB != nil && cluster.Status.DB.TiProxy != nil {
			tg.Tiproxy = &apiv1.Cluster_NodeSpec{
				ComponentType:     common.ComponentType_TIPROXY,
				InstanceType:      "", // fill later
				Count:             uint32(cluster.Status.DB.TiProxy.UpdatedReplicas),
				Storage:           nil, // tiproxy has no storage
				AdditionalStorage: nil, // tiproxy has no storage
			}
			if len(cluster.Status.DB.TiProxy.Members) > 0 {
				tg.Tiproxy.InstanceType = cluster.Status.DB.TiProxy.Members[0].InstanceType
			}
		}
		tg.DisableDns = cluster.Spec.SqlEndpoints.DNS.Disabled
		return
	} else {
		tg.IsDefault = false
		for _, extraGroup := range cluster.Status.ExtraEndpoints {
			if extraGroup.Name == req.TidbGroupId {
				tg.ClusterId = req.ClusterId
				tg.Id = req.TidbGroupId
				found = true

				extraGroupSpec := getExtraGroupSpec(cluster, req.TidbGroupId)
				if extraGroupSpec != nil {
					tg.ClusterIdentifierHash = extraGroupSpec.SqlEndpoints.DNS.Identity
				}

				tg.EnablePrivateLb = extraGroupSpec.SqlEndpoints.Private != nil && extraGroupSpec.SqlEndpoints.Private.Enabled != nil && *extraGroupSpec.SqlEndpoints.Private.Enabled
				tg.PrivateLb = &apiv1.LoadBalancer{}
				if extraGroupSpec.SqlEndpoints.Private != nil && extraGroupSpec.SqlEndpoints.Private.Port != nil {
					tg.PrivateLb.Port = *extraGroupSpec.SqlEndpoints.Private.Port
				}
				if extraGroup.SqlEndpointsStatus.PrivateEndpoint != nil {
					tg.PrivateLb.Host = extraGroup.SqlEndpointsStatus.PrivateEndpoint.Host
					tg.PrivateLb.Synced = s.checkIfExtraGroupLoadBalancerSynced(ctx, cluster, &extraGroup, extraGroupSpec, false)
					tg.PrivateLb.CloudResourceId = extraGroup.SqlEndpointsStatus.PrivateEndpoint.ResourceID
				}

				tg.EnablePublicLb = extraGroupSpec.SqlEndpoints.Public.Enabled != nil && *extraGroupSpec.SqlEndpoints.Public.Enabled
				tg.PublicLb = &apiv1.LoadBalancer{
					IpAllowList: sourceRangesToIpAllowList(extraGroupSpec.SqlEndpoints.Public.LoadBalancerSourceRanges),
					Synced:      s.checkIfExtraGroupLoadBalancerSynced(ctx, cluster, &extraGroup, extraGroupSpec, true),
				}
				if extraGroupSpec.SqlEndpoints.Public.Port != nil {
					tg.PublicLb.Port = *extraGroupSpec.SqlEndpoints.Public.Port
				}
				if extraGroup.SqlEndpointsStatus.PublicEndpoint != nil {
					tg.PublicLb.Host = extraGroup.SqlEndpointsStatus.PublicEndpoint.Host
					tg.PublicLb.CloudResourceId = extraGroup.SqlEndpointsStatus.PublicEndpoint.ResourceID
				}
				if extraGroup.TiDB != nil {
					tg.Count = uint32(extraGroup.TiDB.UpdatedReplicas)
					if len(extraGroup.TiDB.Members) > 0 {
						tg.InstanceType = extraGroup.TiDB.Members[0].InstanceType
					}
					tg.Tidb = &apiv1.Cluster_NodeSpec{
						ComponentType:     common.ComponentType_TIDB,
						InstanceType:      tg.InstanceType,
						Count:             tg.Count,
						Storage:           nil, // tidb has no storage
						AdditionalStorage: nil, // tidb has no storage
					}
				}
				if extraGroup.TiProxy != nil {
					tg.Tiproxy = &apiv1.Cluster_NodeSpec{
						ComponentType:     common.ComponentType_TIPROXY,
						InstanceType:      "", // fill later
						Count:             uint32(extraGroup.TiProxy.UpdatedReplicas),
						Storage:           nil, // tiproxy has no storage
						AdditionalStorage: nil, // tiproxy has no storage
					}
					if len(extraGroup.TiProxy.Members) > 0 {
						tg.Tiproxy.InstanceType = extraGroup.TiProxy.Members[0].InstanceType
					}
				}
				tg.DisableDns = extraGroupSpec.SqlEndpoints.DNS.Disabled
			}
		}
		if !found {
			return nil, status.Errorf(codes.NotFound, "not found tidb group %s", req.TidbGroupId)
		}
		return
	}
}

func (s *TidbGroupServer) CreateTidbGroup(ctx context.Context, req *apiv1.CreateTidbGroupRequest) (*apiv1.TidbGroup, error) {
	logger := tilog.WithContextS(ctx)
	clusterID := req.TidbGroup.ClusterId
	tidbGroupID := req.TidbGroup.Id
	if clusterID == "" {
		return nil, status.Errorf(codes.InvalidArgument, "cluster id is required")
	}
	if tidbGroupID == "" {
		return nil, status.Errorf(codes.InvalidArgument, "tidb group id is required")
	}
	cluster, err := s.getCluster(ctx, clusterID)
	if err != nil {
		return nil, err
	}
	clusterCopy := cluster.DeepCopy()

	isDefault := isDefaultLoadBalancer(tidbGroupID, cluster)
	if isDefault {
		req.TidbGroup.IsDefault = true
		if req.TidbGroup.EnablePrivateLb {
			if clusterCopy.Spec.SqlEndpoints.Private != nil && clusterCopy.Spec.SqlEndpoints.Private.Enabled != nil && *clusterCopy.Spec.SqlEndpoints.Private.Enabled {
				return nil, status.Errorf(codes.AlreadyExists, "private load balancer already exists")
			}
			clusterCopy.Spec.SqlEndpoints.Private = &cloudv1.Service{
				Enabled: ptr.To(true),
			}
			if req.TidbGroup.PrivateLb != nil {
				clusterCopy.Spec.SqlEndpoints.Private = &cloudv1.Service{
					Port: ptr.To(req.TidbGroup.PrivateLb.Port),
				}
			}
		}
		if req.TidbGroup.EnablePublicLb {
			if clusterCopy.Spec.SqlEndpoints.Public.Enabled != nil && *clusterCopy.Spec.SqlEndpoints.Public.Enabled {
				return nil, status.Errorf(codes.AlreadyExists, "public load balancer already exists")
			}
			clusterCopy.Spec.SqlEndpoints.Public = cloudv1.Service{
				Enabled: ptr.To(true),
			}
			if req.TidbGroup.PublicLb != nil {
				clusterCopy.Spec.SqlEndpoints.Public = cloudv1.Service{
					LoadBalancerSourceRanges: ipAllowListToSourceRanges(req.TidbGroup.PublicLb.IpAllowList),
					Port:                     ptr.To(req.TidbGroup.PublicLb.Port),
				}
			}

		}
		if req.TidbGroup.DisableDns {
			logger.Info("disable dns for tidb group", "tidbGroup", tidbGroupID)
			clusterCopy.Spec.SqlEndpoints.DNS.Disabled = true
		} else {
			logger.Info("enable dns for tidb group", "tidbGroup", tidbGroupID)
			clusterCopy.Spec.SqlEndpoints.DNS.Disabled = false
		}
	} else {
		extraGroupSpec := getExtraGroupSpec(cluster, tidbGroupID)
		if extraGroupSpec != nil {
			return nil, status.Errorf(codes.AlreadyExists, "tidb group %s already exists", tidbGroupID)
		}
		if req.TidbGroup.Tidb == nil {
			req.TidbGroup.Tidb = &apiv1.Cluster_NodeSpec{
				InstanceType: req.TidbGroup.InstanceType,
				Count:        req.TidbGroup.Count,
			}
		}
		// instance type is not set, use the default instance type
		if req.TidbGroup.Tidb.InstanceType == "" {
			req.TidbGroup.Tidb.InstanceType = cluster.Spec.Cluster.TiDB.InstanceType
		}
		if req.TidbGroup.Tidb.Count != 0 {
			logger.Warnf("tidb count(%d) is set in tidbgroup(%s) creation.", req.TidbGroup.Tidb.Count, tidbGroupID)
		}

		extraGroup := cloudv1.ExtraGroupSpec{
			Name: tidbGroupID,
			TiDB: cloudv1.TiDB{
				// TODO(ideascf):  To keep API definition symmetry, we shouldn't support to create tidbs in tidbgroup creation.
				// Instead, use the scale API to scale the tidb out from zero in tidbgroups.
				Replicas: int(req.TidbGroup.Tidb.Count),
				// TODO(ideascf):
				// We don't support create a tidbgroup with different instance type as the default tidbgroup.
				// So we should directly use the default instance type from the cluster.
				InstanceType: req.TidbGroup.Tidb.InstanceType,
			},
			SqlEndpoints: cloudv1.SqlEndpoints{
				DNS: cloudv1.DNS{
					Identity: req.TidbGroup.ClusterIdentifierHash,
				},
				Public: cloudv1.Service{
					// default is false
					Enabled: ptr.To(false),
				},
			},
		}
		// We don't support to create tiproxy in tidbgroup creation
		// Use the scale API to create tiproxy in tidbgroups.
		// if req.TidbGroup.Tiproxy != nil {
		// 	extraGroup.TiProxy = cloudv1.TiProxy{
		// 		Replicas:     int(req.TidbGroup.Tiproxy.Count),
		// 		InstanceType: req.TidbGroup.Tiproxy.InstanceType,
		// 	}
		// }

		if req.TidbGroup.EnablePrivateLb {
			extraGroup.SqlEndpoints.Private = &cloudv1.Service{
				Enabled: ptr.To(true),
				// default port is 4000
				Port: ptr.To(int32(4000)),
			}

			if req.TidbGroup.PrivateLb != nil && req.TidbGroup.PrivateLb.Port != 0 {
				extraGroup.SqlEndpoints.Private.Port = ptr.To(req.TidbGroup.PrivateLb.Port)
			}
		}
		if req.TidbGroup.EnablePublicLb {
			extraGroup.SqlEndpoints.Public = cloudv1.Service{
				Enabled: ptr.To(true),
				// default port is 4000
				Port: ptr.To(int32(4000)),
			}
			if req.TidbGroup.PublicLb != nil {
				extraGroup.SqlEndpoints.Public.LoadBalancerSourceRanges = ipAllowListToSourceRanges(req.TidbGroup.PublicLb.IpAllowList)
				if req.TidbGroup.PublicLb.Port != 0 {
					extraGroup.SqlEndpoints.Public.Port = ptr.To(req.TidbGroup.PublicLb.Port)
				}
			}
		}
		if req.TidbGroup.DisableDns {
			logger.Info("disable dns for tidb group", "tidbGroup", tidbGroupID)
			clusterCopy.Spec.SqlEndpoints.DNS.Disabled = true
		} else {
			logger.Info("enable dns for tidb group", "tidbGroup", tidbGroupID)
			clusterCopy.Spec.SqlEndpoints.DNS.Disabled = false
		}
		clusterCopy.Spec.ExtraGroups = append(clusterCopy.Spec.ExtraGroups, extraGroup)
	}

	if err := s.KubeClient.Update(ctx, clusterCopy); err != nil {
		tilog.Error(ctx, "Failed to create tidbgroup", zap.Error(err), zap.Any("req", req))
		return nil, status.Errorf(codes.Internal, "Failed to create tidbgroup %s: %s", clusterID, err)
	}

	return req.TidbGroup, nil
}

func ipAllowListToSourceRanges(addresses []*apiv1.LoadBalancer_IpAddress) []string {
	sourceRanges := []string{}
	for _, address := range addresses {
		sourceRanges = append(sourceRanges, address.CidrBlock)
	}

	sort.Strings(sourceRanges)

	return sourceRanges
}

func sourceRangesToIpAllowList(sourceRanges []string) []*apiv1.LoadBalancer_IpAddress {
	addresses := []*apiv1.LoadBalancer_IpAddress{}
	for _, sourceRange := range sourceRanges {
		addresses = append(addresses, &apiv1.LoadBalancer_IpAddress{
			CidrBlock: sourceRange,
		})
	}

	return addresses
}

func NewTidbGroupServer(kubeClient client.Client) *TidbGroupServer {
	cs := &TidbGroupServer{
		KubeClient:    kubeClient,
		clusterReader: &internalutil.LabelSelectorReader{},
	}

	return cs
}

type tidbGroups []*apiv1.TidbGroup

func (a tidbGroups) Len() int           { return len(a) }
func (a tidbGroups) Less(i, j int) bool { return a[i].Id < a[j].Id }
func (a tidbGroups) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }

func getExtraGroupSpec(cluster *cloudv1.Cluster, id string) *cloudv1.ExtraGroupSpec {
	for _, group := range cluster.Spec.ExtraGroups {
		if group.Name == id {
			return &group
		}
	}
	return nil
}

// LoadBalancerSyncCheckParams contains parameters for checking load balancer sync status
type LoadBalancerSyncCheckParams struct {
	IsPublic        bool
	EndpointEnabled bool
	EndpointHadSet  bool
	DNSRecordName   string
	ExtraGroupName  string // optional, for extra groups logging
}

// checkIfLoadBalancerSynced calculates whether the load balancer is synced based on the same logic as GetClusterPublicLBStatus
func (s *TidbGroupServer) checkIfLoadBalancerSynced(ctx context.Context, cluster *cloudv1.Cluster, params LoadBalancerSyncCheckParams) *bool {
	logger := tilog.WithContextL(ctx).With(
		zap.String("cluster", cluster.GetClusterID()),
		zap.Bool("isPublic", params.IsPublic),
		zap.Bool("endpointEnabled", params.EndpointEnabled),
		zap.Bool("endpointHadSet", params.EndpointHadSet),
		zap.String("dnsRecordName", params.DNSRecordName),
		zap.String("extraGroup", params.ExtraGroupName),
	)

	// Check DNS record status to determine if DNS is being modified
	isDNSModifying, err := s.isDNSRecordModifying(ctx, params.DNSRecordName, cluster)
	if err != nil {
		logger.Error("Failed to check DNS record status", zap.Error(err))
		return nil
	}

	if params.EndpointEnabled {
		// for enable endpoint, EndpointHadSet should be true, and DNS should not be modifying
		logger.Info("Check if LB is synced for enable endpoint", zap.Bool("EndpointHadSet", params.EndpointHadSet), zap.Bool("isDNSModifying", isDNSModifying))
		return ptr.To(params.EndpointHadSet && !isDNSModifying)
	} else {
		// for disable endpoint, EndpointHadSet should be false, and DNS should be not found
		isDNSRecordNotFound, err := s.isDNSRecordNotFound(ctx, params.DNSRecordName, cluster)
		if err != nil {
			logger.Error("Failed to check if DNS record exists", zap.Error(err))
			// If we can't determine DNS record existence, return nil to be safe
			return nil
		}
		logger.Info("Check if LB is synced for disable endpoint", zap.Bool("EndpointHadSet", params.EndpointHadSet), zap.Bool("isDNSRecordNotFound", isDNSRecordNotFound))
		return ptr.To(!params.EndpointHadSet && isDNSRecordNotFound)
	}
}

// checkIfDefaultLoadBalancerSynced is a convenience wrapper for default tidb group load balancer sync check
func (s *TidbGroupServer) checkIfDefaultLoadBalancerSynced(ctx context.Context, cluster *cloudv1.Cluster, isPublicEndpoint bool) *bool {
	var (
		endpointEnabled bool
		endpointHadSet  bool
		dnsRecordName   string
	)

	dnsRecordName = cluster.GetDNSRecordName(isPublicEndpoint, nil)

	if isPublicEndpoint {
		endpointEnabled = cluster.Spec.SqlEndpoints.Public.IsEnabled()
		endpointHadSet = cluster.Status.SqlEndpoints.PublicEndpoint != nil
	} else {
		endpointEnabled = cluster.Spec.SqlEndpoints.Private.IsEnabled()
		endpointHadSet = cluster.Status.SqlEndpoints.PrivateEndpoint != nil
	}

	return s.checkIfLoadBalancerSynced(ctx, cluster, LoadBalancerSyncCheckParams{
		IsPublic:        isPublicEndpoint,
		EndpointEnabled: endpointEnabled,
		EndpointHadSet:  endpointHadSet,
		DNSRecordName:   dnsRecordName,
	})
}

// checkIfExtraGroupLoadBalancerSynced is a convenience wrapper for extra tidb group load balancer sync check
func (s *TidbGroupServer) checkIfExtraGroupLoadBalancerSynced(ctx context.Context, cluster *cloudv1.Cluster, extraGroupStatus *cloudv1.ExtraGroupStatus, extraGroupSpec *cloudv1.ExtraGroupSpec, isPublicEndpoint bool) *bool {
	if extraGroupSpec == nil {
		return nil
	}

	var (
		endpointEnabled bool
		endpointHadSet  bool
		dnsRecordName   string
	)

	dnsRecordName = cluster.GetDNSRecordName(isPublicEndpoint, extraGroupStatus)

	if isPublicEndpoint {
		endpointEnabled = extraGroupSpec.SqlEndpoints.Public.IsEnabled()
		endpointHadSet = extraGroupStatus.SqlEndpointsStatus.PublicEndpoint != nil
	} else {
		endpointEnabled = extraGroupSpec.SqlEndpoints.Private.IsEnabled()
		endpointHadSet = extraGroupStatus.SqlEndpointsStatus.PrivateEndpoint != nil
	}

	return s.checkIfLoadBalancerSynced(ctx, cluster, LoadBalancerSyncCheckParams{
		IsPublic:        isPublicEndpoint,
		EndpointEnabled: endpointEnabled,
		EndpointHadSet:  endpointHadSet,
		DNSRecordName:   dnsRecordName,
		ExtraGroupName:  extraGroupStatus.Name,
	})
}

// isDNSRecordModifying checks if the DNS record is currently being modified (creating, updating, or deleting)
// return false if DNS record is not found, as DNS record is not modifying
// return false if DNS record is found but both Ready and Synced are True, as DNS record is stable
// otherwise, return true if DNS record is being modified
func (s *TidbGroupServer) isDNSRecordModifying(ctx context.Context, dnsRecordName string, cluster *cloudv1.Cluster) (bool, error) {
	logger := tilog.WithContextL(ctx).With(zap.String("dnsRecordName", dnsRecordName), zap.String("provider", cluster.Spec.Kubernetes.Provider))

	if dnsRecordName == "" {
		logger.Info("DNS record name is empty, not modifying")
		return false, nil
	}

	switch cluster.Spec.Kubernetes.Provider {
	case consts.ProviderTypeAKS:
		return s.isDNSRecordModifyingAzure(ctx, dnsRecordName, logger)
	case consts.ProviderTypeEKS, consts.ProviderTypeGKE:
		return s.isDNSRecordModifyingAWS(ctx, dnsRecordName, logger)
	default:
		logger.Warn("Unsupported provider for DNS record checking", zap.String("provider", cluster.Spec.Kubernetes.Provider))
		return false, status.Errorf(codes.InvalidArgument, "unsupported provider for DNS record checking: %s", cluster.Spec.Kubernetes.Provider)
	}
}

// isDNSRecordModifyingAWS checks AWS Route53 ResourceRecordSet modification status
func (s *TidbGroupServer) isDNSRecordModifyingAWS(ctx context.Context, dnsRecordName string, logger *zap.Logger) (bool, error) {
	// Try to get the ResourceRecordSet
	rrs := &route53v1alpha1.ResourceRecordSet{}
	err := s.KubeClient.Get(ctx, types.NamespacedName{Name: dnsRecordName}, rrs)
	if err != nil {
		if apierrors.IsNotFound(err) {
			// DNS record doesn't exist, so it's not modifying
			logger.Info("DNS record not found, not modifying")
			return false, nil
		}
		// Error getting DNS record
		logger.Error("Failed to get DNS record", zap.Error(err))
		return false, err
	}

	// Check if the resource is being deleted
	if rrs.DeletionTimestamp != nil {
		logger.Info("DNS record is being deleted, status is modifying")
		return true, nil
	}

	// This repository pauses Route53 records only after Ready=True and Synced=True.
	if meta.IsPaused(rrs) {
		logger.Info("DNS record is stably paused, not modifying")
		return false, nil
	}

	// Check the Ready and Synced conditions
	readyCondition := rrs.Status.GetCondition(xpv1.TypeReady)
	syncedCondition := rrs.Status.GetCondition(xpv1.TypeSynced)

	logger.Info("Check DNS record status", zap.String("readyStatus", string(readyCondition.Status)), zap.String("syncedStatus", string(syncedCondition.Status)))

	// If Ready condition is not True, the DNS record is still being reconciled
	if readyCondition.Status != corev1.ConditionTrue {
		logger.Info("DNS record Ready condition is not True, status is modifying",
			zap.String("readyStatus", string(readyCondition.Status)),
			zap.String("readyReason", string(readyCondition.Reason)))
		return true, nil
	}

	// If Synced condition is not True, the DNS record is still being reconciled
	if syncedCondition.Status != corev1.ConditionTrue {
		logger.Info("DNS record Synced condition is not True, status is modifying",
			zap.String("syncedStatus", string(syncedCondition.Status)),
			zap.String("syncedReason", string(syncedCondition.Reason)))
		return true, nil
	}

	// If both Ready and Synced are True, the DNS record is stable
	logger.Info("DNS record is stable, not modifying")
	return false, nil
}

// isDNSRecordModifyingAzure checks Azure DNSARecord modification status
func (s *TidbGroupServer) isDNSRecordModifyingAzure(ctx context.Context, dnsRecordName string, logger *zap.Logger) (bool, error) {
	// Try to get the DNSARecord
	dnsRecord := &azurenetworkv1beta1.DNSARecord{}
	err := s.KubeClient.Get(ctx, types.NamespacedName{Name: dnsRecordName}, dnsRecord)
	if err != nil {
		if apierrors.IsNotFound(err) {
			// DNS record doesn't exist, so it's not modifying
			logger.Info("DNS record not found, not modifying")
			return false, nil
		}
		// Error getting DNS record
		logger.Error("Failed to get DNS record", zap.Error(err))
		return false, err
	}

	// Check if the resource is being deleted
	if dnsRecord.DeletionTimestamp != nil {
		logger.Info("DNS record is being deleted, status is modifying")
		return true, nil
	}

	// Check the Ready and Synced conditions
	readyCondition := dnsRecord.Status.GetCondition(xpv1.TypeReady)
	syncedCondition := dnsRecord.Status.GetCondition(xpv1.TypeSynced)

	logger.Info("Check DNS record status", zap.String("readyStatus", string(readyCondition.Status)), zap.String("syncedStatus", string(syncedCondition.Status)))

	// If Ready condition is not True, the DNS record is still being reconciled
	if readyCondition.Status != corev1.ConditionTrue {
		logger.Info("DNS record Ready condition is not True, status is modifying",
			zap.String("readyStatus", string(readyCondition.Status)),
			zap.String("readyReason", string(readyCondition.Reason)))
		return true, nil
	}

	// If Synced condition is not True, the DNS record is still being reconciled
	if syncedCondition.Status != corev1.ConditionTrue {
		logger.Info("DNS record Synced condition is not True, status is modifying",
			zap.String("syncedStatus", string(syncedCondition.Status)),
			zap.String("syncedReason", string(syncedCondition.Reason)))
		return true, nil
	}

	// If both Ready and Synced are True, the DNS record is stable
	logger.Info("DNS record is stable, not modifying")
	return false, nil
}

// isDNSRecordNotFound checks if the DNS record does not exist (returns 404)
// if dnsRecordName is empty, return true
// otherwise, check if the DNS record does not exist (returns true if not found)
func (s *TidbGroupServer) isDNSRecordNotFound(ctx context.Context, dnsRecordName string, cluster *cloudv1.Cluster) (bool, error) {
	logger := tilog.WithContextL(ctx).With(zap.String("dnsRecordName", dnsRecordName), zap.String("provider", cluster.Spec.Kubernetes.Provider))

	if dnsRecordName == "" {
		return true, nil
	}

	switch cluster.Spec.Kubernetes.Provider {
	case consts.ProviderTypeAKS:
		return s.isDNSRecordNotFoundAzure(ctx, dnsRecordName, logger)
	case consts.ProviderTypeEKS, consts.ProviderTypeGKE:
		return s.isDNSRecordNotFoundAWS(ctx, dnsRecordName, logger)
	default:
		logger.Warn("Unsupported provider for DNS record checking", zap.String("provider", cluster.Spec.Kubernetes.Provider))
		return false, status.Errorf(codes.InvalidArgument, "unsupported provider for DNS record checking: %s", cluster.Spec.Kubernetes.Provider)
	}
}

// isDNSRecordNotFoundAWS checks if AWS Route53 ResourceRecordSet does not exist
func (s *TidbGroupServer) isDNSRecordNotFoundAWS(ctx context.Context, dnsRecordName string, logger *zap.Logger) (bool, error) {
	// Try to get the ResourceRecordSet
	rrs := &route53v1alpha1.ResourceRecordSet{}
	err := s.KubeClient.Get(ctx, types.NamespacedName{Name: dnsRecordName}, rrs)
	if err != nil {
		if apierrors.IsNotFound(err) {
			// DNS record doesn't exist, this is what we consider as 404
			logger.Info("DNS record not found (404)")
			return true, nil
		}
		// Error getting DNS record
		logger.Error("Failed to get DNS record", zap.Error(err))
		return false, err
	}

	// DNS record exists
	logger.Info("DNS record exists, not 404")
	return false, nil
}

// isDNSRecordNotFoundAzure checks if Azure DNSARecord does not exist
func (s *TidbGroupServer) isDNSRecordNotFoundAzure(ctx context.Context, dnsRecordName string, logger *zap.Logger) (bool, error) {
	// Try to get the DNSARecord
	dnsRecord := &azurenetworkv1beta1.DNSARecord{}
	err := s.KubeClient.Get(ctx, types.NamespacedName{Name: dnsRecordName}, dnsRecord)
	if err != nil {
		if apierrors.IsNotFound(err) {
			// DNS record doesn't exist, this is what we consider as 404
			logger.Info("DNS record not found (404)")
			return true, nil
		}
		// Error getting DNS record
		logger.Error("Failed to get DNS record", zap.Error(err))
		return false, err
	}

	// DNS record exists
	logger.Info("DNS record exists, not 404")
	return false, nil
}
