// Package replicas defines horizontal replica calculation contracts.
package replicas

import (
	"fmt"
	"math"

	"github.com/tidbcloud/cluster-service-ng/internal/capacity-planner/pkg/metrics"
	"github.com/tidbcloud/cluster-service-ng/internal/capacity-planner/pkg/tier"
)

const maxInt32 = int64(1<<31 - 1)

// Calculator calculates the replica count needed for a tier from utilization
// metrics. It is a pure calculation boundary: metric reads, replica bounds,
// stabilization and scaling execution are handled outside this package.
type Calculator interface {
	Calculate(metric *metrics.Metrics, candidateTier tier.Tier) (int32, error)
}

// TargetUtilizationCalculator implements Kubernetes HPA-style target average
// utilization calculations for a single candidate tier.
type TargetUtilizationCalculator struct {
	resourceName      string
	targetUtilization float64
}

var _ Calculator = (*TargetUtilizationCalculator)(nil)

// NewTargetUtilizationCalculator creates a calculator for one resource and
// target average utilization percentage.
func NewTargetUtilizationCalculator(resourceName string, targetAverageUtilization int32) (*TargetUtilizationCalculator, error) {
	if resourceName == "" {
		return nil, fmt.Errorf("replicas: resource name is required")
	}
	if targetAverageUtilization <= 0 {
		return nil, fmt.Errorf("replicas: target average utilization must be greater than zero")
	}
	return &TargetUtilizationCalculator{
		resourceName:      resourceName,
		targetUtilization: float64(targetAverageUtilization) / 100,
	}, nil
}

func (c *TargetUtilizationCalculator) Calculate(metric *metrics.Metrics, candidateTier tier.Tier) (int32, error) {
	capacity, ok := candidateTier.Capacity(c.resourceName)
	if !ok {
		return 0, fmt.Errorf("replicas: tier resource %q not found", c.resourceName)
	}
	capacityValue := capacity.AsApproximateFloat64()
	if capacityValue <= 0 {
		return 0, fmt.Errorf("replicas: tier %q resource %q capacity must be greater than zero", candidateTier.Name(), c.resourceName)
	}

	return calculateMetric(metric, capacityValue, c.targetUtilization)
}

func calculateMetric(metric *metrics.Metrics, capacity float64, targetUtilization float64) (int32, error) {
	total := float64(0)
	for i := range metric.Series {
		series := &metric.Series[i]
		if len(series.Samples) == 0 {
			continue
		}
		value := series.Samples[len(series.Samples)-1].Value
		if math.IsNaN(value) || math.IsInf(value, 0) || value < 0 {
			return 0, fmt.Errorf("replicas: series[%d] latest sample must be a finite non-negative value", i)
		}
		total += value
	}

	desiredReplicas := math.Ceil(total / (capacity * targetUtilization))
	if desiredReplicas > float64(maxInt32) {
		return 0, fmt.Errorf("replicas: metric recommends %.0f replicas, exceeds int32", desiredReplicas)
	}

	return int32(desiredReplicas), nil
}
