package common

import (
	"fmt"
	"sync"
	"time"

	"github.com/tidbcloud/cluster-service-ng/pkg/autoscale/framework"
)

type ScaleDecision int

const (
	ScaleDecisionNone ScaleDecision = iota
	ScaleDecisionOut
	ScaleDecisionIn
)

type BaseScalerConfig struct {
	ScaleOutStep    uint `yaml:"scaleOutStep"`
	ScaleInStep     uint `yaml:"scaleInStep"`
	MaxReplicas     uint `yaml:"maxReplicas"`
	MinReplicas     uint `yaml:"minReplicas"`
	CooldownMinutes uint `yaml:"cooldownMinutes"`
}

// FillBlankValuesFrom fills zero-valued fields from another config.
// Note: A value of 0 is treated as "not set" and will be replaced by the other's value.
// This is acceptable because 0 is an invalid value for these fields (e.g., minReplicas=0 is not allowed).
func (c *BaseScalerConfig) FillBlankValuesFrom(other *BaseScalerConfig) {
	if other == nil {
		return
	}
	if c.ScaleOutStep == 0 {
		c.ScaleOutStep = other.ScaleOutStep
	}
	if c.ScaleInStep == 0 {
		c.ScaleInStep = other.ScaleInStep
	}
	if c.MaxReplicas == 0 {
		c.MaxReplicas = other.MaxReplicas
	}
	if c.MinReplicas == 0 {
		c.MinReplicas = other.MinReplicas
	}
	if c.CooldownMinutes == 0 {
		c.CooldownMinutes = other.CooldownMinutes
	}
}

func (c *BaseScalerConfig) BaseUniqStr() string {
	return fmt.Sprintf("out=%d,in=%d,max=%d,min=%d,cooldown=%dm",
		c.ScaleOutStep, c.ScaleInStep, c.MaxReplicas, c.MinReplicas, c.CooldownMinutes)
}

var DefaultBaseScalerConfig = &BaseScalerConfig{
	ScaleOutStep:    1,
	ScaleInStep:     1,
	MaxReplicas:     100,
	MinReplicas:     1,
	CooldownMinutes: 5,
}

type BaseScaler struct {
	eventReceiver framework.EventReceiver
	clusterId     framework.ClusterId
	componentName framework.ComponentName
	input         framework.InputInterfaceV1
	output        framework.OutputInterfaceV1
	config        *BaseScalerConfig
	lastScaleTime time.Time
	mu            sync.RWMutex
}

func NewBaseScaler(
	eventReceiver framework.EventReceiver,
	clusterId framework.ClusterId,
	componentName framework.ComponentName,
	config *BaseScalerConfig,
	input framework.InputInterfaceV1,
	output framework.OutputInterfaceV1,
) *BaseScaler {
	if config == nil {
		config = DefaultBaseScalerConfig
	}
	return &BaseScaler{
		eventReceiver: eventReceiver,
		clusterId:     clusterId,
		componentName: componentName,
		input:         input,
		output:        output,
		config:        config,
	}
}

func (b *BaseScaler) IsCooldownExpired() bool {
	b.mu.RLock()
	defer b.mu.RUnlock()
	if b.lastScaleTime.IsZero() {
		return true
	}
	return time.Since(b.lastScaleTime) >= time.Duration(b.config.CooldownMinutes)*time.Minute
}

func (b *BaseScaler) CalculateScaleOutReplicas(currentReplicas uint) uint {
	newReplicas := currentReplicas + b.config.ScaleOutStep
	if newReplicas > b.config.MaxReplicas {
		newReplicas = b.config.MaxReplicas
	}
	return newReplicas
}

func (b *BaseScaler) CalculateScaleInReplicas(currentReplicas uint) uint {
	if currentReplicas <= b.config.ScaleInStep {
		return b.config.MinReplicas
	}
	newReplicas := currentReplicas - b.config.ScaleInStep
	if newReplicas < b.config.MinReplicas {
		newReplicas = b.config.MinReplicas
	}
	return newReplicas
}

func (b *BaseScaler) UpdateLastScaleTime() {
	b.mu.Lock()
	defer b.mu.Unlock()
	b.lastScaleTime = time.Now()
}

func (b *BaseScaler) CanScaleOut(currentReplicas uint) bool {
	return currentReplicas < b.config.MaxReplicas
}

func (b *BaseScaler) CanScaleIn(currentReplicas uint) bool {
	return currentReplicas > b.config.MinReplicas
}

func (b *BaseScaler) GetInput() framework.InputInterfaceV1 {
	return b.input
}

func (b *BaseScaler) GetClusterId() framework.ClusterId {
	return b.clusterId
}

func (b *BaseScaler) GetComponentName() framework.ComponentName {
	return b.componentName
}

func (b *BaseScaler) GetEventReceiver() framework.EventReceiver {
	return b.eventReceiver
}
