package services

import (
	"context"
	"github.com/google/uuid"
	"math"
	"testing"
	"time"

	"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 setupTestPlaybackABTestServiceDB(t *testing.T) (*gorm.DB, *PlaybackABTestService) {
	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 := NewPlaybackABTestService(db, logger)

	return db, service
}

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

	service := NewPlaybackABTestService(db, logger)

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

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

	service := NewPlaybackABTestService(db, nil)

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

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

	filterA := VariantFilter{}
	filterB := VariantFilter{}

	result, err := service.CompareVariants(ctx, "", "B", filterA, filterB)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "variant names cannot be empty")
	assert.Nil(t, result)

	result, err = service.CompareVariants(ctx, "A", "", filterA, filterB)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "variant names cannot be empty")
	assert.Nil(t, result)
}

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

	// Créer user et track
	userID := uuid.New()
	trackID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Slug: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	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)

	filterA := VariantFilter{TrackID: &trackID}
	filterB := VariantFilter{TrackID: &trackID}

	result, err := service.CompareVariants(ctx, "A", "B", filterA, filterB)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, "A", result.VariantA.VariantName)
	assert.Equal(t, "B", result.VariantB.VariantName)
	assert.Equal(t, int64(0), result.VariantA.TotalSessions)
	assert.Equal(t, int64(0), result.VariantB.TotalSessions)
	assert.NotNil(t, result.Significance)
}

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

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

	now := time.Now()
	// Variant A: High completion
	for i := 0; i < 10; i++ {
		db.Create(&models.PlaybackAnalytics{
			TrackID:        trackID,
			UserID:         user1ID,
			PlayTime:       180,
			PauseCount:     0,
			SeekCount:      0,
			CompletionRate: 100.0,
			StartedAt:      now,
			CreatedAt:      now,
		})
	}

	// Variant B: Lower completion
	for i := 0; i < 10; i++ {
		db.Create(&models.PlaybackAnalytics{
			TrackID:        trackID,
			UserID:         user2ID,
			PlayTime:       90,
			PauseCount:     2,
			SeekCount:      1,
			CompletionRate: 50.0,
			StartedAt:      now,
			CreatedAt:      now,
		})
	}

	filterA := VariantFilter{TrackID: &trackID, UserIDs: []uuid.UUID{user1ID}}
	filterB := VariantFilter{TrackID: &trackID, UserIDs: []uuid.UUID{user2ID}}

	result, err := service.CompareVariants(ctx, "A", "B", filterA, filterB)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, "A", result.VariantA.VariantName)
	assert.Equal(t, "B", result.VariantB.VariantName)
	assert.Equal(t, int64(10), result.VariantA.TotalSessions)
	assert.Equal(t, int64(10), result.VariantB.TotalSessions)
	assert.Equal(t, 100.0, result.VariantA.AverageCompletion)
	assert.Equal(t, 50.0, result.VariantB.AverageCompletion)
	assert.NotNil(t, result.Significance)
	assert.NotNil(t, result.Difference)
	assert.NotNil(t, result.PercentageChange)
}

func TestPlaybackABTestService_CalculateVariantStats(t *testing.T) {
	_, service := setupTestPlaybackABTestServiceDB(t)

	analytics := []models.PlaybackAnalytics{
		{PlayTime: 180, PauseCount: 0, SeekCount: 0, CompletionRate: 100.0},
		{PlayTime: 180, PauseCount: 1, SeekCount: 0, CompletionRate: 95.0},
		{PlayTime: 90, PauseCount: 2, SeekCount: 1, CompletionRate: 50.0},
	}

	stats := service.calculateVariantStats("TestVariant", analytics)

	assert.NotNil(t, stats)
	assert.Equal(t, "TestVariant", stats.VariantName)
	assert.Equal(t, int64(3), stats.TotalSessions)
	assert.InDelta(t, 150.0, stats.AveragePlayTime, 0.1)   // (180 + 180 + 90) / 3
	assert.InDelta(t, 81.67, stats.AverageCompletion, 0.1) // (100 + 95 + 50) / 3
	assert.Equal(t, 1.0, stats.AveragePauses)              // (0 + 1 + 2) / 3
	assert.InDelta(t, 0.33, stats.AverageSeeks, 0.1)       // (0 + 0 + 1) / 3
}

func TestPlaybackABTestService_CalculateVariantStats_Empty(t *testing.T) {
	_, service := setupTestPlaybackABTestServiceDB(t)

	analytics := []models.PlaybackAnalytics{}
	stats := service.calculateVariantStats("EmptyVariant", analytics)

	assert.NotNil(t, stats)
	assert.Equal(t, "EmptyVariant", stats.VariantName)
	assert.Equal(t, int64(0), stats.TotalSessions)
}

