package services

import (
	"context"
	"testing"
	"time"

	"github.com/google/uuid"

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

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

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

	db.Exec("PRAGMA foreign_keys = ON")

	err = db.AutoMigrate(&models.User{}, &models.Track{}, &models.PlaybackAnalytics{})
	require.NoError(t, err)

	logger := zaptest.NewLogger(t)
	service := NewPlaybackAnalyticsService(db, logger)

	return db, service
}

func TestNewPlaybackAnalyticsService(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	logger := zaptest.NewLogger(t)
	service := NewPlaybackAnalyticsService(db, logger)

	assert.NotNil(t, service)
	assert.Equal(t, db, service.db)
	assert.NotNil(t, service.logger)
}

func TestNewPlaybackAnalyticsService_NilLogger(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	service := NewPlaybackAnalyticsService(db, nil)

	assert.NotNil(t, service)
	assert.NotNil(t, service.logger)
}

func TestPlaybackAnalyticsService_CalculateCompletionRate(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)

	// Test normal
	rate := service.CalculateCompletionRate(90, 180)
	assert.Equal(t, 50.0, rate)

	// Test 100%
	rate = service.CalculateCompletionRate(180, 180)
	assert.Equal(t, 100.0, rate)

	// Test 0%
	rate = service.CalculateCompletionRate(0, 180)
	assert.Equal(t, 0.0, rate)

	// Test > 100% (should be capped)
	rate = service.CalculateCompletionRate(200, 180)
	assert.Equal(t, 100.0, rate)

	// Test avec duration = 0
	rate = service.CalculateCompletionRate(100, 0)
	assert.Equal(t, 0.0, rate)

	// Test avec playTime négatif
	rate = service.CalculateCompletionRate(-10, 180)
	assert.Equal(t, 0.0, rate)
}

func TestPlaybackAnalyticsService_RecordPlayback_Success(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	user := &models.User{
		ID:       userID,
		Username: "testuser",
		Email:    "test@example.com",
		IsActive: true,
	}
	db.Create(user)

	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Enregistrer analytics
	now := time.Now()
	analytics := &models.PlaybackAnalytics{
		TrackID:    trackID,
		UserID:     userID,
		PlayTime:   120,
		PauseCount: 3,
		SeekCount:  5,
		StartedAt:  now,
		EndedAt:    &now,
	}

	err := service.RecordPlayback(ctx, analytics)
	assert.NoError(t, err)
	assert.NotZero(t, analytics.ID)
	// Use InDelta for floating point comparison (120/180 * 100 = 66.66666666666666)
	assert.InDelta(t, 66.67, analytics.CompletionRate, 0.01) // 120/180 * 100
}

func TestPlaybackAnalyticsService_RecordPlayback_InvalidTrackID(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	analytics := &models.PlaybackAnalytics{
		TrackID:   uuid.Nil,
		UserID:    uuid.New(),
		PlayTime:  120,
		StartedAt: time.Now(),
	}

	err := service.RecordPlayback(ctx, analytics)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid track ID")
}

func TestPlaybackAnalyticsService_RecordPlayback_InvalidUserID(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	analytics := &models.PlaybackAnalytics{
		TrackID:   uuid.New(),
		UserID:    uuid.Nil,
		PlayTime:  120,
		StartedAt: time.Now(),
	}

	err := service.RecordPlayback(ctx, analytics)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid user ID")
}

func TestPlaybackAnalyticsService_RecordPlayback_TrackNotFound(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	analytics := &models.PlaybackAnalytics{
		TrackID:   uuid.New(),
		UserID:    uuid.New(),
		PlayTime:  120,
		StartedAt: time.Now(),
	}

	err := service.RecordPlayback(ctx, analytics)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "track not found")
}

func TestPlaybackAnalyticsService_RecordPlayback_InvalidCompletionRate(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)

	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	analytics := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       120,
		CompletionRate: 150.0, // > 100
		StartedAt:      time.Now(),
	}

	err := service.RecordPlayback(ctx, analytics)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid completion rate")
}

