package services

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

	"github.com/google/uuid"

	"veza-backend-api/internal/models"

	"go.uber.org/zap"
	"gorm.io/gorm"
)

// PlaybackAlertsService gère la détection d'alertes pour les analytics de lecture
// T0374: Create Playback Analytics Alerts Service
type PlaybackAlertsService struct {
	db     *gorm.DB
	logger *zap.Logger
}

// Alert représente une alerte détectée
type Alert struct {
	Type       string                 `json:"type"`               // "anomaly", "low_completion_rate", "drop_off_point"
	Severity   string                 `json:"severity"`           // "low", "medium", "high"
	Message    string                 `json:"message"`            // Message descriptif
	Value      float64                `json:"value"`              // Valeur qui a déclenché l'alerte
	Threshold  float64                `json:"threshold"`          // Seuil utilisé
	DetectedAt time.Time              `json:"detected_at"`        // Date de détection
	Metadata   map[string]interface{} `json:"metadata,omitempty"` // Métadonnées supplémentaires
}

// AlertConfig représente la configuration des seuils d'alerte
type AlertConfig struct {
	LowCompletionRateThreshold float64 // Seuil pour completion rate bas (défaut: 30%)
	AnomalyDeviationThreshold  float64 // Nombre d'écarts-types pour détecter une anomalie (défaut: 2.0)
	DropOffPointThreshold      float64 // Seuil de drop-off en pourcentage de la durée (défaut: 25%)
}

// NewPlaybackAlertsService crée un nouveau service d'alertes d'analytics
func NewPlaybackAlertsService(db *gorm.DB, logger *zap.Logger) *PlaybackAlertsService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &PlaybackAlertsService{
		db:     db,
		logger: logger,
	}
}

// CheckAlerts vérifie les alertes pour un track donné
// T0374: Create Playback Analytics Alerts Service
func (s *PlaybackAlertsService) CheckAlerts(ctx context.Context, trackID uuid.UUID, config *AlertConfig) ([]Alert, error) {
	if trackID == uuid.Nil {
		return nil, fmt.Errorf("invalid track ID: %s", trackID)
	}

	// Utiliser la configuration par défaut si non fournie
	if config == nil {
		config = &AlertConfig{
			LowCompletionRateThreshold: 30.0,
			AnomalyDeviationThreshold:  2.0,
			DropOffPointThreshold:      25.0,
		}
	}

	// Vérifier que le track existe
	var track models.Track
	if err := s.db.WithContext(ctx).First(&track, trackID).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("track not found: %d", trackID)
		}
		return nil, fmt.Errorf("failed to get track: %w", err)
	}

	alerts := make([]Alert, 0)

	// Détecter les anomalies
	anomalyAlerts, err := s.detectAnomalies(ctx, trackID, config)
	if err != nil {
		s.logger.Warn("Failed to detect anomalies", zap.Error(err), zap.String("track_id", trackID.String()))
	} else {
		alerts = append(alerts, anomalyAlerts...)
	}

	// Détecter les completion rates bas
	completionAlerts, err := s.detectLowCompletionRate(ctx, trackID, config)
	if err != nil {
		s.logger.Warn("Failed to detect low completion rates", zap.Error(err), zap.String("track_id", trackID.String()))
	} else {
		alerts = append(alerts, completionAlerts...)
	}

	// Détecter les drop-off points
	dropOffAlerts, err := s.detectDropOffPoints(ctx, trackID, config)
	if err != nil {
		s.logger.Warn("Failed to detect drop-off points", zap.Error(err), zap.String("track_id", trackID.String()))
	} else {
		alerts = append(alerts, dropOffAlerts...)
	}

	s.logger.Info("Checked playback alerts",
		zap.String("track_id", trackID.String()),
		zap.Int("alerts_count", len(alerts)))

	return alerts, nil
}