func TestPlaybackABTestService_CalculateStatisticalSignificance(t *testing.T) {
	_, service := setupTestPlaybackABTestServiceDB(t)

	// Variant A: High completion (tous à 100%)
	analyticsA := []models.PlaybackAnalytics{
		{CompletionRate: 100.0},
		{CompletionRate: 100.0},
		{CompletionRate: 100.0},
		{CompletionRate: 100.0},
		{CompletionRate: 100.0},
	}

	// Variant B: Lower completion (tous à 50%)
	analyticsB := []models.PlaybackAnalytics{
		{CompletionRate: 50.0},
		{CompletionRate: 50.0},
		{CompletionRate: 50.0},
		{CompletionRate: 50.0},
		{CompletionRate: 50.0},
	}

	significance := service.calculateStatisticalSignificance(analyticsA, analyticsB)

	assert.NotNil(t, significance)
	assert.GreaterOrEqual(t, significance.PValue, 0.0)
	assert.LessOrEqual(t, significance.PValue, 1.0)
	assert.Greater(t, significance.ConfidenceLevel, 0.0)
	// EffectSize peut être 0 si les écarts-types sont 0 (toutes les valeurs identiques)
	// Dans ce cas, on vérifie juste qu'il n'est pas NaN
	assert.False(t, math.IsNaN(significance.EffectSize))
	assert.False(t, math.IsInf(significance.EffectSize, 0))
}

func TestPlaybackABTestService_CalculateStatisticalSignificance_Empty(t *testing.T) {
	_, service := setupTestPlaybackABTestServiceDB(t)

	analyticsA := []models.PlaybackAnalytics{}
	analyticsB := []models.PlaybackAnalytics{}

	significance := service.calculateStatisticalSignificance(analyticsA, analyticsB)

	assert.NotNil(t, significance)
	assert.Equal(t, 1.0, significance.PValue)
	assert.False(t, significance.IsSignificant)
}

func TestPlaybackABTestService_CalculateMeanAndStdDev(t *testing.T) {
	_, service := setupTestPlaybackABTestServiceDB(t)

	data := []float64{10.0, 20.0, 30.0, 40.0, 50.0}
	mean, stdDev := service.calculateMeanAndStdDev(data)

	assert.Equal(t, 30.0, mean)
	assert.Greater(t, stdDev, 0.0)
}

func TestPlaybackABTestService_CalculateMeanAndStdDev_Empty(t *testing.T) {
	_, service := setupTestPlaybackABTestServiceDB(t)

	data := []float64{}
	mean, stdDev := service.calculateMeanAndStdDev(data)

	assert.Equal(t, 0.0, mean)
	assert.Equal(t, 0.0, stdDev)
}

func TestPlaybackABTestService_DetermineWinner(t *testing.T) {
	_, service := setupTestPlaybackABTestServiceDB(t)

	statsA := &VariantStats{CompletionRate: 80.0}
	statsB := &VariantStats{CompletionRate: 90.0}
	significance := &StatisticalSignificance{IsSignificant: true, PValue: 0.01}

	winner := service.determineWinner(statsA, statsB, significance)
	assert.Equal(t, "B", winner)

	// Test avec A gagnant
	statsA2 := &VariantStats{CompletionRate: 90.0}
	statsB2 := &VariantStats{CompletionRate: 80.0}
	winner2 := service.determineWinner(statsA2, statsB2, significance)
	assert.Equal(t, "A", winner2)

	// Test non significatif
	significance2 := &StatisticalSignificance{IsSignificant: false, PValue: 0.1}
	winner3 := service.determineWinner(statsA, statsB, significance2)
	assert.Equal(t, "inconclusive", winner3)
}

