package monitoring

import (
	"context"
	"testing"
	"time"

	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func setupTestDB(t *testing.T) *gorm.DB {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)

	// Migrer les modèles nécessaires
	err = db.AutoMigrate(
		&models.Track{},
		&models.PlaybackAnalytics{},
	)
	require.NoError(t, err)

	return db
}

func TestNewPlaybackAnalyticsMonitor(t *testing.T) {
	db := setupTestDB(t)
	logger := zap.NewNop()
	alertsService := services.NewPlaybackAlertsService(db, logger)
	analyticsService := services.NewPlaybackAnalyticsService(db, logger)

	monitor := NewPlaybackAnalyticsMonitor(db, logger, alertsService, analyticsService)

	assert.NotNil(t, monitor)
	assert.Equal(t, db, monitor.db)
	assert.Equal(t, logger, monitor.logger)
	assert.Equal(t, alertsService, monitor.alertsService)
	assert.Equal(t, analyticsService, monitor.analyticsService)
	assert.NotNil(t, monitor.metrics)
	assert.Equal(t, 5*time.Minute, monitor.alertCheckInterval)
}

func TestPlaybackAnalyticsMonitor_RecordEvent(t *testing.T) {
	db := setupTestDB(t)
	logger := zap.NewNop()
	monitor := NewPlaybackAnalyticsMonitor(db, logger, nil, nil)

	trackID := uuid.New()
	userID := uuid.New()

	analytics := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       180,
		PauseCount:     2,
		SeekCount:      1,
		CompletionRate: 75.0,
		StartedAt:      time.Now(),
	}

	// Test avec succès
	monitor.RecordEvent(context.Background(), analytics, 100*time.Millisecond, nil)

	metrics := monitor.GetPerformanceMetrics()
	assert.Equal(t, int64(1), metrics.TotalEventsRecorded)
	assert.Equal(t, int64(0), metrics.TotalEventsFailed)
	assert.Equal(t, 100*time.Millisecond, metrics.AverageRecordLatency)

	// Test avec erreur
	monitor.RecordEvent(context.Background(), analytics, 50*time.Millisecond, assert.AnError)

	metrics = monitor.GetPerformanceMetrics()
	assert.Equal(t, int64(1), metrics.TotalEventsRecorded)
	assert.Equal(t, int64(1), metrics.TotalEventsFailed)
}

func TestPlaybackAnalyticsMonitor_RecordBatchEvent(t *testing.T) {
	db := setupTestDB(t)
	logger := zap.NewNop()
	monitor := NewPlaybackAnalyticsMonitor(db, logger, nil, nil)

	// Test avec succès
	monitor.RecordBatchEvent(context.Background(), 10, 200*time.Millisecond, nil)

	metrics := monitor.GetPerformanceMetrics()
	assert.Equal(t, int64(10), metrics.TotalEventsRecorded)
	assert.Equal(t, int64(0), metrics.TotalEventsFailed)

	// Test avec erreur
	monitor.RecordBatchEvent(context.Background(), 5, 100*time.Millisecond, assert.AnError)

	metrics = monitor.GetPerformanceMetrics()
	assert.Equal(t, int64(10), metrics.TotalEventsRecorded)
	assert.Equal(t, int64(5), metrics.TotalEventsFailed)
}