// detectAnomalies détecte les anomalies dans les statistiques de lecture
func (s *PlaybackAlertsService) detectAnomalies(ctx context.Context, trackID uuid.UUID, config *AlertConfig) ([]Alert, error) {
	var alerts []Alert

	// Récupérer toutes les analytics récentes (30 derniers jours)
	thirtyDaysAgo := time.Now().AddDate(0, 0, -30)
	var analytics []models.PlaybackAnalytics
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND created_at >= ?", trackID, thirtyDaysAgo).
		Find(&analytics).Error; err != nil {
		return nil, fmt.Errorf("failed to get analytics: %w", err)
	}

	if len(analytics) < 10 {
		// Pas assez de données pour détecter des anomalies
		return alerts, nil
	}

	// Calculer la moyenne et l'écart-type pour le play_time
	var playTimes []float64
	var completionRates []float64
	for _, a := range analytics {
		playTimes = append(playTimes, float64(a.PlayTime))
		completionRates = append(completionRates, a.CompletionRate)
	}

	// Détecter les anomalies dans le play_time
	playTimeMean, playTimeStdDev := s.calculateMeanAndStdDev(playTimes)
	for _, a := range analytics {
		playTime := float64(a.PlayTime)
		deviation := math.Abs(playTime - playTimeMean)
		if playTimeStdDev > 0 && deviation > config.AnomalyDeviationThreshold*playTimeStdDev {
			severity := "medium"
			if deviation > config.AnomalyDeviationThreshold*2*playTimeStdDev {
				severity = "high"
			}
			alerts = append(alerts, Alert{
				Type:       "anomaly",
				Severity:   severity,
				Message:    fmt.Sprintf("Anomalous play time detected: %.0f seconds (mean: %.0f, std dev: %.0f)", playTime, playTimeMean, playTimeStdDev),
				Value:      playTime,
				Threshold:  playTimeMean + config.AnomalyDeviationThreshold*playTimeStdDev,
				DetectedAt: time.Now(),
				Metadata: map[string]interface{}{
					"analytics_id": a.ID,
					"user_id":      a.UserID,
					"mean":         playTimeMean,
					"std_dev":      playTimeStdDev,
					"deviation":    deviation,
				},
			})
		}
	}

	// Détecter les anomalies dans le completion rate
	completionMean, completionStdDev := s.calculateMeanAndStdDev(completionRates)
	for _, a := range analytics {
		deviation := math.Abs(a.CompletionRate - completionMean)
		if completionStdDev > 0 && deviation > config.AnomalyDeviationThreshold*completionStdDev {
			severity := "medium"
			if deviation > config.AnomalyDeviationThreshold*2*completionStdDev {
				severity = "high"
			}
			alerts = append(alerts, Alert{
				Type:       "anomaly",
				Severity:   severity,
				Message:    fmt.Sprintf("Anomalous completion rate detected: %.2f%% (mean: %.2f%%, std dev: %.2f%%)", a.CompletionRate, completionMean, completionStdDev),
				Value:      a.CompletionRate,
				Threshold:  completionMean + config.AnomalyDeviationThreshold*completionStdDev,
				DetectedAt: time.Now(),
				Metadata: map[string]interface{}{
					"analytics_id": a.ID,
					"user_id":      a.UserID,
					"mean":         completionMean,
					"std_dev":      completionStdDev,
					"deviation":    deviation,
				},
			})
		}
	}

	return alerts, nil
}

// detectLowCompletionRate détecte les completion rates bas
func (s *PlaybackAlertsService) detectLowCompletionRate(ctx context.Context, trackID uuid.UUID, config *AlertConfig) ([]Alert, error) {
	var alerts []Alert

	// Récupérer les statistiques récentes (7 derniers jours)
	sevenDaysAgo := time.Now().AddDate(0, 0, -7)
	var analytics []models.PlaybackAnalytics
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND created_at >= ?", trackID, sevenDaysAgo).
		Find(&analytics).Error; err != nil {
		return nil, fmt.Errorf("failed to get analytics: %w", err)
	}

	if len(analytics) == 0 {
		return alerts, nil
	}

	// Calculer le taux de completion moyen
	var totalCompletion float64
	var lowCompletionCount int
	for _, a := range analytics {
		totalCompletion += a.CompletionRate
		if a.CompletionRate < config.LowCompletionRateThreshold {
			lowCompletionCount++
		}
	}
	averageCompletion := totalCompletion / float64(len(analytics))

	// Si le taux moyen est bas, créer une alerte
	if averageCompletion < config.LowCompletionRateThreshold {
		severity := "medium"
		if averageCompletion < config.LowCompletionRateThreshold/2 {
			severity = "high"
		}
		alerts = append(alerts, Alert{
			Type:       "low_completion_rate",
			Severity:   severity,
			Message:    fmt.Sprintf("Low average completion rate: %.2f%% (threshold: %.2f%%)", averageCompletion, config.LowCompletionRateThreshold),
			Value:      averageCompletion,
			Threshold:  config.LowCompletionRateThreshold,
			DetectedAt: time.Now(),
			Metadata: map[string]interface{}{
				"total_sessions":       len(analytics),
				"low_completion_count": lowCompletionCount,
				"percentage_low":       float64(lowCompletionCount) / float64(len(analytics)) * 100.0,
			},
		})
	}

	// Si un pourcentage élevé de sessions a un completion rate bas, créer une alerte
	lowCompletionPercentage := float64(lowCompletionCount) / float64(len(analytics)) * 100.0
	if lowCompletionPercentage > 50.0 {
		severity := "medium"
		if lowCompletionPercentage > 75.0 {
			severity = "high"
		}
		alerts = append(alerts, Alert{
			Type:       "low_completion_rate",
			Severity:   severity,
			Message:    fmt.Sprintf("High percentage of sessions with low completion rate: %.2f%%", lowCompletionPercentage),
			Value:      lowCompletionPercentage,
			Threshold:  50.0,
			DetectedAt: time.Now(),
			Metadata: map[string]interface{}{
				"total_sessions":       len(analytics),
				"low_completion_count": lowCompletionCount,
				"average_completion":   averageCompletion,
			},
		})
	}

	return alerts, nil
}

