package services

import (
	"context"
	"fmt"
	"math"
	"time"

	"veza-backend-api/internal/models"

	"github.com/google/uuid"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

// PlaybackABTestService gère le support A/B testing pour les analytics de lecture
// T0379: Create Playback Analytics A/B Testing Support
type PlaybackABTestService struct {
	db     *gorm.DB
	logger *zap.Logger
}

// NewPlaybackABTestService crée un nouveau service A/B testing
func NewPlaybackABTestService(db *gorm.DB, logger *zap.Logger) *PlaybackABTestService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &PlaybackABTestService{
		db:     db,
		logger: logger,
	}
}

// VariantFilter représente les critères de filtrage pour un variant
// GO-004: Migré vers UUID pour TrackID et UserIDs
type VariantFilter struct {
	TrackID     *uuid.UUID  `json:"track_id,omitempty"`
	StartDate   *time.Time  `json:"start_date,omitempty"`
	EndDate     *time.Time  `json:"end_date,omitempty"`
	UserIDs     []uuid.UUID `json:"user_ids,omitempty"`      // Liste d'IDs utilisateurs spécifiques
	MinPlayTime *int        `json:"min_play_time,omitempty"` // Filtre optionnel par temps de lecture minimum
}

// VariantStats représente les statistiques d'un variant
type VariantStats struct {
	VariantName       string  `json:"variant_name"`
	TotalSessions     int64   `json:"total_sessions"`
	TotalPlayTime     int64   `json:"total_play_time"`    // seconds
	AveragePlayTime   float64 `json:"average_play_time"`  // seconds
	AverageCompletion float64 `json:"average_completion"` // percentage
	CompletionRate    float64 `json:"completion_rate"`    // percentage of sessions with >90% completion
	AveragePauses     float64 `json:"average_pauses"`
	AverageSeeks      float64 `json:"average_seeks"`
}

// StatisticalSignificance représente la significativité statistique
type StatisticalSignificance struct {
	PValue                  float64 `json:"p_value"`                   // P-value (0-1)
	IsSignificant           bool    `json:"is_significant"`            // True si p-value < 0.05
	ConfidenceLevel         float64 `json:"confidence_level"`          // Niveau de confiance (95%, 99%, etc.)
	ConfidenceIntervalLower float64 `json:"confidence_interval_lower"` // Borne inférieure de l'intervalle de confiance
	ConfidenceIntervalUpper float64 `json:"confidence_interval_upper"` // Borne supérieure de l'intervalle de confiance
	EffectSize              float64 `json:"effect_size"`               // Taille de l'effet (Cohen's d)
}

// ABTestStatsDifference représente la différence absolue entre deux variants
type ABTestStatsDifference struct {
	TotalSessions     int64   `json:"total_sessions"`
	TotalPlayTime     int64   `json:"total_play_time"`   // seconds
	AveragePlayTime   float64 `json:"average_play_time"` // seconds
	TotalPauses       int64   `json:"total_pauses"`
	AveragePauses     float64 `json:"average_pauses"`
	TotalSeeks        int64   `json:"total_seeks"`
	AverageSeeks      float64 `json:"average_seeks"`
	AverageCompletion float64 `json:"average_completion"` // percentage
	CompletionRate    float64 `json:"completion_rate"`    // percentage
}

// ABTestPercentageChange représente le changement en pourcentage entre deux variants
type ABTestPercentageChange struct {
	TotalSessions     float64 `json:"total_sessions"`
	TotalPlayTime     float64 `json:"total_play_time"`
	AveragePlayTime   float64 `json:"average_play_time"`
	TotalPauses       float64 `json:"total_pauses"`
	AveragePauses     float64 `json:"average_pauses"`
	TotalSeeks        float64 `json:"total_seeks"`
	AverageSeeks      float64 `json:"average_seeks"`
	AverageCompletion float64 `json:"average_completion"`
	CompletionRate    float64 `json:"completion_rate"`
}

