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/autoscale/resolve"
	schemav1 "github.com/tidbcloud/cluster-service-ng/pkg/storage/etcd/schema/v1"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/proto"
)

// resolveSharedPoolRuConfig resolves the exact tenant/default configuration
// frozen into a newly created shared pool. Creation-option reads use the same
// resolver so they cannot advertise shapes or counts that create would reject
// after resolving a different configuration.
func (s *serverImpl) resolveSharedPoolRuConfig(ctx context.Context, tenantID string) (*entity.RuConfig, error) {
	ruConfig, found, err := s.ruConfigRepo.GetRuConfigWithFound(ctx, tenantID, apiv1.RuConfig_TENANT)
	if err != nil {
		tilog.Error(ctx, "failed to get ru config", zap.String("tenantId", tenantID), zap.Error(err))
		return nil, status.Errorf(codes.Internal, "failed to get ru config: %s", err)
	}
	// Preserve the v0.33.1 whole-row contract while policy is disabled. The
	// repository already returns the server default when the tenant row is
	// absent, so there must be no per-field merge or bundle read here.
	if s.bizCfg == nil || !s.bizCfg.AutoscalePolicy.IsEnforced() || tenantID == schemav1.MultiTenantTag {
		return stripYAMLSourcedTiProxyScaleIn(ruConfig, !found), nil
	}
	if s.autoscalePolicyBundleRepo == nil {
		return nil, status.Error(codes.Internal, "autoscale policy bundle repository is unavailable while policy is enforced")
	}

	serverDefault, err := s.ruConfigRepo.GetServerDefaultRuConfig(ctx)
	if err != nil {
		tilog.Error(ctx, "failed to get server default ru config", zap.Error(err))
		return nil, status.Errorf(codes.Internal, "failed to get server default ru config: %s", err)
	}

	resolvedRuConfig, err := resolve.PriorityResolveForSharedPoolFromBundle(
		ctx, s.autoscalePolicyBundleRepo, tenantID, serverDefault)
	if err != nil {
		tilog.Error(ctx, "failed to resolve shared-pool ru config from bundle table",
			zap.String("tenantId", tenantID), zap.Error(err))
		return nil, status.Errorf(codes.Internal, "failed to resolve shared-pool ru config from bundle table: %s", err)
	}

	// Components and these feature flags are stored in the tenant RuConfig,
	// outside the autoscale-policy bundle resolution.
	if ruConfig != nil && ruConfig.FeatureFlags != nil {
		if resolvedRuConfig.FeatureFlags == nil {
			resolvedRuConfig.FeatureFlags = &entity.RuConfigFeatureFlags{}
		}
		resolvedRuConfig.FeatureFlags.EnableTici = ruConfig.FeatureFlags.EnableTici
		resolvedRuConfig.FeatureFlags.EnableResourceManager = ruConfig.FeatureFlags.EnableResourceManager
	}
	if ruConfig != nil && len(ruConfig.GetComponents()) > 0 {
		resolvedRuConfig.Components = ruConfig.GetComponents()
	}

	return resolvedRuConfig, nil
}

// stripYAMLSourcedTiProxyScaleIn clears enable_autoscale_tiproxy_scale_in
// when it comes from YAML fallback. That flag is a per-pool HPA overlay in
// disabled mode; freezing the region YAML default into every new pool would
// make one YAML typo flip every subsequent TiProxy HPA. Clone before mutating.
func stripYAMLSourcedTiProxyScaleIn(ruConfig *entity.RuConfig, yamlFallback bool) *entity.RuConfig {
	if !yamlFallback || ruConfig == nil {
		return ruConfig
	}
	ff := ruConfig.GetFeatureFlags()
	if ff == nil || ff.EnableAutoscaleTiproxyScaleIn == nil {
		return ruConfig
	}
	cloned := &entity.RuConfig{}
	proto.Merge(&cloned.RuConfig, &ruConfig.RuConfig)
	if cloned.FeatureFlags != nil {
		cloned.FeatureFlags.EnableAutoscaleTiproxyScaleIn = nil
	}
	return cloned
}