func TestPlaybackABTestService_GenerateRecommendation(t *testing.T) {
	_, service := setupTestPlaybackABTestServiceDB(t)

	statsA := &VariantStats{CompletionRate: 80.0}
	statsB := &VariantStats{CompletionRate: 90.0}
	significance := &StatisticalSignificance{IsSignificant: true, PValue: 0.01}

	recommendation := service.generateRecommendation(statsA, statsB, significance)
	assert.NotEmpty(t, recommendation)
	assert.Contains(t, recommendation, "variant B")
	assert.Contains(t, recommendation, "significativement meilleur")

	// Test avec A gagnant
	statsA2 := &VariantStats{CompletionRate: 90.0}
	statsB2 := &VariantStats{CompletionRate: 80.0}
	recommendation2 := service.generateRecommendation(statsA2, statsB2, significance)
	assert.Contains(t, recommendation2, "variant A")

	// Test non significatif
	significance2 := &StatisticalSignificance{IsSignificant: false, PValue: 0.1}
	recommendation3 := service.generateRecommendation(statsA, statsB, significance2)
	assert.Contains(t, recommendation3, "pas statistiquement significatifs")
}

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

	// Créer user et track
	userID := uuid.New()
	trackID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Slug: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	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,
		PauseCount:     0,
		SeekCount:      0,
		CompletionRate: 100.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	db.Create(analytics)

	filter := VariantFilter{TrackID: &trackID}
	result, err := service.getAnalyticsForVariant(ctx, filter)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, 1, len(result))
}

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

	// Créer user et track
	userID := uuid.New()
	trackID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Slug: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	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()
	yesterday := now.AddDate(0, 0, -1)
	tomorrow := now.AddDate(0, 0, 1)

	// Analytics créé aujourd'hui
	analytics := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       180,
		PauseCount:     0,
		SeekCount:      0,
		CompletionRate: 100.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	db.Create(analytics)

	// Filtrer par date (hier à demain) - devrait inclure l'analytics
	filter := VariantFilter{
		TrackID:   &trackID,
		StartDate: &yesterday,
		EndDate:   &tomorrow,
	}
	result, err := service.getAnalyticsForVariant(ctx, filter)

	require.NoError(t, err)
	assert.Equal(t, 1, len(result))

	// Filtrer par date (avant-hier à hier) - ne devrait pas inclure l'analytics
	dayBeforeYesterday := now.AddDate(0, 0, -2)
	filter2 := VariantFilter{
		TrackID:   &trackID,
		StartDate: &dayBeforeYesterday,
		EndDate:   &yesterday,
	}
	result2, err := service.getAnalyticsForVariant(ctx, filter2)

	require.NoError(t, err)
	assert.Equal(t, 0, len(result2))
}

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

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

	now := time.Now()
	analytics1 := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         user1ID,
		PlayTime:       180,
		CompletionRate: 100.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	analytics2 := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         user2ID,
		PlayTime:       90,
		CompletionRate: 50.0,
		StartedAt:      now,
		CreatedAt:      now,
	}
	db.Create(analytics1)
	db.Create(analytics2)

	// Filtrer par user 1 seulement
	filter := VariantFilter{
		TrackID: &trackID,
		UserIDs: []uuid.UUID{user1ID},
	}
	result, err := service.getAnalyticsForVariant(ctx, filter)

	require.NoError(t, err)
	assert.Equal(t, 1, len(result))
	assert.Equal(t, user1ID, result[0].UserID)
}

func TestPlaybackABTestService_SafePercentageChange(t *testing.T) {
	_, service := setupTestPlaybackABTestServiceDB(t)

	// Test normal
	result := service.safePercentageChange(100.0, 120.0)
	assert.Equal(t, 20.0, result)

	// Test avec base zéro et courant non-zéro
	result2 := service.safePercentageChange(0.0, 100.0)
	assert.True(t, math.IsInf(result2, 1))

	// Test avec base zéro et courant zéro
	result3 := service.safePercentageChange(0.0, 0.0)
	assert.Equal(t, 0.0, result3)

	// Test négatif
	result4 := service.safePercentageChange(100.0, 80.0)
	assert.Equal(t, -20.0, result4)
}

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

	// Créer user et track
	userID := uuid.New()
	trackID := uuid.New()
	user := &models.User{ID: userID, Username: "testuser", Slug: "testuser", Email: "test@example.com", IsActive: true}
	db.Create(user)
	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()
	weekAgo := now.AddDate(0, 0, -7)
	threeDaysAgo := now.AddDate(0, 0, -3)

	// Analytics pour variant A (il y a une semaine)
	analyticsA := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       180,
		CompletionRate: 100.0,
		StartedAt:      weekAgo,
		CreatedAt:      weekAgo,
	}
	db.Create(analyticsA)

	// Analytics pour variant B (il y a 3 jours)
	analyticsB := &models.PlaybackAnalytics{
		TrackID:        trackID,
		UserID:         userID,
		PlayTime:       90,
		CompletionRate: 50.0,
		StartedAt:      threeDaysAgo,
		CreatedAt:      threeDaysAgo,
	}
	db.Create(analyticsB)

	// Filtrer variant A par période (il y a 8 jours à 6 jours)
	eightDaysAgo := now.AddDate(0, 0, -8)
	sixDaysAgo := now.AddDate(0, 0, -6)
	filterA := VariantFilter{
		TrackID:   &trackID,
		StartDate: &eightDaysAgo,
		EndDate:   &sixDaysAgo,
	}

	// Filtrer variant B par période (il y a 4 jours à 2 jours)
	fourDaysAgo := now.AddDate(0, 0, -4)
	twoDaysAgo := now.AddDate(0, 0, -2)
	filterB := VariantFilter{
		TrackID:   &trackID,
		StartDate: &fourDaysAgo,
		EndDate:   &twoDaysAgo,
	}

	result, err := service.CompareVariants(ctx, "A", "B", filterA, filterB)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, int64(1), result.VariantA.TotalSessions)
	assert.Equal(t, int64(1), result.VariantB.TotalSessions)
}