// ABTestResult représente le résultat d'un test A/B
type ABTestResult struct {
	VariantA         *VariantStats            `json:"variant_a"`
	VariantB         *VariantStats            `json:"variant_b"`
	Difference       *ABTestStatsDifference   `json:"difference"`
	PercentageChange *ABTestPercentageChange  `json:"percentage_change"`
	Significance     *StatisticalSignificance `json:"significance"`
	Winner           string                   `json:"winner,omitempty"`         // "A", "B", ou "inconclusive"
	Recommendation   string                   `json:"recommendation,omitempty"` // Recommandation basée sur les résultats
	AnalyzedAt       time.Time                `json:"analyzed_at"`
}

// CompareVariants compare deux variants et calcule la significativité statistique
// T0379: Create Playback Analytics A/B Testing Support
func (s *PlaybackABTestService) CompareVariants(ctx context.Context, variantA, variantB string, filterA, filterB VariantFilter) (*ABTestResult, error) {
	if variantA == "" || variantB == "" {
		return nil, fmt.Errorf("variant names cannot be empty")
	}

	// Récupérer les analytics pour le variant A
	analyticsA, err := s.getAnalyticsForVariant(ctx, filterA)
	if err != nil {
		return nil, fmt.Errorf("failed to get analytics for variant A: %w", err)
	}

	// Récupérer les analytics pour le variant B
	analyticsB, err := s.getAnalyticsForVariant(ctx, filterB)
	if err != nil {
		return nil, fmt.Errorf("failed to get analytics for variant B: %w", err)
	}

	// Calculer les statistiques pour chaque variant
	statsA := s.calculateVariantStats(variantA, analyticsA)
	statsB := s.calculateVariantStats(variantB, analyticsB)

	// Calculer les différences
	difference := s.calculateDifference(statsA, statsB)
	percentageChange := s.calculatePercentageChange(statsA, statsB)

	// Calculer la significativité statistique
	significance := s.calculateStatisticalSignificance(analyticsA, analyticsB)

	// Déterminer le gagnant
	winner := s.determineWinner(statsA, statsB, significance)
	recommendation := s.generateRecommendation(statsA, statsB, significance)

	result := &ABTestResult{
		VariantA:         statsA,
		VariantB:         statsB,
		Difference:       difference,
		PercentageChange: percentageChange,
		Significance:     significance,
		Winner:           winner,
		Recommendation:   recommendation,
		AnalyzedAt:       time.Now(),
	}

	s.logger.Info("Compared A/B test variants",
		zap.String("variant_a", variantA),
		zap.String("variant_b", variantB),
		zap.Int64("sessions_a", statsA.TotalSessions),
		zap.Int64("sessions_b", statsB.TotalSessions),
		zap.Bool("significant", significance.IsSignificant),
		zap.String("winner", winner))

	return result, nil
}

// getAnalyticsForVariant récupère les analytics pour un variant selon les filtres
func (s *PlaybackABTestService) getAnalyticsForVariant(ctx context.Context, filter VariantFilter) ([]models.PlaybackAnalytics, error) {
	query := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{})

	if filter.TrackID != nil && *filter.TrackID != uuid.Nil {
		query = query.Where("track_id = ?", *filter.TrackID)
	}

	if filter.StartDate != nil {
		query = query.Where("created_at >= ?", *filter.StartDate)
	}

	if filter.EndDate != nil {
		query = query.Where("created_at <= ?", *filter.EndDate)
	}

	if len(filter.UserIDs) > 0 {
		query = query.Where("user_id IN ?", filter.UserIDs)
	}

	if filter.MinPlayTime != nil && *filter.MinPlayTime > 0 {
		query = query.Where("play_time >= ?", *filter.MinPlayTime)
	}

	var analytics []models.PlaybackAnalytics
	if err := query.Find(&analytics).Error; err != nil {
		return nil, fmt.Errorf("failed to query analytics: %w", err)
	}

	return analytics, nil
}

