// Package metrics owns Prometheus metrics emitted by the capacity planner
// itself. Decision metric sources belong in pkg/metrics.
package metrics

import (
	"errors"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	ctrlmetrics "sigs.k8s.io/controller-runtime/pkg/metrics"
)

const (
	// Namespace is the Prometheus namespace used by this component's custom
	// metrics collectors.
	Namespace = "capacity_planner"
	// ReconcilerSubsystem groups metrics emitted by the Autoscaler reconciler.
	ReconcilerSubsystem = "reconciler"

	ReconcileResultSuccess = "success"
	ReconcileResultError   = "error"
	ReconcileResultUnknown = "unknown"
)

// Collectors groups this component's custom Prometheus collectors.
type Collectors struct {
	ReconcileRunsTotal       *prometheus.CounterVec
	ReconcileDurationSeconds *prometheus.HistogramVec
}

// NewCollectors creates the capacity planner metrics collectors without registering
// them. Call Register or MustRegister to attach them to a registry.
func NewCollectors() *Collectors {
	return &Collectors{
		ReconcileRunsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{
			Namespace: Namespace,
			Subsystem: ReconcilerSubsystem,
			Name:      "runs_total",
			Help:      "Total number of Autoscaler reconcile attempts by result.",
		}, []string{"result"}),
		ReconcileDurationSeconds: prometheus.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: Namespace,
			Subsystem: ReconcilerSubsystem,
			Name:      "duration_seconds",
			Help:      "Autoscaler reconcile duration in seconds by result.",
			Buckets:   prometheus.DefBuckets,
		}, []string{"result"}),
	}
}

// Register registers the collectors with reg.
func (c *Collectors) Register(reg prometheus.Registerer) error {
	if c == nil {
		return errors.New("metrics: nil collectors")
	}
	if reg == nil {
		return errors.New("metrics: nil registerer")
	}
	return errors.Join(
		reg.Register(c.ReconcileRunsTotal),
		reg.Register(c.ReconcileDurationSeconds),
	)
}

// ObserveReconcile records one reconcile attempt.
func (c *Collectors) ObserveReconcile(result string, duration time.Duration) {
	if c == nil {
		return
	}
	if result == "" {
		result = ReconcileResultUnknown
	}
	c.ReconcileRunsTotal.WithLabelValues(result).Inc()
	c.ReconcileDurationSeconds.WithLabelValues(result).Observe(duration.Seconds())
}

// Register creates and registers collectors with reg.
func Register(reg prometheus.Registerer) (*Collectors, error) {
	c := NewCollectors()
	return c, c.Register(reg)
}

// MustRegister creates and registers collectors with reg. It panics on
// registration failure, matching prometheus.MustRegister semantics.
func MustRegister(reg prometheus.Registerer) *Collectors {
	c, err := Register(reg)
	if err != nil {
		panic(err)
	}
	return c
}

// RegisterControllerRuntime registers collectors with controller-runtime's
// metrics registry.
func RegisterControllerRuntime() (*Collectors, error) {
	return Register(ctrlmetrics.Registry)
}

// MustRegisterControllerRuntime is RegisterControllerRuntime with panic-on-error
// semantics.
func MustRegisterControllerRuntime() *Collectors {
	c, err := RegisterControllerRuntime()
	if err != nil {
		panic(err)
	}
	return c
}
