package metrics

import (
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"veza-backend-api/internal/errors"
)

func TestNewAggregatedMetrics(t *testing.T) {
	agg := NewAggregatedMetrics()
	require.NotNil(t, agg)
	assert.NotNil(t, agg.windows)
	assert.NotNil(t, agg.windowSizes)
	assert.Equal(t, 3, len(agg.windowSizes)) // 1m, 5m, 1h
}

func TestAggregatedMetrics_AddError(t *testing.T) {
	agg := NewAggregatedMetrics()

	// Ajouter une erreur
	agg.AddError("1m", errors.ErrCodeValidation, 400)
	agg.AddError("5m", errors.ErrCodeValidation, 400)
	agg.AddError("1h", errors.ErrCodeValidation, 400)

	// Vérifier que les fenêtres ont été créées
	windows1m := agg.GetAggregated("1m")
	assert.Greater(t, len(windows1m), 0)

	windows5m := agg.GetAggregated("5m")
	assert.Greater(t, len(windows5m), 0)

	windows1h := agg.GetAggregated("1h")
	assert.Greater(t, len(windows1h), 0)

	// Vérifier que l'erreur a été comptabilisée
	assert.Equal(t, int64(1), windows1m[len(windows1m)-1].Errors)
	assert.Equal(t, int64(1), windows5m[len(windows5m)-1].Errors)
	assert.Equal(t, int64(1), windows1h[len(windows1h)-1].Errors)
}

func TestAggregatedMetrics_AddMultipleErrors(t *testing.T) {
	agg := NewAggregatedMetrics()

	// Ajouter plusieurs erreurs dans la même fenêtre
	for i := 0; i < 5; i++ {
		agg.AddError("1m", errors.ErrCodeInternal, 500)
	}

	windows := agg.GetAggregated("1m")
	require.Greater(t, len(windows), 0)

	// Vérifier que toutes les erreurs sont dans la dernière fenêtre
	lastWindow := windows[len(windows)-1]
	assert.Equal(t, int64(5), lastWindow.Errors)
	assert.Equal(t, int64(5), lastWindow.ErrorsByCode[errors.ErrCodeInternal])
	assert.Equal(t, int64(5), lastWindow.ErrorsByHTTPStatus[500])
}

func TestAggregatedMetrics_AddRequest(t *testing.T) {
	agg := NewAggregatedMetrics()

	// Ajouter des requêtes
	agg.AddRequest("1m")
	agg.AddRequest("1m")
	agg.AddRequest("5m")

	windows1m := agg.GetAggregated("1m")
	require.Greater(t, len(windows1m), 0)
	assert.Equal(t, int64(2), windows1m[len(windows1m)-1].Requests)

	windows5m := agg.GetAggregated("5m")
	require.Greater(t, len(windows5m), 0)
	assert.Equal(t, int64(1), windows5m[len(windows5m)-1].Requests)
}

func TestAggregatedMetrics_GetAggregated(t *testing.T) {
	agg := NewAggregatedMetrics()

	// Ajouter des erreurs pour différentes fenêtres
	agg.AddError("1m", errors.ErrCodeValidation, 400)
	agg.AddError("5m", errors.ErrCodeNotFound, 404)

	windows1m := agg.GetAggregated("1m")
	require.Greater(t, len(windows1m), 0)

	// Vérifier la structure de la fenêtre
	window := windows1m[len(windows1m)-1]
	assert.NotZero(t, window.Start)
	assert.NotZero(t, window.End)
	assert.Greater(t, window.End.Unix(), window.Start.Unix())
}

func TestAggregatedMetrics_GetAllAggregated(t *testing.T) {
	agg := NewAggregatedMetrics()

	// Ajouter des métriques pour toutes les fenêtres
	agg.AddError("1m", errors.ErrCodeValidation, 400)
	agg.AddError("5m", errors.ErrCodeNotFound, 404)
	agg.AddError("1h", errors.ErrCodeInternal, 500)

	allWindows := agg.GetAllAggregated()

	assert.Contains(t, allWindows, "1m")
	assert.Contains(t, allWindows, "5m")
	assert.Contains(t, allWindows, "1h")

	assert.Greater(t, len(allWindows["1m"]), 0)
	assert.Greater(t, len(allWindows["5m"]), 0)
	assert.Greater(t, len(allWindows["1h"]), 0)
}