// calculateVariantStats calcule les statistiques pour un variant
func (s *PlaybackABTestService) calculateVariantStats(variantName string, analytics []models.PlaybackAnalytics) *VariantStats {
	if len(analytics) == 0 {
		return &VariantStats{
			VariantName: variantName,
		}
	}

	var totalSessions int64
	var totalPlayTime int64
	var totalCompletion float64
	var totalPauses int64
	var totalSeeks int64
	var completedSessions int64

	for _, a := range analytics {
		totalSessions++
		totalPlayTime += int64(a.PlayTime)
		totalCompletion += a.CompletionRate
		totalPauses += int64(a.PauseCount)
		totalSeeks += int64(a.SeekCount)
		if a.CompletionRate >= 90.0 {
			completedSessions++
		}
	}

	sessionCount := float64(totalSessions)
	stats := &VariantStats{
		VariantName:       variantName,
		TotalSessions:     totalSessions,
		TotalPlayTime:     totalPlayTime,
		AveragePlayTime:   float64(totalPlayTime) / sessionCount,
		AverageCompletion: totalCompletion / sessionCount,
		CompletionRate:    float64(completedSessions) / sessionCount * 100.0,
		AveragePauses:     float64(totalPauses) / sessionCount,
		AverageSeeks:      float64(totalSeeks) / sessionCount,
	}

	return stats
}

// calculateDifference calcule la différence absolue entre deux variants
func (s *PlaybackABTestService) calculateDifference(statsA, statsB *VariantStats) *ABTestStatsDifference {
	return &ABTestStatsDifference{
		TotalSessions:     statsB.TotalSessions - statsA.TotalSessions,
		TotalPlayTime:     statsB.TotalPlayTime - statsA.TotalPlayTime,
		AveragePlayTime:   statsB.AveragePlayTime - statsA.AveragePlayTime,
		TotalPauses:       int64(statsB.AveragePauses*float64(statsB.TotalSessions)) - int64(statsA.AveragePauses*float64(statsA.TotalSessions)),
		AveragePauses:     statsB.AveragePauses - statsA.AveragePauses,
		TotalSeeks:        int64(statsB.AverageSeeks*float64(statsB.TotalSessions)) - int64(statsA.AverageSeeks*float64(statsA.TotalSessions)),
		AverageSeeks:      statsB.AverageSeeks - statsA.AverageSeeks,
		AverageCompletion: statsB.AverageCompletion - statsA.AverageCompletion,
		CompletionRate:    statsB.CompletionRate - statsA.CompletionRate,
	}
}

// calculatePercentageChange calcule le changement en pourcentage entre deux variants
func (s *PlaybackABTestService) calculatePercentageChange(statsA, statsB *VariantStats) *ABTestPercentageChange {
	return &ABTestPercentageChange{
		TotalSessions:     s.safePercentageChange(float64(statsA.TotalSessions), float64(statsB.TotalSessions)),
		TotalPlayTime:     s.safePercentageChange(float64(statsA.TotalPlayTime), float64(statsB.TotalPlayTime)),
		AveragePlayTime:   s.safePercentageChange(statsA.AveragePlayTime, statsB.AveragePlayTime),
		TotalPauses:       s.safePercentageChange(statsA.AveragePauses*float64(statsA.TotalSessions), statsB.AveragePauses*float64(statsB.TotalSessions)),
		AveragePauses:     s.safePercentageChange(statsA.AveragePauses, statsB.AveragePauses),
		TotalSeeks:        s.safePercentageChange(statsA.AverageSeeks*float64(statsA.TotalSessions), statsB.AverageSeeks*float64(statsB.TotalSessions)),
		AverageSeeks:      s.safePercentageChange(statsA.AverageSeeks, statsB.AverageSeeks),
		AverageCompletion: s.safePercentageChange(statsA.AverageCompletion, statsB.AverageCompletion),
		CompletionRate:    s.safePercentageChange(statsA.CompletionRate, statsB.CompletionRate),
	}
}

