package framework

import (
	"fmt"

	"github.com/prometheus/client_golang/prometheus"
)

const (
	eventDataNameSharedPoolId = "SharedPoolId"
	eventDataNameActionType   = "ActionType"
)

var (
	autoscaleTiKVDecisionTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "autoscale_tikv_decision_total",
			Help: "Total TiKV capacity scaling decisions.",
		}, []string{"shared_pool_id", "decision"})

	autoscaleTiKVCooldownSkipTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "autoscale_tikv_cooldown_skip_total",
			Help: "Total TiKV scaling actions skipped due to cooldown.",
		}, []string{"shared_pool_id", "reason"})

	autoscaleTiKVActionTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name: "autoscale_tikv_action_total",
			Help: "Total TiKV capacity action outcomes.",
		}, []string{"shared_pool_id", "action", "result"})
)

func init() {
	prometheus.MustRegister(autoscaleTiKVDecisionTotal)
	prometheus.MustRegister(autoscaleTiKVCooldownSkipTotal)
	prometheus.MustRegister(autoscaleTiKVActionTotal)
}

type PrometheusEventReceiver struct{}

func NewPrometheusEventReceiver() *PrometheusEventReceiver {
	return &PrometheusEventReceiver{}
}

func (p *PrometheusEventReceiver) OnEvent(name EventName, data ...EventData) {
	poolID := extractDataValue(data, eventDataNameSharedPoolId)
	actionType := extractDataValue(data, eventDataNameActionType)

	switch name {
	case "ScalerTiKVCapacityV1.NoAction":
		autoscaleTiKVDecisionTotal.WithLabelValues(poolID, "no_action").Inc()

	case "ScalerTiKVCapacityV1.Disk.PathA":
		autoscaleTiKVDecisionTotal.WithLabelValues(poolID, "disk_path_a").Inc()

	case "ScalerTiKVCapacityV1.Disk.PathB":
		autoscaleTiKVDecisionTotal.WithLabelValues(poolID, "disk_path_b").Inc()

	case "ScalerTiKVCapacityV1.CPU.ScaleOut":
		autoscaleTiKVDecisionTotal.WithLabelValues(poolID, "cpu_scale_out").Inc()

	case "ScalerTiKVCapacityV1.IOPS.Expand":
		autoscaleTiKVDecisionTotal.WithLabelValues(poolID, "iops_expand").Inc()

	case "ScalerTiKVCapacityV1.Throughput.Expand":
		autoscaleTiKVDecisionTotal.WithLabelValues(poolID, "throughput_expand").Inc()

	case "ScalerTiKVCapacityV1.VolumeModify.Cooldown":
		autoscaleTiKVCooldownSkipTotal.WithLabelValues(poolID, "volume_modify").Inc()

	case "ScalerTiKVCapacityV1.Disk.PathB.Cooldown":
		autoscaleTiKVCooldownSkipTotal.WithLabelValues(poolID, "disk_path_b").Inc()

	case "ScalerTiKVCapacityV1.CPU.ScaleOut.Cooldown":
		autoscaleTiKVCooldownSkipTotal.WithLabelValues(poolID, "cpu_scale_out").Inc()

	case "OutputCapacityReservationV1.CreateReservation.WaitError":
		autoscaleTiKVActionTotal.WithLabelValues(poolID, actionType, "error").Inc()

	case "OutputCapacityReservationV1.CreateReservation.Success":
		switch actionType {
		case "add_nodes":
			autoscaleTiKVActionTotal.WithLabelValues(poolID, "add_nodes", "success").Inc()
		case "expand_disk":
			autoscaleTiKVActionTotal.WithLabelValues(poolID, "expand_disk", "fallback").Inc()
		}

	case "OutputCapacityReservationV1.CreateReservation.Error":
		autoscaleTiKVActionTotal.WithLabelValues(poolID, actionType, "error").Inc()

	case "OutputCapacityReservationV1.ExpandDisk.Success":
		autoscaleTiKVActionTotal.WithLabelValues(poolID, "expand_disk", "success").Inc()

	case "OutputCapacityReservationV1.ExpandDisk.Error":
		autoscaleTiKVActionTotal.WithLabelValues(poolID, "expand_disk", "error").Inc()

	case "OutputCapacityReservationV1.ExpandDisk.Fallback.CreateError":
		autoscaleTiKVActionTotal.WithLabelValues(poolID, "expand_disk", "error").Inc()

	case "OutputCapacityReservationV1.ExpandDisk.Fallback.WaitError":
		autoscaleTiKVActionTotal.WithLabelValues(poolID, "expand_disk", "error").Inc()

	case "OutputCapacityReservationV1.ExpandIOPS.Success":
		autoscaleTiKVActionTotal.WithLabelValues(poolID, "expand_iops", "success").Inc()

	case "OutputCapacityReservationV1.ExpandIOPS.Error":
		autoscaleTiKVActionTotal.WithLabelValues(poolID, "expand_iops", "error").Inc()

	case "OutputCapacityReservationV1.ExpandThroughput.Success":
		autoscaleTiKVActionTotal.WithLabelValues(poolID, "expand_throughput", "success").Inc()

	case "OutputCapacityReservationV1.ExpandThroughput.Error":
		autoscaleTiKVActionTotal.WithLabelValues(poolID, "expand_throughput", "error").Inc()
	}
}

