package store

import (
	"context"
	"time"

	"git.t-juice.club/torjus/apiary/models"
	"github.com/prometheus/client_golang/prometheus"
)

var _ LoginAttemptStore = &MetricsCollectingStore{}

const tickDuration = 5 * time.Second

type MetricsCollectingStore struct {
	store LoginAttemptStore

	attemptsCounter      *prometheus.CounterVec
	uniqueUsernamesCount prometheus.Gauge
	uniquePasswordsCount prometheus.Gauge
	uniqueIPsCount       prometheus.Gauge
	totalAttemptsCount   prometheus.Gauge

	statsTicker *time.Ticker
}

func NewMetricsCollectingStore(ctx context.Context, store LoginAttemptStore) *MetricsCollectingStore {
	mcs := &MetricsCollectingStore{store: store}

	mcs.attemptsCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Name:        "apiary_ssh_attempt_counter",
			Help:        "Total count of login attempts toward SSH",
			ConstLabels: prometheus.Labels{"service": "honeypot_ssh"},
		},
		[]string{"CountryCode"},
	)
	mcs.uniqueUsernamesCount = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Name:        "apiary_ssh_unique_usernames_count",
			Help:        "Counter of unique usernames.",
			ConstLabels: prometheus.Labels{"service": "honeypot_ssh"},
		})

	mcs.uniquePasswordsCount = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Name:        "apiary_ssh_unique_passwords_count",
			Help:        "Counter of unique passwords.",
			ConstLabels: prometheus.Labels{"service": "honeypot_ssh"},
		})

	mcs.uniqueIPsCount = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Name:        "apiary_ssh_unique_ips_count",
			Help:        "Counter of unique IPs.",
			ConstLabels: prometheus.Labels{"service": "honeypot_ssh"},
		})

	mcs.totalAttemptsCount = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Name:        "apiary_ssh_total_login_attempts",
			Help:        "Total amount of login attempts made.",
			ConstLabels: prometheus.Labels{"service": "honeypot_ssh"},
		})

	prometheus.MustRegister(mcs.attemptsCounter)
	prometheus.MustRegister(mcs.uniqueUsernamesCount)
	prometheus.MustRegister(mcs.uniquePasswordsCount)
	prometheus.MustRegister(mcs.uniqueIPsCount)
	prometheus.MustRegister(mcs.totalAttemptsCount)

	mcs.statsTicker = time.NewTicker(tickDuration)
	go func() {
		defer mcs.statsTicker.Stop()
		for {
			select {
			case <-ctx.Done():
				return
			case <-mcs.statsTicker.C:
				mcs.Stats(LoginStatsTotals, 0)
			}
		}
	}()

	return mcs
}

func (s *MetricsCollectingStore) AddAttempt(l *models.LoginAttempt) error {
	err := s.store.AddAttempt(l)
	s.attemptsCounter.WithLabelValues(l.Country).Inc()
	return err
}

func (s *MetricsCollectingStore) All() (<-chan models.LoginAttempt, error) {
	return s.store.All()
}

func (s *MetricsCollectingStore) Stats(statType LoginStats, limit int) ([]StatsResult, error) {
	stats, err := s.store.Stats(statType, limit)
	if statType == LoginStatsTotals {
		// Reset ticker to avoid updating twice within the duration
		s.statsTicker.Reset(tickDuration)
		for _, element := range stats {
			switch element.Name {
			case "UniquePasswords":
				s.uniquePasswordsCount.Set(float64(element.Count))
			case "UniqueUsernames":
				s.uniqueUsernamesCount.Set(float64(element.Count))
			case "UniqueIPs":
				s.uniqueIPsCount.Set(float64(element.Count))
			case "TotalLoginAttempts":
				s.totalAttemptsCount.Set(float64(element.Count))
			default:
				continue
			}
		}
	}
	return stats, err

}

func (s *MetricsCollectingStore) Query(query AttemptQuery) ([]models.LoginAttempt, error) {
	return s.store.Query(query)
}

func (s *MetricsCollectingStore) IsHealthy() error {
	return s.store.IsHealthy()
}