func TestPlaybackAnalyticsService_RecordPlayback_ZeroStartedAt(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)

	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	analytics := &models.PlaybackAnalytics{
		TrackID:   trackID,
		UserID:    userID,
		PlayTime:  120,
		StartedAt: time.Time{}, // Zero time
	}

	err := service.RecordPlayback(ctx, analytics)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "started_at is required")
}

func TestPlaybackAnalyticsService_GetTrackStats(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)

	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Créer plusieurs sessions
	now := time.Now()
	sessions := []*models.PlaybackAnalytics{
		{TrackID: trackID, UserID: userID, PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 66.67, StartedAt: now},
		{TrackID: trackID, UserID: userID, PlayTime: 180, PauseCount: 1, SeekCount: 1, CompletionRate: 100.0, StartedAt: now},
		{TrackID: trackID, UserID: userID, PlayTime: 90, PauseCount: 3, SeekCount: 5, CompletionRate: 50.0, StartedAt: now},
	}

	for _, session := range sessions {
		db.Create(session)
	}

	stats, err := service.GetTrackStats(ctx, trackID)
	require.NoError(t, err)

	assert.Equal(t, int64(3), stats.TotalSessions)
	assert.Equal(t, int64(390), stats.TotalPlayTime)       // 120 + 180 + 90
	assert.Equal(t, 130.0, stats.AveragePlayTime)          // 390 / 3
	assert.Equal(t, int64(6), stats.TotalPauses)           // 2 + 1 + 3
	assert.Equal(t, 2.0, stats.AveragePauses)              // 6 / 3
	assert.Equal(t, int64(9), stats.TotalSeeks)            // 3 + 1 + 5
	assert.Equal(t, 3.0, stats.AverageSeeks)               // 9 / 3
	assert.InDelta(t, 72.22, stats.AverageCompletion, 0.1) // (66.67 + 100 + 50) / 3
	assert.InDelta(t, 33.33, stats.CompletionRate, 0.01)   // 1 session avec >= 90% / 3
}

func TestPlaybackAnalyticsService_GetTrackStats_NoSessions(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)

	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	stats, err := service.GetTrackStats(ctx, trackID)
	require.NoError(t, err)

	assert.Equal(t, int64(0), stats.TotalSessions)
	assert.Equal(t, int64(0), stats.TotalPlayTime)
	assert.Equal(t, 0.0, stats.AveragePlayTime)
}

func TestPlaybackAnalyticsService_GetTrackStats_TrackNotFound(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	_, err := service.GetTrackStats(ctx, uuid.New())
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "track not found")
}

func TestPlaybackAnalyticsService_GetUserStats(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)

	track1ID := uuid.New()
	track2ID := uuid.New()
	track1 := &models.Track{ID: track1ID, UserID: userID, Title: "Track 1", FilePath: "/1.mp3", FileSize: 1024, Format: "MP3", Duration: 180, IsPublic: true, Status: models.TrackStatusCompleted}
	track2 := &models.Track{ID: track2ID, UserID: userID, Title: "Track 2", FilePath: "/2.mp3", FileSize: 1024, Format: "MP3", Duration: 120, IsPublic: true, Status: models.TrackStatusCompleted}
	db.Create(track1)
	db.Create(track2)

	now := time.Now()
	sessions := []*models.PlaybackAnalytics{
		{TrackID: track1ID, UserID: userID, PlayTime: 120, PauseCount: 2, SeekCount: 3, CompletionRate: 66.67, StartedAt: now},
		{TrackID: track2ID, UserID: userID, PlayTime: 100, PauseCount: 1, SeekCount: 2, CompletionRate: 83.33, StartedAt: now},
	}

	for _, session := range sessions {
		db.Create(session)
	}

	stats, err := service.GetUserStats(ctx, userID)
	require.NoError(t, err)

	assert.Equal(t, int64(2), stats.TotalSessions)
	assert.Equal(t, int64(220), stats.TotalPlayTime) // 120 + 100
	assert.Equal(t, 110.0, stats.AveragePlayTime)    // 220 / 2
	assert.Equal(t, int64(3), stats.TotalPauses)     // 2 + 1
	assert.Equal(t, 1.5, stats.AveragePauses)        // 3 / 2
	assert.Equal(t, int64(5), stats.TotalSeeks)      // 3 + 2
	assert.Equal(t, 2.5, stats.AverageSeeks)         // 5 / 2
}