func TestAggregatedMetrics_SlidingWindow(t *testing.T) {
	agg := NewAggregatedMetrics()

	// Simuler plusieurs fenêtres en ajoutant des erreurs avec des délais
	now := time.Now()

	// Ajouter une erreur maintenant
	agg.AddError("1m", errors.ErrCodeValidation, 400)

	// Attendre un peu (pas besoin d'attendre 1 minute, on teste juste la logique)
	windows1 := agg.GetAggregated("1m")
	assert.Equal(t, 1, len(windows1))

	// Ajouter une autre erreur - devrait être dans la même fenêtre si on est dans la même minute
	agg.AddError("1m", errors.ErrCodeValidation, 400)
	windows2 := agg.GetAggregated("1m")

	// Soit la même fenêtre (si même minute), soit une nouvelle
	assert.GreaterOrEqual(t, len(windows2), 1)

	// Le total devrait être au moins 2 erreurs
	totalErrors := int64(0)
	for _, w := range windows2 {
		totalErrors += w.Errors
	}
	assert.GreaterOrEqual(t, totalErrors, int64(2))

	// S'assurer que le temps n'est pas dans le futur
	for _, w := range windows2 {
		assert.LessOrEqual(t, w.Start.Unix(), now.Unix()+60) // Max 1 minute dans le futur
		assert.LessOrEqual(t, w.End.Unix(), now.Unix()+120)  // Max 2 minutes dans le futur
	}
}

func TestAggregatedMetrics_InvalidWindowType(t *testing.T) {
	agg := NewAggregatedMetrics()

	// Ajouter une erreur avec un type de fenêtre invalide
	agg.AddError("invalid", errors.ErrCodeValidation, 400)

	// Ne devrait pas créer de fenêtre
	windows := agg.GetAggregated("invalid")
	assert.Equal(t, 0, len(windows))
}

func TestAggregatedMetrics_ErrorsByCode(t *testing.T) {
	agg := NewAggregatedMetrics()

	// Ajouter différentes erreurs avec différents codes
	agg.AddError("1m", errors.ErrCodeValidation, 400)
	agg.AddError("1m", errors.ErrCodeNotFound, 404)
	agg.AddError("1m", errors.ErrCodeValidation, 400)

	windows := agg.GetAggregated("1m")
	require.Greater(t, len(windows), 0)

	lastWindow := windows[len(windows)-1]

	// Vérifier que les erreurs sont comptabilisées par code
	assert.Equal(t, int64(2), lastWindow.ErrorsByCode[errors.ErrCodeValidation])
	assert.Equal(t, int64(1), lastWindow.ErrorsByCode[errors.ErrCodeNotFound])
}

func TestAggregatedMetrics_ErrorsByHTTPStatus(t *testing.T) {
	agg := NewAggregatedMetrics()

	// Ajouter différentes erreurs avec différents status HTTP
	agg.AddError("1m", errors.ErrCodeValidation, 400)
	agg.AddError("1m", errors.ErrCodeNotFound, 404)
	agg.AddError("1m", errors.ErrCodeInternal, 500)
	agg.AddError("1m", errors.ErrCodeValidation, 400)

	windows := agg.GetAggregated("1m")
	require.Greater(t, len(windows), 0)

	lastWindow := windows[len(windows)-1]

	// Vérifier que les erreurs sont comptabilisées par status HTTP
	assert.Equal(t, int64(2), lastWindow.ErrorsByHTTPStatus[400])
	assert.Equal(t, int64(1), lastWindow.ErrorsByHTTPStatus[404])
	assert.Equal(t, int64(1), lastWindow.ErrorsByHTTPStatus[500])
}

func TestErrorMetrics_IntegrationWithAggregation(t *testing.T) {
	errorMetrics := NewErrorMetrics()
	require.NotNil(t, errorMetrics.aggregated)

	// Enregistrer des erreurs
	errorMetrics.RecordError(errors.ErrCodeValidation, 400)
	errorMetrics.RecordError(errors.ErrCodeNotFound, 404)

	// Vérifier que l'agrégation a été mise à jour
	windows1m := errorMetrics.GetAggregatedMetrics().GetAggregated("1m")
	require.Greater(t, len(windows1m), 0)

	lastWindow := windows1m[len(windows1m)-1]
	assert.GreaterOrEqual(t, lastWindow.Errors, int64(2))
}
