package metrics

import (
	"github.com/go-kit/kit/metrics"
	"github.com/go-kit/kit/metrics/multi"
)

// Registry has to implemented by any system that wants to monitor and expose metrics.
type Registry interface {
	// IsEnabled shows whether metrics instrumentation is enabled.
	IsEnabled() bool
	// server metrics
	ConfigReloadsCounter() metrics.Counter
	ConfigReloadsFailureCounter() metrics.Counter
	LastConfigReloadSuccessGauge() metrics.Gauge
	LastConfigReloadFailureGauge() metrics.Gauge

	// entry point metrics
	EntrypointReqsCounter() metrics.Counter
	EntrypointReqDurationHistogram() metrics.Histogram
	EntrypointOpenConnsGauge() metrics.Gauge

	// backend metrics
	BackendReqsCounter() metrics.Counter
	BackendReqDurationHistogram() metrics.Histogram
	BackendOpenConnsGauge() metrics.Gauge
	BackendRetriesCounter() metrics.Counter
	BackendServerUpGauge() metrics.Gauge
}

// NewVoidRegistry is a noop implementation of metrics.Registry.
// It is used to avoid nil checking in components that do metric collections.
func NewVoidRegistry() Registry {
	return NewMultiRegistry([]Registry{})
}

// NewMultiRegistry is an implementation of metrics.Registry that wraps multiple registries.
// It handles the case when a registry hasn't registered some metric and returns nil.
// This allows for feature imparity between the different metric implementations.
func NewMultiRegistry(registries []Registry) Registry {
	configReloadsCounter := []metrics.Counter{}
	configReloadsFailureCounter := []metrics.Counter{}
	lastConfigReloadSuccessGauge := []metrics.Gauge{}
	lastConfigReloadFailureGauge := []metrics.Gauge{}
	entrypointReqsCounter := []metrics.Counter{}
	entrypointReqDurationHistogram := []metrics.Histogram{}
	entrypointOpenConnsGauge := []metrics.Gauge{}
	backendReqsCounter := []metrics.Counter{}
	backendReqDurationHistogram := []metrics.Histogram{}
	backendOpenConnsGauge := []metrics.Gauge{}
	backendRetriesCounter := []metrics.Counter{}
	backendServerUpGauge := []metrics.Gauge{}

	for _, r := range registries {
		if r.ConfigReloadsCounter() != nil {
			configReloadsCounter = append(configReloadsCounter, r.ConfigReloadsCounter())
		}
		if r.ConfigReloadsFailureCounter() != nil {
			configReloadsFailureCounter = append(configReloadsFailureCounter, r.ConfigReloadsFailureCounter())
		}
		if r.LastConfigReloadSuccessGauge() != nil {
			lastConfigReloadSuccessGauge = append(lastConfigReloadSuccessGauge, r.LastConfigReloadSuccessGauge())
		}
		if r.LastConfigReloadFailureGauge() != nil {
			lastConfigReloadFailureGauge = append(lastConfigReloadFailureGauge, r.LastConfigReloadFailureGauge())
		}
		if r.EntrypointReqsCounter() != nil {
			entrypointReqsCounter = append(entrypointReqsCounter, r.EntrypointReqsCounter())
		}
		if r.EntrypointReqDurationHistogram() != nil {
			entrypointReqDurationHistogram = append(entrypointReqDurationHistogram, r.EntrypointReqDurationHistogram())
		}
		if r.EntrypointOpenConnsGauge() != nil {
			entrypointOpenConnsGauge = append(entrypointOpenConnsGauge, r.EntrypointOpenConnsGauge())
		}
		if r.BackendReqsCounter() != nil {
			backendReqsCounter = append(backendReqsCounter, r.BackendReqsCounter())
		}
		if r.BackendReqDurationHistogram() != nil {
			backendReqDurationHistogram = append(backendReqDurationHistogram, r.BackendReqDurationHistogram())
		}
		if r.BackendOpenConnsGauge() != nil {
			backendOpenConnsGauge = append(backendOpenConnsGauge, r.BackendOpenConnsGauge())
		}
		if r.BackendRetriesCounter() != nil {
			backendRetriesCounter = append(backendRetriesCounter, r.BackendRetriesCounter())
		}
		if r.BackendServerUpGauge() != nil {
			backendServerUpGauge = append(backendServerUpGauge, r.BackendServerUpGauge())
		}
	}

	return &standardRegistry{
		enabled:                        len(registries) > 0,
		configReloadsCounter:           multi.NewCounter(configReloadsCounter...),
		configReloadsFailureCounter:    multi.NewCounter(configReloadsFailureCounter...),
		lastConfigReloadSuccessGauge:   multi.NewGauge(lastConfigReloadSuccessGauge...),
		lastConfigReloadFailureGauge:   multi.NewGauge(lastConfigReloadFailureGauge...),
		entrypointReqsCounter:          multi.NewCounter(entrypointReqsCounter...),
		entrypointReqDurationHistogram: multi.NewHistogram(entrypointReqDurationHistogram...),
		entrypointOpenConnsGauge:       multi.NewGauge(entrypointOpenConnsGauge...),
		backendReqsCounter:             multi.NewCounter(backendReqsCounter...),
		backendReqDurationHistogram:    multi.NewHistogram(backendReqDurationHistogram...),
		backendOpenConnsGauge:          multi.NewGauge(backendOpenConnsGauge...),
		backendRetriesCounter:          multi.NewCounter(backendRetriesCounter...),
		backendServerUpGauge:           multi.NewGauge(backendServerUpGauge...),
	}
}