func TestPlaybackAnalyticsMonitor_UpdateMetrics(t *testing.T) {
	db := setupTestDB(t)
	logger := zap.NewNop()
	monitor := NewPlaybackAnalyticsMonitor(db, logger, nil, nil)

	trackID := uuid.New()
	userID1 := uuid.New()
	userID2 := uuid.New()

	// Créer un track
	track := &models.Track{
		ID:       trackID,
		Title:    "Test Track",
		Duration: 180,
	}
	require.NoError(t, db.Create(track).Error)

	// Créer des analytics
	analytics1 := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID1,
		PlayTime:       180,
		CompletionRate: 100.0,
		StartedAt:      time.Now().Add(-10 * time.Minute),
		CreatedAt:      time.Now().Add(-10 * time.Minute),
	}
	analytics2 := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID2,
		PlayTime:       90,
		CompletionRate: 50.0,
		StartedAt:      time.Now().Add(-5 * time.Minute),
		CreatedAt:      time.Now().Add(-5 * time.Minute),
	}
	require.NoError(t, db.Create(analytics1).Error)
	require.NoError(t, db.Create(analytics2).Error)

	// Mettre à jour les métriques
	err := monitor.UpdateMetrics(context.Background())
	require.NoError(t, err)

	metrics := monitor.GetPerformanceMetrics()
	assert.GreaterOrEqual(t, metrics.ActiveSessions, int64(0))
	assert.Greater(t, metrics.AverageCompletionRate, 0.0)
	assert.Greater(t, metrics.AveragePlayTime, 0.0)
}

func TestPlaybackAnalyticsMonitor_CheckAlerts(t *testing.T) {
	db := setupTestDB(t)
	logger := zap.NewNop()
	alertsService := services.NewPlaybackAlertsService(db, logger)
	monitor := NewPlaybackAnalyticsMonitor(db, logger, alertsService, nil)

	trackID := uuid.New()
	userID := uuid.New()

	// Créer un track
	track := &models.Track{
		ID:       trackID,
		Title:    "Test Track",
		Duration: 180,
	}
	require.NoError(t, db.Create(track).Error)

	// Créer des analytics avec un faible taux de complétion
	analytics := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       30,
		CompletionRate: 15.0, // Faible taux de complétion
		StartedAt:      time.Now().Add(-1 * time.Hour),
		CreatedAt:      time.Now().Add(-1 * time.Hour),
	}
	require.NoError(t, db.Create(analytics).Error)

	// Vérifier les alertes
	alerts, err := monitor.CheckAlerts(context.Background())
	require.NoError(t, err)

	// Il devrait y avoir au moins une alerte pour le faible taux de complétion
	assert.GreaterOrEqual(t, len(alerts), 0) // Peut être 0 si les seuils ne sont pas atteints

	metrics := monitor.GetPerformanceMetrics()
	assert.GreaterOrEqual(t, metrics.TotalAlertsGenerated, int64(0))
}

func TestPlaybackAnalyticsMonitor_GetPerformanceMetrics(t *testing.T) {
	db := setupTestDB(t)
	logger := zap.NewNop()
	monitor := NewPlaybackAnalyticsMonitor(db, logger, nil, nil)

	trackID := uuid.New()
	userID := uuid.New()

	// Enregistrer quelques événements
	analytics := &models.PlaybackAnalytics{
		TrackID:  trackID,
		UserID:   userID,
		PlayTime: 180,
	}

	monitor.RecordEvent(context.Background(), analytics, 100*time.Millisecond, nil)
	monitor.RecordEvent(context.Background(), analytics, 150*time.Millisecond, nil)
	monitor.RecordEvent(context.Background(), analytics, 200*time.Millisecond, assert.AnError)

	metrics := monitor.GetPerformanceMetrics()

	assert.Equal(t, int64(2), metrics.TotalEventsRecorded)
	assert.Equal(t, int64(1), metrics.TotalEventsFailed)
	assert.Greater(t, metrics.AverageRecordLatency, time.Duration(0))
	assert.NotZero(t, metrics.LastUpdated)
}

