package metrics

import "github.com/prometheus/client_golang/prometheus"

// PreparedStmtCacheStat represents prepared statement cache statistics for metrics export.
type PreparedStmtCacheStat struct {
	TenantID     string
	User         string
	KeyspaceName string
	ClusterID    string
	Size         int
	Hits         uint64
	Misses       uint64
	Evictions    uint64
	Errors       uint64
}

// PreparedStmtCacheStatsProvider returns a snapshot of prepared statement cache stats.
type PreparedStmtCacheStatsProvider func() []PreparedStmtCacheStat

type preparedStmtCacheStatsCollector struct {
	sizeDesc      *prometheus.Desc
	hitsDesc      *prometheus.Desc
	missesDesc    *prometheus.Desc
	evictionsDesc *prometheus.Desc
	errorsDesc    *prometheus.Desc
}

func newPreparedStmtCacheStatsCollector() *preparedStmtCacheStatsCollector {
	labels := []string{"tenant", "user", "KeyspaceName", "ClusterId"}
	return &preparedStmtCacheStatsCollector{
		sizeDesc: prometheus.NewDesc(
			"proxy_mysql_prepared_stmt_cache_size",
			"Current number of cached MySQL prepared statements.",
			labels,
			nil,
		),
		hitsDesc: prometheus.NewDesc(
			"proxy_mysql_prepared_stmt_cache_hits_total",
			"Total prepared statement cache hits.",
			labels,
			nil,
		),
		missesDesc: prometheus.NewDesc(
			"proxy_mysql_prepared_stmt_cache_misses_total",
			"Total prepared statement cache misses.",
			labels,
			nil,
		),
		evictionsDesc: prometheus.NewDesc(
			"proxy_mysql_prepared_stmt_cache_evictions_total",
			"Total prepared statement cache evictions.",
			labels,
			nil,
		),
		errorsDesc: prometheus.NewDesc(
			"proxy_mysql_prepared_stmt_cache_errors_total",
			"Total prepared statement cache errors.",
			labels,
			nil,
		),
	}
}

func (c *preparedStmtCacheStatsCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- c.sizeDesc
	ch <- c.hitsDesc
	ch <- c.missesDesc
	ch <- c.evictionsDesc
	ch <- c.errorsDesc
}

func (c *preparedStmtCacheStatsCollector) Collect(ch chan<- prometheus.Metric) {
	if c == nil {
		return
	}
	provider := activePreparedStmtCacheStatsProvider()
	if provider == nil {
		return
	}
	stats := provider()
	for _, stat := range stats {
		metricLabels := MetricLabels{
			KeyspaceName: stat.KeyspaceName,
			ClusterID:    stat.ClusterID,
		}.withDefaults()
		if stat.User != "" && (metricLabels.KeyspaceName == unknownKeyspaceName || metricLabels.ClusterID == unknownClusterID) {
			resolved := ResolveMetricLabels(nil, stat.User)
			if metricLabels.KeyspaceName == unknownKeyspaceName {
				metricLabels.KeyspaceName = resolved.KeyspaceName
			}
			if metricLabels.ClusterID == unknownClusterID {
				metricLabels.ClusterID = resolved.ClusterID
			}
		}
		labels := []string{stat.TenantID, stat.User, metricLabels.KeyspaceName, metricLabels.ClusterID}
		ch <- prometheus.MustNewConstMetric(c.sizeDesc, prometheus.GaugeValue, float64(stat.Size), labels...)
		ch <- prometheus.MustNewConstMetric(c.hitsDesc, prometheus.CounterValue, float64(stat.Hits), labels...)
		ch <- prometheus.MustNewConstMetric(c.missesDesc, prometheus.CounterValue, float64(stat.Misses), labels...)
		ch <- prometheus.MustNewConstMetric(c.evictionsDesc, prometheus.CounterValue, float64(stat.Evictions), labels...)
		ch <- prometheus.MustNewConstMetric(c.errorsDesc, prometheus.CounterValue, float64(stat.Errors), labels...)
	}
}