func TestPlaybackAnalyticsService_GetUserStats_UserNotFound(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	_, err := service.GetUserStats(ctx, uuid.New())
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "user not found")
}

func TestPlaybackAnalyticsService_GetSessionsByDateRange(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)

	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Créer des sessions à différentes dates
	baseTime := time.Date(2024, 1, 15, 12, 0, 0, 0, time.UTC)
	sessions := []*models.PlaybackAnalytics{
		{TrackID: trackID, UserID: userID, PlayTime: 120, StartedAt: baseTime.AddDate(0, 0, -2), CreatedAt: baseTime.AddDate(0, 0, -2)}, // 2 jours avant
		{TrackID: trackID, UserID: userID, PlayTime: 180, StartedAt: baseTime.AddDate(0, 0, -1), CreatedAt: baseTime.AddDate(0, 0, -1)}, // 1 jour avant
		{TrackID: trackID, UserID: userID, PlayTime: 90, StartedAt: baseTime, CreatedAt: baseTime},                                      // Aujourd'hui
		{TrackID: trackID, UserID: userID, PlayTime: 100, StartedAt: baseTime.AddDate(0, 0, 1), CreatedAt: baseTime.AddDate(0, 0, 1)},   // 1 jour après
	}

	for _, session := range sessions {
		db.Create(session)
	}

	// Récupérer les sessions des 3 derniers jours
	startDate := baseTime.AddDate(0, 0, -2)
	endDate := baseTime

	result, err := service.GetSessionsByDateRange(ctx, trackID, startDate, endDate)
	require.NoError(t, err)

	// Devrait retourner 3 sessions (2 jours avant, 1 jour avant, aujourd'hui)
	assert.Len(t, result, 3)
}

func TestPlaybackAnalyticsService_GetSessionsByDateRange_InvalidTrackID(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	startDate := time.Now().AddDate(0, 0, -7)
	endDate := time.Now()

	_, err := service.GetSessionsByDateRange(ctx, uuid.Nil, startDate, endDate)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid track ID")
}

// Tests pour TrackCompletion (T0366)
func TestPlaybackAnalyticsService_TrackCompletion_Success(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	// Créer user et track
	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Créer une session d'analytics
	now := time.Now()
	analytics := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       171, // 95% de 180 secondes
		PauseCount:     2,
		SeekCount:      3,
		CompletionRate: 0, // Sera calculé
		StartedAt:      now,
	}
	db.Create(analytics)

	// Tester le tracking de completion
	err := service.TrackCompletion(ctx, analytics, 180)
	require.NoError(t, err)

	// Vérifier que le completion rate a été calculé
	assert.InDelta(t, 95.0, analytics.CompletionRate, 0.1)

	// Vérifier que EndedAt a été défini (completion ≥95%)
	assert.NotNil(t, analytics.EndedAt)

	// Vérifier dans la base de données
	var updatedAnalytics models.PlaybackAnalytics
	db.First(&updatedAnalytics, analytics.ID)
	assert.InDelta(t, 95.0, updatedAnalytics.CompletionRate, 0.1)
	assert.NotNil(t, updatedAnalytics.EndedAt)
}

func TestPlaybackAnalyticsService_TrackCompletion_NotCompleted(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	now := time.Now()
	analytics := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       90, // 50% de 180 secondes
		PauseCount:     2,
		SeekCount:      3,
		CompletionRate: 0,
		StartedAt:      now,
	}
	db.Create(analytics)

	err := service.TrackCompletion(ctx, analytics, 180)
	require.NoError(t, err)

	// Vérifier que le completion rate a été calculé
	assert.InDelta(t, 50.0, analytics.CompletionRate, 0.1)

	// Vérifier que EndedAt n'a PAS été défini (<95%)
	assert.Nil(t, analytics.EndedAt)

	// Vérifier dans la base de données
	var updatedAnalytics models.PlaybackAnalytics
	db.First(&updatedAnalytics, analytics.ID)
	assert.InDelta(t, 50.0, updatedAnalytics.CompletionRate, 0.1)
	assert.Nil(t, updatedAnalytics.EndedAt)
}