// safePercentageChange calcule le changement en pourcentage en gérant la division par zéro
func (s *PlaybackABTestService) safePercentageChange(base, current float64) float64 {
	if base == 0 {
		if current == 0 {
			return 0.0
		}
		return math.Inf(1) // Infini si la base est zéro et le courant est non-zéro
	}
	return ((current - base) / base) * 100.0
}

// calculateStatisticalSignificance calcule la significativité statistique entre deux variants
// Utilise un test t de Student pour comparer les moyennes de completion rate
func (s *PlaybackABTestService) calculateStatisticalSignificance(analyticsA, analyticsB []models.PlaybackAnalytics) *StatisticalSignificance {
	if len(analyticsA) == 0 || len(analyticsB) == 0 {
		return &StatisticalSignificance{
			PValue:          1.0,
			IsSignificant:   false,
			ConfidenceLevel: 95.0,
			EffectSize:      0.0,
		}
	}

	// Extraire les completion rates
	completionRatesA := make([]float64, len(analyticsA))
	for i, a := range analyticsA {
		completionRatesA[i] = a.CompletionRate
	}

	completionRatesB := make([]float64, len(analyticsB))
	for i, a := range analyticsB {
		completionRatesB[i] = a.CompletionRate
	}

	// Calculer les moyennes et écarts-types
	meanA, stdDevA := s.calculateMeanAndStdDev(completionRatesA)
	meanB, stdDevB := s.calculateMeanAndStdDev(completionRatesB)

	// Calculer le test t de Student
	pValue := s.calculateTTest(completionRatesA, completionRatesB, meanA, meanB, stdDevA, stdDevB)

	// Calculer l'intervalle de confiance à 95%
	confidenceLevel := 95.0
	seA := stdDevA / math.Sqrt(float64(len(completionRatesA)))
	seB := stdDevB / math.Sqrt(float64(len(completionRatesB)))
	tValue := 1.96 // Pour un intervalle de confiance à 95%

	diff := meanB - meanA
	seDiff := math.Sqrt(seA*seA + seB*seB)
	confidenceIntervalLower := diff - tValue*seDiff
	confidenceIntervalUpper := diff + tValue*seDiff

	// Calculer la taille de l'effet (Cohen's d)
	pooledStdDev := math.Sqrt((stdDevA*stdDevA + stdDevB*stdDevB) / 2.0)
	effectSize := 0.0
	if pooledStdDev > 0 {
		effectSize = (meanB - meanA) / pooledStdDev
	}

	return &StatisticalSignificance{
		PValue:                  pValue,
		IsSignificant:           pValue < 0.05,
		ConfidenceLevel:         confidenceLevel,
		ConfidenceIntervalLower: confidenceIntervalLower,
		ConfidenceIntervalUpper: confidenceIntervalUpper,
		EffectSize:              effectSize,
	}
}

// calculateMeanAndStdDev calcule la moyenne et l'écart-type
func (s *PlaybackABTestService) calculateMeanAndStdDev(data []float64) (mean, stdDev float64) {
	if len(data) == 0 {
		return 0, 0
	}

	// Calcul de la moyenne
	var sum float64
	for _, v := range data {
		sum += v
	}
	mean = sum / float64(len(data))

	// Calcul de l'écart-type
	var sumSqDiff float64
	for _, v := range data {
		diff := v - mean
		sumSqDiff += diff * diff
	}
	if len(data) > 1 {
		stdDev = math.Sqrt(sumSqDiff / float64(len(data)-1)) // Échantillon
	} else {
		stdDev = 0
	}

	return mean, stdDev
}

