package server

import (
	"context"

	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/metricsprovider"
	schemav1 "github.com/tidbcloud/cluster-service-ng/pkg/storage/etcd/schema/v1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	autoscalingv2 "k8s.io/api/autoscaling/v2"
	"k8s.io/apimachinery/pkg/labels"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func (s *serverImpl) GetRuConfig(ctx context.Context, id string, scope entity.RuConfigScope) (*entity.RuConfig, error) {
	if scope == apiv1.RuConfig_SCOPE_UNSPECIFIED {
		return nil, status.Errorf(codes.InvalidArgument, "ru config scope is unspecified")
	}

	ruConfig, err := s.ruConfigRepo.GetRuConfig(ctx, id, scope)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get ru config: %s", err)
	}
	return ruConfig, nil
}

func (s *serverImpl) SetRuConfig(ctx context.Context, id string, scope entity.RuConfigScope, ruConfig *entity.RuConfig) (*entity.RuConfig, error) {
	if scope == apiv1.RuConfig_SCOPE_UNSPECIFIED {
		return nil, status.Errorf(codes.InvalidArgument, "ru config scope is unspecified")
	}
	if scope == apiv1.RuConfig_SHARED_POOL {
		if err := s.guardSharedPoolMutationByID(ctx, id, sharedPoolMutationBlockedWhilePaused); err != nil {
			return nil, err
		}
	}
	shouldSyncTiProxyScaleIn, err := s.shouldSyncTiProxyScaleInGateFromRuConfig(ctx, id, scope, ruConfig)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to check TiProxy scale-in sync: %s", err)
	}
	// Sync live HPAs before persisting so a failed dataplane update does not
	// leave the row ahead of the cluster. Retry then still sees a delta.
	if shouldSyncTiProxyScaleIn {
		if err := s.syncTiProxyScaleInGateFromRuConfig(ctx, id, scope, ruConfig); err != nil {
			return nil, err
		}
	}

	err = s.ruConfigRepo.SetRuConfig(ctx, id, scope, ruConfig)
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to set ru config: %s", err)
	}
	return ruConfig, nil
}

func (s *serverImpl) shouldSyncTiProxyScaleInGateFromRuConfig(
	ctx context.Context,
	id string,
	scope entity.RuConfigScope,
	ruConfig *entity.RuConfig,
) (bool, error) {
	nextFlag := tiProxyScaleInFlagFromRuConfig(ruConfig)
	if scope != apiv1.RuConfig_SHARED_POOL || nextFlag == nil {
		return false, nil
	}

	prevRuConfig, found, err := s.ruConfigRepo.GetRuConfigWithFound(ctx, id, scope)
	if err != nil {
		return false, err
	}
	if !found {
		return true, nil
	}
	prevFlag := tiProxyScaleInFlagFromRuConfig(prevRuConfig)
	return prevFlag == nil || *prevFlag != *nextFlag, nil
}

func tiProxyScaleInFlagFromRuConfig(ruConfig *entity.RuConfig) *bool {
	if ruConfig == nil || ruConfig.GetFeatureFlags() == nil {
		return nil
	}
	return ruConfig.GetFeatureFlags().EnableAutoscaleTiproxyScaleIn
}

func (s *serverImpl) syncTiProxyScaleInGateFromRuConfig(
	ctx context.Context,
	id string,
	scope entity.RuConfigScope,
	ruConfig *entity.RuConfig,
) error {
	if scope != apiv1.RuConfig_SHARED_POOL || ruConfig == nil ||
		ruConfig.GetFeatureFlags() == nil ||
		ruConfig.GetFeatureFlags().EnableAutoscaleTiproxyScaleIn == nil {
		return nil
	}
	if s.sharedPoolRepo == nil {
		return status.Error(codes.Internal, "sharedPoolRepo is unavailable while syncing TiProxy scale-in")
	}

	scaleDownEnabled := ruConfig.GetFeatureFlags().EnableAutoscaleTiproxyScaleIn
	sharedPool, err := s.sharedPoolRepo.Load(ctx, id, false)
	if err != nil {
		return status.Errorf(codes.Internal, "failed to load shared pool for TiProxy scale-in sync: %s", err)
	}
	if sharedPool == nil {
		return status.Errorf(codes.NotFound, "shared pool %s not found while syncing TiProxy scale-in", id)
	}
	if !s.tiProxyScaleInFlagAppliesToHPA(sharedPool) {
		return nil
	}
	if s.dataplaneManager == nil {
		return status.Error(codes.Internal, "dataplaneManager is unavailable while syncing TiProxy scale-in")
	}
	remoteCli, err := s.dataplaneManager.GetClient(sharedPool.GetDataplaneName())
	if err != nil {
		return status.Errorf(codes.Internal, "failed to get remote client for TiProxy scale-in sync: %s", err)
	}

	hpas := new(autoscalingv2.HorizontalPodAutoscalerList)
	if err := remoteCli.List(ctx, hpas, &client.ListOptions{
		Namespace: "tidb" + sharedPool.GetId(),
		LabelSelector: labels.SelectorFromSet(map[string]string{
			schemav1.LabelComponentKey: componentLabelForHPA(apiv1.ComponentType_TIPROXY),
		}),
	}); err != nil {
		return status.Errorf(codes.Internal, "failed to list TiProxy HPAs for scale-in sync: %s", err)
	}

	for i := range hpas.Items {
		hpa := &hpas.Items[i]
		if err := updateHPAWithRetry(ctx, remoteCli, hpa,
			apiv1.ComponentType_TIPROXY, nil, scaleDownEnabled,
			metricsprovider.TiProxyCPUScaleInGateConfig); err != nil {
			return status.Errorf(codes.Internal,
				"failed to sync TiProxy HPA %s scale-in from ru config: %s", hpa.GetName(), err)
		}
	}
	return nil
}

func (s *serverImpl) tiProxyScaleInFlagAppliesToHPA(sharedPool *entity.SharedPool) bool {
	if s.bizCfg == nil || !s.bizCfg.AutoscalePolicy.IsEnforced() {
		return true
	}
	tenantID, err := entity.ResolveSharedPoolTenantID(sharedPool)
	return err == nil && tenantID == schemav1.MultiTenantTag
}