func TestPlaybackAnalyticsService_TrackCompletion_Exactly95(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	now := time.Now()
	analytics := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       171, // Exactement 95% (171/180 = 0.95)
		PauseCount:     2,
		SeekCount:      3,
		CompletionRate: 0,
		StartedAt:      now,
	}
	db.Create(analytics)

	err := service.TrackCompletion(ctx, analytics, 180)
	require.NoError(t, err)

	// Vérifier que EndedAt a été défini (≥95%)
	assert.NotNil(t, analytics.EndedAt)
}

func TestPlaybackAnalyticsService_TrackCompletion_100Percent(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	now := time.Now()
	analytics := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       180, // 100%
		PauseCount:     2,
		SeekCount:      3,
		CompletionRate: 0,
		StartedAt:      now,
	}
	db.Create(analytics)

	err := service.TrackCompletion(ctx, analytics, 180)
	require.NoError(t, err)

	assert.Equal(t, 100.0, analytics.CompletionRate)
	assert.NotNil(t, analytics.EndedAt)
}

func TestPlaybackAnalyticsService_TrackCompletion_NilAnalytics(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	err := service.TrackCompletion(ctx, nil, 180)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "analytics cannot be nil")
}

func TestPlaybackAnalyticsService_TrackCompletion_NotSaved(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	analytics := &models.PlaybackAnalytics{
		ID:        uuid.Nil, // Non sauvegardé
		TrackID:   uuid.New(),
		UserID:    uuid.New(),
		PlayTime:  90,
		StartedAt: time.Now(),
	}

	err := service.TrackCompletion(ctx, analytics, 180)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "analytics must be saved")
}

func TestPlaybackAnalyticsService_TrackCompletion_InvalidDuration(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	now := time.Now()
	analytics := &models.PlaybackAnalytics{
		TrackID:   trackID,
		UserID:    userID,
		PlayTime:  90,
		StartedAt: now,
	}
	db.Create(analytics)

	err := service.TrackCompletion(ctx, analytics, 0)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid track duration")
}

func TestPlaybackAnalyticsService_UpdatePlaybackProgress_Success(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	now := time.Now()
	analytics := &models.PlaybackAnalytics{
		TrackID:   trackID,
		UserID:    userID,
		PlayTime:  50,
		StartedAt: now,
	}
	db.Create(analytics)

	// Mettre à jour le progrès
	err := service.UpdatePlaybackProgress(ctx, analytics.ID, 171, 180)
	require.NoError(t, err)

	// Vérifier que le progrès a été mis à jour
	var updatedAnalytics models.PlaybackAnalytics
	db.First(&updatedAnalytics, analytics.ID)
	assert.Equal(t, 171, updatedAnalytics.PlayTime)
	assert.InDelta(t, 95.0, updatedAnalytics.CompletionRate, 0.1)
	assert.NotNil(t, updatedAnalytics.EndedAt)
}

func TestPlaybackAnalyticsService_UpdatePlaybackProgress_AnalyticsNotFound(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	err := service.UpdatePlaybackProgress(ctx, uuid.New(), 90, 180)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "analytics not found")
}

func TestPlaybackAnalyticsService_UpdatePlaybackProgress_InvalidParams(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	// Test avec analytics ID invalide
	err := service.UpdatePlaybackProgress(ctx, uuid.Nil, 90, 180)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid analytics ID")

	// Test avec play time négatif
	err = service.UpdatePlaybackProgress(ctx, uuid.New(), -10, 180)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid play time")

	// Test avec duration invalide
	err = service.UpdatePlaybackProgress(ctx, uuid.New(), 90, 0)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid track duration")
}

