package autorelay

import (
	

	
	
	pbv2 
	
)

const metricNamespace = "libp2p_autorelay"

var (
	status = prometheus.NewGauge(prometheus.GaugeOpts{
		Namespace: metricNamespace,
		Name:      "status",
		Help:      "relay finder active",
	})
	reservationsOpenedTotal = prometheus.NewCounter(
		prometheus.CounterOpts{
			Namespace: metricNamespace,
			Name:      "reservations_opened_total",
			Help:      "Reservations Opened",
		},
	)
	reservationsClosedTotal = prometheus.NewCounter(
		prometheus.CounterOpts{
			Namespace: metricNamespace,
			Name:      "reservations_closed_total",
			Help:      "Reservations Closed",
		},
	)
	reservationRequestsOutcomeTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: metricNamespace,
			Name:      "reservation_requests_outcome_total",
			Help:      "Reservation Request Outcome",
		},
		[]string{"request_type", "outcome"},
	)

	relayAddressesUpdatedTotal = prometheus.NewCounter(
		prometheus.CounterOpts{
			Namespace: metricNamespace,
			Name:      "relay_addresses_updated_total",
			Help:      "Relay Addresses Updated Count",
		},
	)
	relayAddressesCount = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Namespace: metricNamespace,
			Name:      "relay_addresses_count",
			Help:      "Relay Addresses Count",
		},
	)

	candidatesCircuitV2SupportTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: metricNamespace,
			Name:      "candidates_circuit_v2_support_total",
			Help:      "Candidates supporting circuit v2",
		},
		[]string{"support"},
	)
	candidatesTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: metricNamespace,
			Name:      "candidates_total",
			Help:      "Candidates Total",
		},
		[]string{"type"},
	)
	candLoopState = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Namespace: metricNamespace,
			Name:      "candidate_loop_state",
			Help:      "Candidate Loop State",
		},
	)

	scheduledWorkTime = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: metricNamespace,
			Name:      "scheduled_work_time",
			Help:      "Scheduled Work Times",
		},
		[]string{"work_type"},
	)

	desiredReservations = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Namespace: metricNamespace,
			Name:      "desired_reservations",
			Help:      "Desired Reservations",
		},
	)

	collectors = []prometheus.Collector{
		status,
		reservationsOpenedTotal,
		reservationsClosedTotal,
		reservationRequestsOutcomeTotal,
		relayAddressesUpdatedTotal,
		relayAddressesCount,
		candidatesCircuitV2SupportTotal,
		candidatesTotal,
		candLoopState,
		scheduledWorkTime,
		desiredReservations,
	}
)

type candidateLoopState int

const (
	peerSourceRateLimited candidateLoopState = iota
	waitingOnPeerChan
	waitingForTrigger
	stopped
)

// MetricsTracer is the interface for tracking metrics for autorelay
type MetricsTracer interface {
	RelayFinderStatus(isActive bool)

	ReservationEnded(cnt int)
	ReservationOpened(cnt int)
	ReservationRequestFinished(isRefresh bool, err error)

	RelayAddressCount(int)
	RelayAddressUpdated()

	CandidateChecked(supportsCircuitV2 bool)
	CandidateAdded(cnt int)
	CandidateRemoved(cnt int)
	CandidateLoopState(state candidateLoopState)

	ScheduledWorkUpdated(scheduledWork *scheduledWorkTimes)

	DesiredReservations(int)
}

type metricsTracer struct{}

var _ MetricsTracer = &metricsTracer{}

type metricsTracerSetting struct {
	reg prometheus.Registerer
}

type MetricsTracerOption func(*metricsTracerSetting)

func ( prometheus.Registerer) MetricsTracerOption {
	return func( *metricsTracerSetting) {
		if  != nil {
			.reg = 
		}
	}
}

func ( ...MetricsTracerOption) MetricsTracer {
	 := &metricsTracerSetting{reg: prometheus.DefaultRegisterer}
	for ,  := range  {
		()
	}
	metricshelper.RegisterCollectors(.reg, collectors...)

	// Initialise these counters to 0 otherwise the first reservation requests aren't handled
	// correctly when using promql increase function
	reservationRequestsOutcomeTotal.WithLabelValues("refresh", "success")
	reservationRequestsOutcomeTotal.WithLabelValues("new", "success")
	candidatesCircuitV2SupportTotal.WithLabelValues("yes")
	candidatesCircuitV2SupportTotal.WithLabelValues("no")
	return &metricsTracer{}
}

func ( *metricsTracer) ( bool) {
	if  {
		status.Set(1)
	} else {
		status.Set(0)
	}
}

func ( *metricsTracer) ( int) {
	reservationsClosedTotal.Add(float64())
}

func ( *metricsTracer) ( int) {
	reservationsOpenedTotal.Add(float64())
}

