package nodegroup

import (
	"fmt"
	"strconv"
	"time"
)

// Label keys used for node instance group management.
// These follow the Kubernetes label naming convention with tidbcloud.com domain.
const (
	// LabelNodeSpec is the spec label (e.g., "8C16G")
	LabelNodeSpec = "tidbcloud.com/node-spec"

	// LabelPriority is the scheduling priority (higher = preferred)
	LabelPriority = "core.pingcap.com/priority"

	// LabelDeprecationPhase is the deprecation phase (soft, hard)
	LabelDeprecationPhase = "tidbcloud.com/deprecation-phase"

	// LabelDeprecatedAt is the unix timestamp when deprecation started
	LabelDeprecatedAt = "tidbcloud.com/deprecated-at"

	// LabelShortageDetectedAt is the unix timestamp when shortage was detected
	LabelShortageDetectedAt = "tidbcloud.com/shortage-detected-at"

	// LabelRealNodeSpec is the real spec of the node (for fallback node pools)
	LabelRealNodeSpec = "tidbcloud.com/real-node-spec"

	// LabelManagedBy indicates who manages this node instance group
	LabelManagedBy = "tidbcloud.com/managed-by"

	// ManagedByInstanceTypeManager is the value for LabelManagedBy
	ManagedByInstanceTypeManager = "node-instance-group-manager"
	// legacyManagedByValue is the old value for LabelManagedBy, kept for backward compatibility.
	legacyManagedByValue = "instance-type-manager"
)

// Priority values for node instance groups.
// Lower priority nodes are scaled down first by TiDB Operator.
// All values must be valid K8s label values (alphanumeric, no negative signs).
const (
	PriorityPrimary  = 1000
	PriorityFallback = 100
)

// InstanceTypeSpec contains all information about an instance type.
type InstanceTypeSpec struct {
	Spec              string // e.g., "8C16G"
	InstanceType      string // e.g., "m5.2xlarge"
	AvailableCPU      string // e.g., "7" (cores)
	AvailableMemoryMi string // e.g., "15360" (MiB)
	Priority          uint32 // scheduling priority (>= 1000 for primary, < 1000 for fallback)
}

// BuildPrimaryLabels builds labels for a primary node instance group.
// Priority must be >= 1000. If not set (0), defaults to 1000.
func BuildPrimaryLabels(spec *InstanceTypeSpec) map[string]string {
	priority := spec.Priority
	if priority == 0 {
		priority = PriorityPrimary
	}
	return map[string]string{
		LabelNodeSpec:  spec.Spec,
		LabelPriority:  strconv.FormatUint(uint64(priority), 10),
		LabelManagedBy: ManagedByInstanceTypeManager,
	}
}

// BuildFallbackLabels builds labels for a fallback node instance group.
// CRITICAL: LabelNodeSpec must use primarySpec (not fallbackSpec) so that pods
// selecting the primary spec can be scheduled onto fallback nodes during shortage.
func BuildFallbackLabels(fallbackSpec *InstanceTypeSpec, primarySpec string, priority uint32) map[string]string {
	labels := map[string]string{
		LabelNodeSpec:           primarySpec, // Use PRIMARY spec for pod scheduling compatibility
		LabelPriority:           strconv.FormatUint(uint64(priority), 10),
		LabelManagedBy:          ManagedByInstanceTypeManager,
		LabelShortageDetectedAt: fmt.Sprintf("%d", time.Now().Unix()),
		LabelRealNodeSpec:       fallbackSpec.Spec,
	}
	return labels
}

// AddDeprecationLabels adds deprecation metadata labels to existing labels.
// Returns a new map with merged labels (does not modify the input).
func AddDeprecationLabels(existingLabels map[string]string, phase string) map[string]string {
	result := MergeLabels(existingLabels, nil)
	result[LabelDeprecationPhase] = phase
	result[LabelDeprecatedAt] = fmt.Sprintf("%d", time.Now().Unix())
	return result
}

// MergeLabels merges two label maps, with newLabels taking precedence.
// Returns a new map without modifying the inputs.
// This is CRITICAL for UpdateNodeInstanceGroup which uses replace semantics.
func MergeLabels(existing, newLabels map[string]string) map[string]string {
	result := make(map[string]string, len(existing)+len(newLabels))

	// Copy existing labels
	for k, v := range existing {
		result[k] = v
	}

	// Override with new labels
	for k, v := range newLabels {
		result[k] = v
	}

	return result
}

// HasLabel checks if the labels contain a specific key-value pair.
func HasLabel(labels map[string]string, key, value string) bool {
	if labels == nil {
		return false
	}
	v, ok := labels[key]
	return ok && v == value
}

// IsManagedByInstanceTypeManager checks if the node instance group is managed by instance type manager.
// Supports both new and legacy label values.
func IsManagedByInstanceTypeManager(labels map[string]string) bool {
	return HasLabel(labels, LabelManagedBy, ManagedByInstanceTypeManager) ||
		HasLabel(labels, LabelManagedBy, legacyManagedByValue)
}

// IsDeprecated checks if the node instance group has deprecation labels.
func IsDeprecated(labels map[string]string) bool {
	if labels == nil {
		return false
	}
	_, hasPhase := labels[LabelDeprecationPhase]
	return hasPhase
}

// ValidatePriority validates the priority value based on instance type role.
// Primary instance types must have priority >= 1000 (default 1000).
// Fallback instance types must have priority < 1000.
func ValidatePriority(priority uint32, isPrimary bool) error {
	if isPrimary {
		if priority > 0 && priority < PriorityPrimary {
			return fmt.Errorf("primary instance priority must be >= %d, got %d", PriorityPrimary, priority)
		}
	} else {
		if priority >= PriorityPrimary {
			return fmt.Errorf("fallback instance priority must be < %d, got %d", PriorityPrimary, priority)
		}
	}
	return nil
}
