package core

import (
	"context"
	"fmt"
	"strconv"
	"sync"

	"github.com/navidrome/navidrome/consts"
	"github.com/navidrome/navidrome/log"
	"github.com/navidrome/navidrome/model"
	"github.com/prometheus/client_golang/prometheus"
)

func WriteInitialMetrics() {
	getPrometheusMetrics().versionInfo.With(prometheus.Labels{"version": consts.Version}).Set(1)
}

func WriteAfterScanMetrics(ctx context.Context, dataStore model.DataStore, success bool) {
	processSqlAggregateMetrics(ctx, dataStore, getPrometheusMetrics().dbTotal)

	scanLabels := prometheus.Labels{"success": strconv.FormatBool(success)}
	getPrometheusMetrics().lastMediaScan.With(scanLabels).SetToCurrentTime()
	getPrometheusMetrics().mediaScansCounter.With(scanLabels).Inc()
}

// Prometheus' metrics requires initialization. But not more than once
var (
	prometheusMetricsInstance *prometheusMetrics
	prometheusOnce            sync.Once
)

type prometheusMetrics struct {
	dbTotal           *prometheus.GaugeVec
	versionInfo       *prometheus.GaugeVec
	lastMediaScan     *prometheus.GaugeVec
	mediaScansCounter *prometheus.CounterVec
}

func getPrometheusMetrics() *prometheusMetrics {
	prometheusOnce.Do(func() {
		var err error
		prometheusMetricsInstance, err = newPrometheusMetrics()
		if err != nil {
			log.Fatal("Unable to create Prometheus metrics instance.", err)
		}
	})
	return prometheusMetricsInstance
}

func newPrometheusMetrics() (*prometheusMetrics, error) {
	res := &prometheusMetrics{
		dbTotal: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Name: "db_model_totals",
				Help: "Total number of DB items per model",
			},
			[]string{"model"},
		),
		versionInfo: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Name: "navidrome_info",
				Help: "Information about Navidrome version",
			},
			[]string{"version"},
		),
		lastMediaScan: prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Name: "media_scan_last",
				Help: "Last media scan timestamp by success",
			},
			[]string{"success"},
		),
		mediaScansCounter: prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: "media_scans",
				Help: "Total success media scans by success",
			},
			[]string{"success"},
		),
	}

	err := prometheus.DefaultRegisterer.Register(res.dbTotal)
	if err != nil {
		return nil, fmt.Errorf("unable to register db_model_totals metrics: %w", err)
	}
	err = prometheus.DefaultRegisterer.Register(res.versionInfo)
	if err != nil {
		return nil, fmt.Errorf("unable to register navidrome_info metrics: %w", err)
	}
	err = prometheus.DefaultRegisterer.Register(res.lastMediaScan)
	if err != nil {
		return nil, fmt.Errorf("unable to register media_scan_last metrics: %w", err)
	}
	err = prometheus.DefaultRegisterer.Register(res.mediaScansCounter)
	if err != nil {
		return nil, fmt.Errorf("unable to register media_scans metrics: %w", err)
	}
	return res, nil
}

func processSqlAggregateMetrics(ctx context.Context, dataStore model.DataStore, targetGauge *prometheus.GaugeVec) {
	albumsCount, err := dataStore.Album(ctx).CountAll()
	if err != nil {
		log.Warn("album CountAll error", err)
		return
	}
	targetGauge.With(prometheus.Labels{"model": "album"}).Set(float64(albumsCount))

	songsCount, err := dataStore.MediaFile(ctx).CountAll()
	if err != nil {
		log.Warn("media CountAll error", err)
		return
	}
	targetGauge.With(prometheus.Labels{"model": "media"}).Set(float64(songsCount))

	usersCount, err := dataStore.User(ctx).CountAll()
	if err != nil {
		log.Warn("user CountAll error", err)
		return
	}
	targetGauge.With(prometheus.Labels{"model": "user"}).Set(float64(usersCount))
}