package server

import (
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
)

// RcuScaleScope distinguishes shared-pool-level from per-cluster-level scaling.
type RcuScaleScope int

const (
	// RcuScaleScopeSharedPool means the component uses the aggregated RU
	// across all TiDB clusters in the shared pool.
	RcuScaleScopeSharedPool RcuScaleScope = iota

	// RcuScaleScopePerCluster means the component uses each individual
	// TiDB cluster's RU.
	RcuScaleScopePerCluster
)

// RcuScaleOverride is an optional predicate that gates whether a scale
// action should proceed.  It receives the current and target NodeSpec and
// returns true when scaling is warranted.  A nil override means the
// generic needsUpgrade / needsDowngrade logic applies unconditionally.
type RcuScaleOverride func(current, target *entity.NodeSpec) bool

// RcuScaleComponentConfig describes one component that participates in
// REQ-1 (scale-up) and REQ-2 (scale-down) RCU-based autoscaling.
type RcuScaleComponentConfig struct {
	ComponentType apiv1.ComponentType
	Scope         RcuScaleScope
	// ScaleOverride, when non-nil, is checked *in addition to* the generic
	// upgrade/downgrade comparison.  Both the generic check and the override
	// must agree for the scale action to proceed.
	ScaleOverride RcuScaleOverride
}

// GetRcuScaleComponents returns the authoritative whitelist of components that
// participate in RCU-based spec scaling (REQ-1 / REQ-2).
//
// To add or remove a component from RCU scaling, edit this function.
// To remove TiKV's special CPU-change-only rule, set its ScaleOverride to nil.
func GetRcuScaleComponents() []RcuScaleComponentConfig {
	return []RcuScaleComponentConfig{
		{ComponentType: apiv1.ComponentType_TIKV, Scope: RcuScaleScopeSharedPool, ScaleOverride: tikvCPUChangeOnly},
		{ComponentType: apiv1.ComponentType_TIDB, Scope: RcuScaleScopePerCluster},
		{ComponentType: apiv1.ComponentType_TIKV_WORKER, Scope: RcuScaleScopeSharedPool},
		{ComponentType: apiv1.ComponentType_TIDB_WORKER, Scope: RcuScaleScopeSharedPool},
		{ComponentType: apiv1.ComponentType_COPROCESSOR_WORKER, Scope: RcuScaleScopeSharedPool},
	}
}

// tikvCPUChangeOnly is the TiKV-specific override: only trigger scaling when
// the CPU dimension actually changes.  This prevents 8c16g ↔ 8c32g transitions
// (same CPU, different memory) while still allowing 8c → 16c transitions.
func tikvCPUChangeOnly(current, target *entity.NodeSpec) bool {
	return current.GetCpu() != target.GetCpu()
}

// rcuScaleComponentSet builds a quick-lookup set from the whitelist.
func rcuScaleComponentSet() map[apiv1.ComponentType]RcuScaleComponentConfig {
	components := GetRcuScaleComponents()
	m := make(map[apiv1.ComponentType]RcuScaleComponentConfig, len(components))
	for _, cfg := range components {
		m[cfg.ComponentType] = cfg
	}
	return m
}

// componentSpecKey identifies a specific component instance.
type componentSpecKey struct {
	compType apiv1.ComponentType
	id       string
}

// snapshotComponentSpecs captures the current NodeSpec of every component in
// the shared pool.  Call this before generatedSharedPoolComponent so that
// ScaleOverride functions can compare old vs new specs.
// We store plain value copies to avoid proto message copy-lock issues.
func snapshotComponentSpecs(sp *entity.SharedPool) map[componentSpecKey]*entity.NodeSpec {
	m := make(map[componentSpecKey]*entity.NodeSpec, len(sp.GetComponentGroups()))
	for _, comp := range sp.GetComponentGroups() {
		if comp.GetNodeQuota() != nil && comp.GetNodeQuota().GetNodeSpec() != nil {
			spec := comp.GetNodeQuota().GetNodeSpec()
			m[componentSpecKey{compType: comp.GetComponentType(), id: comp.GetId()}] = &entity.NodeSpec{
				Cpu:      spec.GetCpu(),
				MemoryGi: spec.GetMemoryGi(),
			}
		}
	}
	return m
}