// detectDropOffPoints détecte les points de drop-off (moments où les utilisateurs arrêtent de regarder)
func (s *PlaybackAlertsService) detectDropOffPoints(ctx context.Context, trackID uuid.UUID, config *AlertConfig) ([]Alert, error) {
	var alerts []Alert

	// Récupérer le track pour connaître sa durée
	var track models.Track
	if err := s.db.WithContext(ctx).First(&track, trackID).Error; err != nil {
		return nil, fmt.Errorf("failed to get track: %w", err)
	}

	if track.Duration <= 0 {
		return alerts, nil
	}

	// Récupérer les analytics récentes (7 derniers jours)
	sevenDaysAgo := time.Now().AddDate(0, 0, -7)
	var analytics []models.PlaybackAnalytics
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND created_at >= ?", trackID, sevenDaysAgo).
		Find(&analytics).Error; err != nil {
		return nil, fmt.Errorf("failed to get analytics: %w", err)
	}

	if len(analytics) == 0 {
		return alerts, nil
	}

	// Calculer le pourcentage de la durée où les utilisateurs arrêtent
	dropOffThresholdSeconds := float64(track.Duration) * (config.DropOffPointThreshold / 100.0)
	var dropOffCount int
	var dropOffTimes []float64

	for _, a := range analytics {
		// Si le play_time est inférieur au seuil de drop-off, c'est un drop-off
		if float64(a.PlayTime) < dropOffThresholdSeconds {
			dropOffCount++
			dropOffTimes = append(dropOffTimes, float64(a.PlayTime))
		}
	}

	dropOffPercentage := float64(dropOffCount) / float64(len(analytics)) * 100.0

	// Si un pourcentage significatif de sessions s'arrête tôt, créer une alerte
	if dropOffPercentage > 30.0 {
		// Calculer le temps moyen de drop-off
		var avgDropOffTime float64
		if len(dropOffTimes) > 0 {
			var sum float64
			for _, t := range dropOffTimes {
				sum += t
			}
			avgDropOffTime = sum / float64(len(dropOffTimes))
		}

		severity := "medium"
		if dropOffPercentage > 50.0 {
			severity = "high"
		}

		dropOffPointPercentage := (avgDropOffTime / float64(track.Duration)) * 100.0

		alerts = append(alerts, Alert{
			Type:       "drop_off_point",
			Severity:   severity,
			Message:    fmt.Sprintf("Drop-off detected: %.2f%% of sessions stop before %.2f%% of track duration (avg drop-off at %.2f%%)", dropOffPercentage, config.DropOffPointThreshold, dropOffPointPercentage),
			Value:      dropOffPercentage,
			Threshold:  30.0,
			DetectedAt: time.Now(),
			Metadata: map[string]interface{}{
				"total_sessions":            len(analytics),
				"drop_off_count":            dropOffCount,
				"drop_off_threshold":        config.DropOffPointThreshold,
				"average_drop_off_time":     avgDropOffTime,
				"drop_off_point_percentage": dropOffPointPercentage,
				"track_duration":            track.Duration,
			},
		})
	}

	return alerts, nil
}

// calculateMeanAndStdDev calcule la moyenne et l'écart-type d'une série de valeurs
func (s *PlaybackAlertsService) calculateMeanAndStdDev(values []float64) (mean, stdDev float64) {
	if len(values) == 0 {
		return 0, 0
	}

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

	// Calculer l'écart-type
	var variance float64
	for _, v := range values {
		diff := v - mean
		variance += diff * diff
	}
	variance = variance / float64(len(values))
	stdDev = math.Sqrt(variance)

	return mean, stdDev
}