type standardRegistry struct {
	enabled                        bool
	configReloadsCounter           metrics.Counter
	configReloadsFailureCounter    metrics.Counter
	lastConfigReloadSuccessGauge   metrics.Gauge
	lastConfigReloadFailureGauge   metrics.Gauge
	entrypointReqsCounter          metrics.Counter
	entrypointReqDurationHistogram metrics.Histogram
	entrypointOpenConnsGauge       metrics.Gauge
	backendReqsCounter             metrics.Counter
	backendReqDurationHistogram    metrics.Histogram
	backendOpenConnsGauge          metrics.Gauge
	backendRetriesCounter          metrics.Counter
	backendServerUpGauge           metrics.Gauge
}

func (r *standardRegistry) IsEnabled() bool {
	return r.enabled
}

func (r *standardRegistry) ConfigReloadsCounter() metrics.Counter {
	return r.configReloadsCounter
}

func (r *standardRegistry) ConfigReloadsFailureCounter() metrics.Counter {
	return r.configReloadsFailureCounter
}

func (r *standardRegistry) LastConfigReloadSuccessGauge() metrics.Gauge {
	return r.lastConfigReloadSuccessGauge
}

func (r *standardRegistry) LastConfigReloadFailureGauge() metrics.Gauge {
	return r.lastConfigReloadFailureGauge
}

func (r *standardRegistry) EntrypointReqsCounter() metrics.Counter {
	return r.entrypointReqsCounter
}

func (r *standardRegistry) EntrypointReqDurationHistogram() metrics.Histogram {
	return r.entrypointReqDurationHistogram
}

func (r *standardRegistry) EntrypointOpenConnsGauge() metrics.Gauge {
	return r.entrypointOpenConnsGauge
}

func (r *standardRegistry) BackendReqsCounter() metrics.Counter {
	return r.backendReqsCounter
}

func (r *standardRegistry) BackendReqDurationHistogram() metrics.Histogram {
	return r.backendReqDurationHistogram
}

func (r *standardRegistry) BackendOpenConnsGauge() metrics.Gauge {
	return r.backendOpenConnsGauge
}

func (r *standardRegistry) BackendRetriesCounter() metrics.Counter {
	return r.backendRetriesCounter
}

func (r *standardRegistry) BackendServerUpGauge() metrics.Gauge {
	return r.backendServerUpGauge
}