// Tests pour les optimisations de performance (T0381)
func TestPlaybackAnalyticsService_NewPlaybackAnalyticsServiceWithCache(t *testing.T) {
	db, _ := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	logger := zaptest.NewLogger(t)

	// Créer un mock cache service (simplifié pour les tests)
	// Note: Dans un vrai test, on utiliserait un vrai client Redis ou un mock
	service := NewPlaybackAnalyticsService(db, logger)

	assert.NotNil(t, service)
	assert.Nil(t, service.cache) // Pas de cache par défaut
	assert.Equal(t, 100, service.batchSize)
	assert.Equal(t, 5*time.Minute, service.cacheTTL)
}

func TestPlaybackAnalyticsService_SetBatchSize(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)

	// Test avec une taille valide
	service.SetBatchSize(50)
	assert.Equal(t, 50, service.batchSize)

	// Test avec une taille invalide (devrait garder la valeur précédente)
	service.SetBatchSize(0)
	assert.Equal(t, 50, service.batchSize) // Devrait rester à 50

	// Test avec une taille négative
	service.SetBatchSize(-10)
	assert.Equal(t, 50, service.batchSize) // Devrait rester à 50
}

func TestPlaybackAnalyticsService_RecordPlaybackBatch(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Slug: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Créer plusieurs analytics
	now := time.Now()
	analyticsList := []*models.PlaybackAnalytics{
		{TrackID: trackID, UserID: userID, PlayTime: 120, PauseCount: 1, SeekCount: 2, StartedAt: now},
		{TrackID: trackID, UserID: userID, PlayTime: 180, PauseCount: 0, SeekCount: 0, StartedAt: now},
		{TrackID: trackID, UserID: userID, PlayTime: 90, PauseCount: 2, SeekCount: 3, StartedAt: now},
	}

	err := service.RecordPlaybackBatch(ctx, analyticsList)
	require.NoError(t, err)

	// Vérifier que tous les analytics ont été enregistrés
	var count int64
	db.Model(&models.PlaybackAnalytics{}).Where("track_id = ?", trackID).Count(&count)
	assert.Equal(t, int64(3), count)
}

func TestPlaybackAnalyticsService_RecordPlaybackBatch_EmptyList(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	err := service.RecordPlaybackBatch(ctx, []*models.PlaybackAnalytics{})
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "analytics list cannot be empty")
}

func TestPlaybackAnalyticsService_RecordPlaybackBatch_InvalidData(t *testing.T) {
	_, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	now := time.Now()
	analyticsList := []*models.PlaybackAnalytics{
		{TrackID: uuid.Nil, UserID: uuid.New(), PlayTime: 120, StartedAt: now}, // TrackID invalide
	}

	err := service.RecordPlaybackBatch(ctx, analyticsList)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "invalid track ID")
}

func TestPlaybackAnalyticsService_GetSessionsByDateRangePaginated(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Slug: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Créer 10 sessions
	now := time.Now()
	for i := 0; i < 10; i++ {
		analytics := &models.PlaybackAnalytics{
			TrackID:   trackID,
			UserID:    userID,
			PlayTime:  120 + i*10,
			StartedAt: now.Add(time.Duration(i) * time.Hour),
			CreatedAt: now.Add(time.Duration(i) * time.Hour),
		}
		db.Create(analytics)
	}

	startDate := now.Add(-1 * time.Hour)
	endDate := now.Add(12 * time.Hour)

	// Page 1, 5 éléments par page
	result, err := service.GetSessionsByDateRangePaginated(ctx, trackID, startDate, endDate, 1, 5)
	require.NoError(t, err)
	assert.Equal(t, 5, len(result))

	// Page 2, 5 éléments par page
	result2, err := service.GetSessionsByDateRangePaginated(ctx, trackID, startDate, endDate, 2, 5)
	require.NoError(t, err)
	assert.Equal(t, 5, len(result2))

	// Vérifier qu'il n'y a pas de doublons
	ids1 := make(map[uuid.UUID]bool)
	for _, s := range result {
		ids1[s.ID] = true
	}
	for _, s := range result2 {
		assert.False(t, ids1[s.ID], "Duplicate ID found")
	}
}