func TestPlaybackAnalyticsMonitor_GetDashboardMetrics(t *testing.T) {
	db := setupTestDB(t)
	logger := zap.NewNop()
	alertsService := services.NewPlaybackAlertsService(db, logger)
	monitor := NewPlaybackAnalyticsMonitor(db, logger, alertsService, nil)

	trackID := uuid.New()
	userID := uuid.New()

	// Créer un track
	track := &models.Track{
		ID:       trackID,
		Title:    "Test Track",
		Duration: 180,
	}
	require.NoError(t, db.Create(track).Error)

	// Créer des analytics
	analytics := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       180,
		CompletionRate: 100.0,
		StartedAt:      time.Now().Add(-1 * time.Hour),
		CreatedAt:      time.Now().Add(-1 * time.Hour),
	}
	require.NoError(t, db.Create(analytics).Error)

	// Enregistrer quelques événements
	monitor.RecordEvent(context.Background(), analytics, 100*time.Millisecond, nil)
	monitor.RecordEvent(context.Background(), analytics, 150*time.Millisecond, nil)

	// Obtenir les métriques du dashboard
	dashboard, err := monitor.GetDashboardMetrics(context.Background())
	require.NoError(t, err)

	assert.NotNil(t, dashboard)
	assert.NotNil(t, dashboard.Performance)
	assert.NotNil(t, dashboard.RecentAlerts)
	assert.NotNil(t, dashboard.TopTracks)
	assert.GreaterOrEqual(t, dashboard.ErrorRate, 0.0)
	assert.GreaterOrEqual(t, dashboard.SuccessRate, 0.0)
	assert.GreaterOrEqual(t, dashboard.Throughput, 0.0)
	assert.NotZero(t, dashboard.Timestamp)
}

func TestPlaybackAnalyticsMonitor_GetTopTracks(t *testing.T) {
	db := setupTestDB(t)
	logger := zap.NewNop()
	monitor := NewPlaybackAnalyticsMonitor(db, logger, nil, nil)

	trackID1 := uuid.New()
	trackID2 := uuid.New()

	// Créer des tracks
	track1 := &models.Track{
		ID:       trackID1,
		Title:    "Track 1",
		Duration: 180,
	}
	track2 := &models.Track{
		ID:       trackID2,
		Title:    "Track 2",
		Duration: 240,
	}
	require.NoError(t, db.Create(track1).Error)
	require.NoError(t, db.Create(track2).Error)

	// Créer des analytics pour track1 (plus de sessions)
	for i := 0; i < 5; i++ {
		analytics := &models.PlaybackAnalytics{
			TrackID:        trackID1,
			UserID:         uuid.New(),
			PlayTime:       180,
			CompletionRate: 100.0,
			StartedAt:      time.Now().Add(-1 * time.Hour),
			CreatedAt:      time.Now().Add(-1 * time.Hour),
		}
		require.NoError(t, db.Create(analytics).Error)
	}

	// Créer des analytics pour track2 (moins de sessions)
	for i := 0; i < 2; i++ {
		analytics := &models.PlaybackAnalytics{
			TrackID:        trackID2,
			UserID:         uuid.New(),
			PlayTime:       120,
			CompletionRate: 50.0,
			StartedAt:      time.Now().Add(-1 * time.Hour),
			CreatedAt:      time.Now().Add(-1 * time.Hour),
		}
		require.NoError(t, db.Create(analytics).Error)
	}

	// Obtenir les top tracks
	topTracks, err := monitor.getTopTracks(context.Background(), 10)
	require.NoError(t, err)

	assert.GreaterOrEqual(t, len(topTracks), 2)
	// Track1 devrait être en premier (plus de sessions)
	if len(topTracks) >= 2 {
		assert.Equal(t, trackID1, topTracks[0].TrackID)
		assert.Equal(t, int64(5), topTracks[0].TotalSessions)
	}
}

func TestPlaybackAnalyticsMonitor_StartBackgroundMonitoring(t *testing.T) {
	db := setupTestDB(t)
	logger := zap.NewNop()
	monitor := NewPlaybackAnalyticsMonitor(db, logger, nil, nil)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	// Démarrer le monitoring en arrière-plan avec un intervalle court
	done := make(chan bool)
	go func() {
		monitor.StartBackgroundMonitoring(ctx, 100*time.Millisecond)
		done <- true
	}()

	// Attendre un peu pour que le monitoring se mette à jour
	time.Sleep(200 * time.Millisecond)

	// Arrêter le monitoring
	cancel()

	// Attendre que la goroutine se termine
	select {
	case <-done:
		// OK
	case <-time.After(1 * time.Second):
		t.Fatal("Background monitoring did not stop")
	}
}
