package metrics

import (
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"

	utilmetrics "github.com/karmada-io/karmada/pkg/util/metrics"
)

// SchedulerSubsystem - subsystem name used by scheduler
const SchedulerSubsystem = "karmada_scheduler"

const (
	scheduledResult = "scheduled"
	errorResult     = "error"
)

const (
	// BindingAdd is the event when a new binding is added to API server.
	BindingAdd = "BindingAdd"
	// BindingUpdate is the event when a new binding is updated to API server.
	BindingUpdate = "BindingUpdate"
	// ScheduleAttemptFailure is the event when a schedule attempt fails.
	ScheduleAttemptFailure = "ScheduleAttemptFailure"
	// PolicyChanged means binding needs to be rescheduled for the policy changed
	PolicyChanged = "PolicyChanged"
	// ClusterNotReady means binding needs to be rescheduled for cluster is not ready
	ClusterNotReady = "ClusterNotReady"
)

const (
	// ScheduleStepFilter means the step in generic scheduler to filter clusters
	ScheduleStepFilter = "Filter"
	// ScheduleStepScore means the step in generic scheduler to score clusters
	ScheduleStepScore = "Score"
	// ScheduleStepSelect means the step in generic scheduler to select clusters
	ScheduleStepSelect = "Select"
	// ScheduleStepAssignReplicas means the step in generic scheduler to assign replicas
	ScheduleStepAssignReplicas = "AssignReplicas"
)

var (
	scheduleAttempts = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Subsystem: SchedulerSubsystem,
			Name:      "schedule_attempts_total",
			Help:      "Number of attempts to schedule resourceBinding",
		}, []string{"result", "schedule_type"})

	e2eSchedulingLatency = promauto.NewHistogramVec(
		prometheus.HistogramOpts{
			Subsystem: SchedulerSubsystem,
			Name:      "e2e_scheduling_duration_seconds",
			Help:      "E2e scheduling latency in seconds",
			Buckets:   prometheus.ExponentialBuckets(0.001, 2, 15),
		}, []string{"result", "schedule_type"})

	schedulingAlgorithmLatency = promauto.NewHistogramVec(
		prometheus.HistogramOpts{
			Subsystem: SchedulerSubsystem,
			Name:      "scheduling_algorithm_duration_seconds",
			Help:      "Scheduling algorithm latency in seconds(exclude scale scheduler)",
			Buckets:   prometheus.ExponentialBuckets(0.001, 2, 15),
		}, []string{"schedule_step"})

	schedulerQueueIncomingBindings = promauto.NewCounterVec(
		prometheus.CounterOpts{
			Subsystem: SchedulerSubsystem,
			Name:      "queue_incoming_bindings_total",
			Help:      "Number of bindings added to scheduling queues by event type.",
		}, []string{"event"})
)

// BindingSchedule can record a scheduling attempt and the duration
// since `start`.
func BindingSchedule(scheduleType string, duration float64, err error) {
	if err != nil {
		observeScheduleAttemptAndLatency(errorResult, scheduleType, duration)
	} else {
		observeScheduleAttemptAndLatency(scheduledResult, scheduleType, duration)
	}
}

func observeScheduleAttemptAndLatency(result, scheduleType string, duration float64) {
	e2eSchedulingLatency.WithLabelValues(result, scheduleType).Observe(duration)
	scheduleAttempts.WithLabelValues(result, scheduleType).Inc()
}

// ScheduleStep can record each scheduling step duration.
func ScheduleStep(action string, startTime time.Time) {
	schedulingAlgorithmLatency.WithLabelValues(action).Observe(utilmetrics.DurationInSeconds(startTime))
}

// CountSchedulerBindings records the number of binding added to scheduling queues by event type.
func CountSchedulerBindings(event string) {
	schedulerQueueIncomingBindings.WithLabelValues(event).Inc()
}