func ( *metricsTracer) ( bool,  error) {
	 := metricshelper.GetStringSlice()
	defer metricshelper.PutStringSlice()

	if  {
		* = append(*, "refresh")
	} else {
		* = append(*, "new")
	}
	* = append(*, getReservationRequestStatus())
	reservationRequestsOutcomeTotal.WithLabelValues(*...).Inc()

	if ! &&  == nil {
		reservationsOpenedTotal.Inc()
	}
}

func ( *metricsTracer) () {
	relayAddressesUpdatedTotal.Inc()
}

func ( *metricsTracer) ( int) {
	relayAddressesCount.Set(float64())
}

func ( *metricsTracer) ( bool) {
	 := metricshelper.GetStringSlice()
	defer metricshelper.PutStringSlice()
	if  {
		* = append(*, "yes")
	} else {
		* = append(*, "no")
	}
	candidatesCircuitV2SupportTotal.WithLabelValues(*...).Inc()
}

func ( *metricsTracer) ( int) {
	 := metricshelper.GetStringSlice()
	defer metricshelper.PutStringSlice()
	* = append(*, "added")
	candidatesTotal.WithLabelValues(*...).Add(float64())
}

func ( *metricsTracer) ( int) {
	 := metricshelper.GetStringSlice()
	defer metricshelper.PutStringSlice()
	* = append(*, "removed")
	candidatesTotal.WithLabelValues(*...).Add(float64())
}

func ( *metricsTracer) ( candidateLoopState) {
	candLoopState.Set(float64())
}

func ( *metricsTracer) ( *scheduledWorkTimes) {
	 := metricshelper.GetStringSlice()
	defer metricshelper.PutStringSlice()

	* = append(*, "allowed peer source call")
	scheduledWorkTime.WithLabelValues(*...).Set(float64(.nextAllowedCallToPeerSource.Unix()))
	* = (*)[:0]

	* = append(*, "reservation refresh")
	scheduledWorkTime.WithLabelValues(*...).Set(float64(.nextRefresh.Unix()))
	* = (*)[:0]

	* = append(*, "clear backoff")
	scheduledWorkTime.WithLabelValues(*...).Set(float64(.nextBackoff.Unix()))
	* = (*)[:0]

	* = append(*, "old candidate check")
	scheduledWorkTime.WithLabelValues(*...).Set(float64(.nextOldCandidateCheck.Unix()))
}

func ( *metricsTracer) ( int) {
	desiredReservations.Set(float64())
}

func getReservationRequestStatus( error) string {
	if  == nil {
		return "success"
	}

	 := "err other"
	var  client.ReservationError
	if errors.As(, &) {
		switch .Status {
		case pbv2.Status_CONNECTION_FAILED:
			return "connection failed"
		case pbv2.Status_MALFORMED_MESSAGE:
			return "malformed message"
		case pbv2.Status_RESERVATION_REFUSED:
			return "reservation refused"
		case pbv2.Status_PERMISSION_DENIED:
			return "permission denied"
		case pbv2.Status_RESOURCE_LIMIT_EXCEEDED:
			return "resource limit exceeded"
		}
	}
	return 
}

// wrappedMetricsTracer wraps MetricsTracer and ignores all calls when mt is nil
type wrappedMetricsTracer struct {
	mt MetricsTracer
}

var _ MetricsTracer = &wrappedMetricsTracer{}

func ( *wrappedMetricsTracer) ( bool) {
	if .mt != nil {
		.mt.RelayFinderStatus()
	}
}

func ( *wrappedMetricsTracer) ( int) {
	if .mt != nil {
		.mt.ReservationEnded()
	}
}

func ( *wrappedMetricsTracer) ( int) {
	if .mt != nil {
		.mt.ReservationOpened()
	}
}

func ( *wrappedMetricsTracer) ( bool,  error) {
	if .mt != nil {
		.mt.ReservationRequestFinished(, )
	}
}

func ( *wrappedMetricsTracer) () {
	if .mt != nil {
		.mt.RelayAddressUpdated()
	}
}

func ( *wrappedMetricsTracer) ( int) {
	if .mt != nil {
		.mt.RelayAddressCount()
	}
}

func ( *wrappedMetricsTracer) ( bool) {
	if .mt != nil {
		.mt.CandidateChecked()
	}
}

func ( *wrappedMetricsTracer) ( int) {
	if .mt != nil {
		.mt.CandidateAdded()
	}
}

func ( *wrappedMetricsTracer) ( int) {
	if .mt != nil {
		.mt.CandidateRemoved()
	}
}

func ( *wrappedMetricsTracer) ( *scheduledWorkTimes) {
	if .mt != nil {
		.mt.ScheduledWorkUpdated()
	}
}

func ( *wrappedMetricsTracer) ( int) {
	if .mt != nil {
		.mt.DesiredReservations()
	}
}

func ( *wrappedMetricsTracer) ( candidateLoopState) {
	if .mt != nil {
		.mt.CandidateLoopState()
	}
}