// calculateTTest calcule la p-value d'un test t de Student
// Approximation simplifiée pour deux échantillons indépendants
func (s *PlaybackABTestService) calculateTTest(dataA, dataB []float64, meanA, meanB, stdDevA, stdDevB float64) float64 {
	nA := float64(len(dataA))
	nB := float64(len(dataB))

	if nA < 2 || nB < 2 {
		return 1.0 // Pas assez de données pour un test significatif
	}

	// Calcul de l'erreur standard de la différence
	seA := stdDevA / math.Sqrt(nA)
	seB := stdDevB / math.Sqrt(nB)
	seDiff := math.Sqrt(seA*seA + seB*seB)

	if seDiff == 0 {
		return 1.0
	}

	// Calcul de la statistique t
	tStat := (meanB - meanA) / seDiff

	// Calcul des degrés de liberté (approximation de Welch)
	_ = s.calculateWelchDF(seA, seB, nA, nB) // Calculé mais non utilisé dans l'approximation normale

	// Approximation de la p-value (test bilatéral)
	// Utilisation d'une approximation normale pour simplifier
	// En production, on utiliserait une table t ou une fonction de distribution
	pValue := 2.0 * (1.0 - s.normalCDF(math.Abs(tStat)))

	return pValue
}

// calculateWelchDF calcule les degrés de liberté pour le test t de Welch
func (s *PlaybackABTestService) calculateWelchDF(seA, seB, nA, nB float64) float64 {
	if seA == 0 && seB == 0 {
		return nA + nB - 2
	}
	if seA == 0 {
		return nB - 1
	}
	if seB == 0 {
		return nA - 1
	}

	numerator := math.Pow(seA*seA+seB*seB, 2)
	denominator := math.Pow(seA*seA, 2)/(nA-1) + math.Pow(seB*seB, 2)/(nB-1)

	if denominator == 0 {
		return nA + nB - 2
	}

	return numerator / denominator
}

// normalCDF calcule la fonction de répartition cumulative de la distribution normale standard
// Approximation utilisant la fonction d'erreur
func (s *PlaybackABTestService) normalCDF(x float64) float64 {
	return 0.5 * (1.0 + s.erf(x/math.Sqrt2))
}

// erf calcule la fonction d'erreur (approximation)
func (s *PlaybackABTestService) erf(x float64) float64 {
	// Approximation de la fonction d'erreur
	// Formule d'Abramowitz et Stegun
	a1 := 0.254829592
	a2 := -0.284496736
	a3 := 1.421413741
	a4 := -1.453152027
	a5 := 1.061405429
	p := 0.3275911

	sign := 1.0
	if x < 0 {
		sign = -1.0
		x = -x
	}

	t := 1.0 / (1.0 + p*x)
	y := 1.0 - (((((a5*t+a4)*t)+a3)*t+a2)*t+a1)*t*math.Exp(-x*x)

	return sign * y
}

// determineWinner détermine le gagnant du test A/B
func (s *PlaybackABTestService) determineWinner(statsA, statsB *VariantStats, significance *StatisticalSignificance) string {
	if !significance.IsSignificant {
		return "inconclusive"
	}

	// Le gagnant est déterminé par le completion rate le plus élevé
	if statsB.CompletionRate > statsA.CompletionRate {
		return "B"
	} else if statsA.CompletionRate > statsB.CompletionRate {
		return "A"
	}

	return "inconclusive"
}

// generateRecommendation génère une recommandation basée sur les résultats
func (s *PlaybackABTestService) generateRecommendation(statsA, statsB *VariantStats, significance *StatisticalSignificance) string {
	if !significance.IsSignificant {
		return "Les résultats ne sont pas statistiquement significatifs. Continuer le test ou augmenter la taille de l'échantillon."
	}

	if statsB.CompletionRate > statsA.CompletionRate {
		improvement := ((statsB.CompletionRate - statsA.CompletionRate) / statsA.CompletionRate) * 100.0
		return fmt.Sprintf("Le variant B est significativement meilleur avec une amélioration de %.2f%% du taux de complétion.", improvement)
	} else if statsA.CompletionRate > statsB.CompletionRate {
		improvement := ((statsA.CompletionRate - statsB.CompletionRate) / statsB.CompletionRate) * 100.0
		return fmt.Sprintf("Le variant A est significativement meilleur avec une amélioration de %.2f%% du taux de complétion.", improvement)
	}

	return "Aucune différence significative entre les variants."
}
