package metrics

import (
	"testing"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestRecordDBQuery(t *testing.T) {
	start := time.Now()
	time.Sleep(10 * time.Millisecond)
	duration := time.Since(start)

	RecordDBQuery("SELECT", "users", duration)

	// Vérifier que les métriques ont été enregistrées
	registry := prometheus.DefaultRegisterer.(*prometheus.Registry)
	metricFamilies, err := registry.Gather()
	require.NoError(t, err)

	foundQueriesTotal := false
	foundDuration := false

	for _, mf := range metricFamilies {
		if *mf.Name == "veza_db_queries_total" {
			foundQueriesTotal = true
			assert.Greater(t, len(mf.Metric), 0)
		}
		if *mf.Name == "veza_db_query_duration_seconds" {
			foundDuration = true
			assert.Greater(t, len(mf.Metric), 0)
		}
	}

	assert.True(t, foundQueriesTotal, "veza_db_queries_total metric should exist")
	assert.True(t, foundDuration, "veza_db_query_duration_seconds metric should exist")
}

func TestRecordDBQuery_MultipleOperations(t *testing.T) {
	operations := []struct {
		operation string
		table     string
		duration  time.Duration
	}{
		{"SELECT", "users", 10 * time.Millisecond},
		{"INSERT", "users", 15 * time.Millisecond},
		{"UPDATE", "users", 12 * time.Millisecond},
		{"DELETE", "users", 8 * time.Millisecond},
		{"SELECT", "tracks", 20 * time.Millisecond},
	}

	for _, op := range operations {
		RecordDBQuery(op.operation, op.table, op.duration)
	}

	// Vérifier que toutes les métriques sont enregistrées
	registry := prometheus.DefaultRegisterer.(*prometheus.Registry)
	metricFamilies, err := registry.Gather()
	require.NoError(t, err)

	for _, mf := range metricFamilies {
		if *mf.Name == "veza_db_queries_total" {
			// Au moins 5 requêtes devraient être comptées
			assert.GreaterOrEqual(t, len(mf.Metric), 1)
		}
	}
}

func TestUpdateDBConnections(t *testing.T) {
	UpdateDBConnections(10, 5, 5)

	// Vérifier que les métriques ont été mises à jour
	registry := prometheus.DefaultRegisterer.(*prometheus.Registry)
	metricFamilies, err := registry.Gather()
	require.NoError(t, err)

	foundConnections := false
	openValue := float64(0)
	idleValue := float64(0)
	inUseValue := float64(0)

	for _, mf := range metricFamilies {
		if *mf.Name == "veza_db_connections" {
			foundConnections = true
			for _, metric := range mf.Metric {
				if metric.Gauge != nil {
					for _, label := range metric.Label {
						switch *label.Value {
						case "open":
							openValue = *metric.Gauge.Value
						case "idle":
							idleValue = *metric.Gauge.Value
						case "in_use":
							inUseValue = *metric.Gauge.Value
						}
					}
				}
			}
		}
	}

	assert.True(t, foundConnections, "veza_db_connections metric should exist")
	assert.Equal(t, float64(10), openValue, "open connections should be 10")
	assert.Equal(t, float64(5), idleValue, "idle connections should be 5")
	assert.Equal(t, float64(5), inUseValue, "in_use connections should be 5")
}

func TestUpdateDBConnections_ZeroValues(t *testing.T) {
	UpdateDBConnections(0, 0, 0)

	registry := prometheus.DefaultRegisterer.(*prometheus.Registry)
	metricFamilies, err := registry.Gather()
	require.NoError(t, err)

	for _, mf := range metricFamilies {
		if *mf.Name == "veza_db_connections" {
			for _, metric := range mf.Metric {
				if metric.Gauge != nil {
					for _, label := range metric.Label {
						if *label.Value == "open" {
							assert.Equal(t, float64(0), *metric.Gauge.Value)
						}
					}
				}
			}
		}
	}
}

func TestUpdateDBConnections_AllStates(t *testing.T) {
	testCases := []struct {
		open  int
		idle  int
		inUse int
	}{
		{10, 5, 5},
		{25, 20, 5},
		{1, 0, 1},
		{100, 90, 10},
	}

	for _, tc := range testCases {
		t.Run("", func(t *testing.T) {
			UpdateDBConnections(tc.open, tc.idle, tc.inUse)

			registry := prometheus.DefaultRegisterer.(*prometheus.Registry)
			metricFamilies, err := registry.Gather()
			require.NoError(t, err)

			for _, mf := range metricFamilies {
				if *mf.Name == "veza_db_connections" {
					values := make(map[string]float64)
					for _, metric := range mf.Metric {
						if metric.Gauge != nil {
							for _, label := range metric.Label {
								values[*label.Value] = *metric.Gauge.Value
							}
						}
					}

					assert.Equal(t, float64(tc.open), values["open"])
					assert.Equal(t, float64(tc.idle), values["idle"])
					assert.Equal(t, float64(tc.inUse), values["in_use"])
				}
			}
		})
	}
}

func TestRecordDBQuery_HistogramBuckets(t *testing.T) {
	// Tester avec différentes durées
	durations := []time.Duration{
		1 * time.Millisecond,
		10 * time.Millisecond,
		50 * time.Millisecond,
		100 * time.Millisecond,
		500 * time.Millisecond,
		1 * time.Second,
	}

	for _, duration := range durations {
		RecordDBQuery("SELECT", "test", duration)
	}

	// Vérifier que l'histogramme est correctement configuré
	registry := prometheus.DefaultRegisterer.(*prometheus.Registry)
	metricFamilies, err := registry.Gather()
	require.NoError(t, err)

	for _, mf := range metricFamilies {
		if *mf.Name == "veza_db_query_duration_seconds" {
			assert.Equal(t, dto.MetricType_HISTOGRAM, *mf.Type)
			assert.Greater(t, len(mf.Metric), 0)
		}
	}
}

func TestRecordDBQuery_UnknownTable(t *testing.T) {
	// Tester avec table "unknown"
	RecordDBQuery("SELECT", "unknown", 10*time.Millisecond)

	registry := prometheus.DefaultRegisterer.(*prometheus.Registry)
	metricFamilies, err := registry.Gather()
	require.NoError(t, err)

	for _, mf := range metricFamilies {
		if *mf.Name == "veza_db_queries_total" {
			for _, metric := range mf.Metric {
				for _, label := range metric.Label {
					if *label.Name == "table" && *label.Value == "unknown" {
						assert.True(t, true, "Should record queries with unknown table")
					}
				}
			}
		}
	}
}