func TestPlaybackAnalyticsService_GetSessionsByDateRangePaginatedResult(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Slug: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Créer 25 sessions
	now := time.Now()
	for i := 0; i < 25; i++ {
		analytics := &models.PlaybackAnalytics{
			TrackID:   trackID,
			UserID:    userID,
			PlayTime:  120 + i*10,
			StartedAt: now.Add(time.Duration(i) * time.Hour),
			CreatedAt: now.Add(time.Duration(i) * time.Hour),
		}
		db.Create(analytics)
	}

	startDate := now.Add(-1 * time.Hour)
	endDate := now.Add(26 * time.Hour)

	// Tester avec pagination
	result, err := service.GetSessionsByDateRangePaginatedResult(ctx, trackID, startDate, endDate, 1, 10)
	require.NoError(t, err)

	assert.Equal(t, int64(25), result.Total)
	assert.Equal(t, 1, result.Page)
	assert.Equal(t, 10, result.PageSize)
	assert.Equal(t, 3, result.TotalPages) // 25 / 10 = 2.5, arrondi à 3
	assert.Equal(t, 10, len(result.Data))
}

func TestPlaybackAnalyticsService_GetSessionsByDateRangePaginatedResult_DefaultValues(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Slug: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	now := time.Now()
	for i := 0; i < 25; i++ {
		analytics := &models.PlaybackAnalytics{
			TrackID:   trackID,
			UserID:    userID,
			PlayTime:  120 + i*10,
			StartedAt: now.Add(time.Duration(i) * time.Hour),
			CreatedAt: now.Add(time.Duration(i) * time.Hour),
		}
		db.Create(analytics)
	}

	startDate := now.Add(-1 * time.Hour)
	endDate := now.Add(26 * time.Hour)

	// Tester avec page = 0 (devrait devenir 1)
	result, err := service.GetSessionsByDateRangePaginatedResult(ctx, trackID, startDate, endDate, 0, 0)
	require.NoError(t, err)
	assert.Equal(t, 1, result.Page)
	assert.Equal(t, 50, result.PageSize) // Taille par défaut

	// Tester avec pageSize > 1000 (devrait être limité à 1000)
	result2, err := service.GetSessionsByDateRangePaginatedResult(ctx, trackID, startDate, endDate, 1, 2000)
	require.NoError(t, err)
	assert.Equal(t, 1000, result2.PageSize) // Limite maximale
}

func TestPlaybackAnalyticsService_GetSessionsByDateRangePaginated_NoPagination(t *testing.T) {
	db, service := setupTestPlaybackAnalyticsServiceDB(t)
	ctx := context.Background()

	// Créer user et track
	userID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Slug: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	trackID := uuid.New()
	track := &models.Track{
		ID:       trackID,
		UserID:   userID,
		Title:    "Test Track",
		FilePath: "/test.mp3",
		FileSize: 1024,
		Format:   "MP3",
		Duration: 180,
		IsPublic: true,
		Status:   models.TrackStatusCompleted,
	}
	db.Create(track)

	// Créer 5 sessions
	now := time.Now()
	for i := 0; i < 5; i++ {
		analytics := &models.PlaybackAnalytics{
			TrackID:   trackID,
			UserID:    userID,
			PlayTime:  120,
			StartedAt: now.Add(time.Duration(i) * time.Hour),
			CreatedAt: now.Add(time.Duration(i) * time.Hour),
		}
		db.Create(analytics)
	}

	startDate := now.Add(-1 * time.Hour)
	endDate := now.Add(6 * time.Hour)

	// Tester sans pagination (pageSize = 0)
	result, err := service.GetSessionsByDateRangePaginated(ctx, trackID, startDate, endDate, 0, 100)
	require.NoError(t, err)
	assert.Equal(t, 5, len(result)) // Devrait retourner toutes les sessions
}