func extractDataValue(data []EventData, name string) string {
	for _, d := range data {
		if d.DataName == name && d.Object != nil {
			return fmt.Sprintf("%v", d.Object)
		}
	}
	return ""
}

// ClearPoolMetrics removes every per-pool prometheus series owned by
// this package for the given sharedPoolID. Called from
// cmd/autoscale_engine/main.go's pool cancellation paths (discovery
// loop removed-pool branch, watchScaler unexpected-exit branch,
// stopAll) alongside scaler_tikv_capacity_v1.ClearPoolMetrics. The
// three CounterVec families above (decision_total,
// cooldown_skip_total, action_total) carry shared_pool_id as the
// first label; without this helper, removed-pool series accumulate
// forever in the client_golang registry (counter values do not GC
// even when no longer incremented). Cardinality budget is ~15
// series / pool (counter × bounded {decision, reason, action×result}).
//
// Safe to call on an unknown sharedPoolID — DeletePartialMatch is a
// noop when no matching series exists. Idempotent.
//
// Cleanup contract documented in AGENTS.md §Metrics design and the
// PR review checklist.
func ClearPoolMetrics(sharedPoolID string) {
	autoscaleTiKVDecisionTotal.DeletePartialMatch(prometheus.Labels{"shared_pool_id": sharedPoolID})
	autoscaleTiKVCooldownSkipTotal.DeletePartialMatch(prometheus.Labels{"shared_pool_id": sharedPoolID})
	autoscaleTiKVActionTotal.DeletePartialMatch(prometheus.Labels{"shared_pool_id": sharedPoolID})
}

// --- Test-only helpers ---
//
// The helpers below expose the underlying CounterVec Inc path so
// cross-package tests (cmd/autoscale_engine/main_test.go, etc.) can
// seed the framework counters without going through
// PrometheusEventReceiver.OnEvent. This keeps the cancellation-path
// integration tests decoupled from the framework event-name → label
// mapping, which is exercised by TestPrometheusEventReceiver_* in
// this same package (pkg/autoscale/framework/).
//
// Production code MUST NOT call these helpers. Use
// PrometheusEventReceiver.OnEvent instead. The "ForTest" suffix
// makes accidental misuse grep-able.

// DecisionTotalForTest increments autoscale_tikv_decision_total for
// (sharedPoolID, decision). Test-only.
func DecisionTotalForTest(sharedPoolID, decision string) prometheus.Counter {
	return autoscaleTiKVDecisionTotal.WithLabelValues(sharedPoolID, decision)
}

// CooldownSkipTotalForTest increments
// autoscale_tikv_cooldown_skip_total for (sharedPoolID, reason).
// Test-only.
func CooldownSkipTotalForTest(sharedPoolID, reason string) prometheus.Counter {
	return autoscaleTiKVCooldownSkipTotal.WithLabelValues(sharedPoolID, reason)
}

// ActionTotalForTest increments autoscale_tikv_action_total for
// (sharedPoolID, action, result). Test-only.
func ActionTotalForTest(sharedPoolID, action, result string) prometheus.Counter {
	return autoscaleTiKVActionTotal.WithLabelValues(sharedPoolID, action, result)
}
