package metrics

import (
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
	"github.com/prometheus/client_golang/prometheus"
)

const (
	poolSubsystem = "pool"
)

const (
	overallErrorsMetric       = "overall_errors"
	overallNodeErrorsMetric   = "overall_node_errors"
	overallNodeRequestsMetric = "overall_node_requests"
	currentErrorMetric        = "current_errors"
	avgRequestDurationMetric  = "avg_request_duration"
	currentNodesMetric        = "current_nodes"
)

const (
	methodGetBalance       = "get_balance"
	methodPutContainer     = "put_container"
	methodGetContainer     = "get_container"
	methodListContainer    = "list_container"
	methodDeleteContainer  = "delete_container"
	methodGetContainerEacl = "get_container_eacl"
	methodSetContainerEacl = "set_container_eacl"
	methodEndpointInfo     = "endpoint_info"
	methodNetworkInfo      = "network_info"
	methodPutObject        = "put_object"
	methodDeleteObject     = "delete_object"
	methodGetObject        = "get_object"
	methodHeadObject       = "head_object"
	methodRangeObject      = "range_object"
	methodCreateSession    = "create_session"
)

type poolMetricsCollector struct {
	poolStatScraper     StatisticScraper
	overallErrors       prometheus.Gauge
	overallNodeErrors   *prometheus.GaugeVec
	overallNodeRequests *prometheus.GaugeVec
	currentErrors       *prometheus.GaugeVec
	requestDuration     *prometheus.GaugeVec
	currentNodes        *prometheus.GaugeVec
}

func newPoolMetricsCollector(scraper StatisticScraper) *poolMetricsCollector {
	return &poolMetricsCollector{
		poolStatScraper:     scraper,
		overallErrors:       mustNewGauge(appMetricsDesc[poolSubsystem][overallErrorsMetric]),
		overallNodeErrors:   mustNewGaugeVec(appMetricsDesc[poolSubsystem][overallNodeErrorsMetric]),
		overallNodeRequests: mustNewGaugeVec(appMetricsDesc[poolSubsystem][overallNodeRequestsMetric]),
		currentErrors:       mustNewGaugeVec(appMetricsDesc[poolSubsystem][currentErrorMetric]),
		requestDuration:     mustNewGaugeVec(appMetricsDesc[poolSubsystem][avgRequestDurationMetric]),
		currentNodes:        mustNewGaugeVec(appMetricsDesc[poolSubsystem][currentNodesMetric]),
	}
}

func (m *poolMetricsCollector) Collect(ch chan<- prometheus.Metric) {
	m.updateStatistic()
	m.overallErrors.Collect(ch)
	m.overallNodeErrors.Collect(ch)
	m.overallNodeRequests.Collect(ch)
	m.currentErrors.Collect(ch)
	m.requestDuration.Collect(ch)
	m.currentNodes.Collect(ch)
}

func (m *poolMetricsCollector) Describe(descs chan<- *prometheus.Desc) {
	m.overallErrors.Describe(descs)
	m.overallNodeErrors.Describe(descs)
	m.overallNodeRequests.Describe(descs)
	m.currentErrors.Describe(descs)
	m.requestDuration.Describe(descs)
	m.currentNodes.Describe(descs)
}

func (m *poolMetricsCollector) updateStatistic() {
	stat := m.poolStatScraper.Statistic()

	m.overallNodeErrors.Reset()
	m.overallNodeRequests.Reset()
	m.currentErrors.Reset()
	m.requestDuration.Reset()
	m.currentNodes.Reset()

	for _, node := range stat.Nodes() {
		m.overallNodeErrors.WithLabelValues(node.Address()).Set(float64(node.OverallErrors()))
		m.overallNodeRequests.WithLabelValues(node.Address()).Set(float64(node.Requests()))

		m.currentErrors.WithLabelValues(node.Address()).Set(float64(node.CurrentErrors()))
		m.updateRequestsDuration(node)
	}

	for _, addr := range stat.CurrentNodes() {
		m.currentNodes.WithLabelValues(addr).Set(1)
	}

	m.overallErrors.Set(float64(stat.OverallErrors()))
}

func (m *poolMetricsCollector) updateRequestsDuration(node pool.NodeStatistic) {
	m.requestDuration.WithLabelValues(node.Address(), methodGetBalance).Set(float64(node.AverageGetBalance().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodPutContainer).Set(float64(node.AveragePutContainer().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodGetContainer).Set(float64(node.AverageGetContainer().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodListContainer).Set(float64(node.AverageListContainer().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodDeleteContainer).Set(float64(node.AverageDeleteContainer().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodGetContainerEacl).Set(float64(node.AverageGetContainerEACL().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodSetContainerEacl).Set(float64(node.AverageSetContainerEACL().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodEndpointInfo).Set(float64(node.AverageEndpointInfo().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodNetworkInfo).Set(float64(node.AverageNetworkInfo().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodPutObject).Set(float64(node.AveragePutObject().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodDeleteObject).Set(float64(node.AverageDeleteObject().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodGetObject).Set(float64(node.AverageGetObject().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodHeadObject).Set(float64(node.AverageHeadObject().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodRangeObject).Set(float64(node.AverageRangeObject().Milliseconds()))
	m.requestDuration.WithLabelValues(node.Address(), methodCreateSession).Set(float64(node.AverageCreateSession().Milliseconds()))
}