package services

import (
	"context"
	"fmt"
	"time"

	"github.com/google/uuid"

	"veza-backend-api/internal/models"

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

// PlaybackRetentionService gère l'analyse de rétention des analytics de lecture
// T0375: Create Playback Analytics Retention Analysis
type PlaybackRetentionService struct {
	db     *gorm.DB
	logger *zap.Logger
}

// NewPlaybackRetentionService crée un nouveau service d'analyse de rétention
func NewPlaybackRetentionService(db *gorm.DB, logger *zap.Logger) *PlaybackRetentionService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &PlaybackRetentionService{
		db:     db,
		logger: logger,
	}
}

// SegmentRetention représente la rétention pour un segment du track
type SegmentRetention struct {
	SegmentStart    float64 `json:"segment_start"`     // Pourcentage de début du segment (0-100)
	SegmentEnd      float64 `json:"segment_end"`       // Pourcentage de fin du segment (0-100)
	RetentionRate   float64 `json:"retention_rate"`    // Pourcentage d'utilisateurs qui atteignent ce segment
	ExitCount       int64   `json:"exit_count"`        // Nombre d'utilisateurs qui sortent dans ce segment
	ExitRate        float64 `json:"exit_rate"`         // Pourcentage d'utilisateurs qui sortent dans ce segment
	AveragePlayTime float64 `json:"average_play_time"` // Temps de lecture moyen dans ce segment (secondes)
}

// ExitPoint représente un point de sortie identifié
type ExitPoint struct {
	SegmentStart    float64 `json:"segment_start"`     // Pourcentage de début du segment
	SegmentEnd      float64 `json:"segment_end"`       // Pourcentage de fin du segment
	ExitCount       int64   `json:"exit_count"`        // Nombre de sorties
	ExitRate        float64 `json:"exit_rate"`         // Taux de sortie (%)
	TotalSessions   int64   `json:"total_sessions"`    // Nombre total de sessions
	AveragePlayTime float64 `json:"average_play_time"` // Temps de lecture moyen avant sortie
}

// EngagementMetrics représente les métriques d'engagement
type EngagementMetrics struct {
	OverallRetentionRate float64 `json:"overall_retention_rate"` // Taux de rétention global (%)
	EngagementScore      float64 `json:"engagement_score"`       // Score d'engagement (0-100)
	AverageCompletion    float64 `json:"average_completion"`     // Taux de complétion moyen (%)
	HighEngagementRate   float64 `json:"high_engagement_rate"`   // Pourcentage de sessions avec engagement élevé (>75% completion)
	LowEngagementRate    float64 `json:"low_engagement_rate"`    // Pourcentage de sessions avec engagement faible (<25% completion)
	AveragePauses        float64 `json:"average_pauses"`         // Nombre moyen de pauses
	AverageSeeks         float64 `json:"average_seeks"`          // Nombre moyen de seeks
}

// RetentionAnalysisResult représente le résultat complet de l'analyse de rétention
type RetentionAnalysisResult struct {
	TrackID           uuid.UUID          `json:"track_id"`
	TrackDuration     int                `json:"track_duration"` // secondes
	TotalSessions     int64              `json:"total_sessions"`
	SegmentRetentions []SegmentRetention `json:"segment_retentions"`
	ExitPoints        []ExitPoint        `json:"exit_points"`
	EngagementMetrics EngagementMetrics  `json:"engagement_metrics"`
	AnalyzedAt        time.Time          `json:"analyzed_at"`
}

// AnalyzeRetention analyse la rétention pour un track
// T0375: Create Playback Analytics Retention Analysis
func (s *PlaybackRetentionService) AnalyzeRetention(ctx context.Context, trackID uuid.UUID, segmentCount int) (*RetentionAnalysisResult, error) {
	if trackID == uuid.Nil {
		return nil, fmt.Errorf("invalid track ID: %s", trackID)
	}

	if segmentCount <= 0 {
		segmentCount = 10 // Par défaut, 10 segments
	}
	if segmentCount > 100 {
		segmentCount = 100 // Maximum 100 segments
	}

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

	if track.Duration <= 0 {
		return nil, fmt.Errorf("track has invalid duration: %d", track.Duration)
	}

	// Récupérer toutes les analytics pour ce track
	var analytics []models.PlaybackAnalytics
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ?", trackID).
		Find(&analytics).Error; err != nil {
		return nil, fmt.Errorf("failed to get analytics: %w", err)
	}

	// Initialiser les segments même s'il n'y a pas de sessions
	segmentRetentions := make([]SegmentRetention, segmentCount)
	segmentSize := 100.0 / float64(segmentCount)
	for i := 0; i < segmentCount; i++ {
		segmentRetentions[i] = SegmentRetention{
			SegmentStart:    float64(i) * segmentSize,
			SegmentEnd:      float64(i)*segmentSize + segmentSize,
			RetentionRate:   0.0,
			ExitCount:       0,
			ExitRate:        0.0,
			AveragePlayTime: 0.0,
		}
	}

	if len(analytics) == 0 {
		// Retourner un résultat avec segments initialisés mais vides
		return &RetentionAnalysisResult{
			TrackID:           trackID,
			TrackDuration:     track.Duration,
			TotalSessions:     0,
			SegmentRetentions: segmentRetentions,
			ExitPoints:        []ExitPoint{},
			EngagementMetrics: EngagementMetrics{},
			AnalyzedAt:        time.Now(),
		}, nil
	}

	// Calculer la rétention par segment
	segmentRetentions = s.calculateSegmentRetention(analytics, track.Duration, segmentCount)

	// Identifier les points de sortie
	exitPoints := s.identifyExitPoints(analytics, segmentCount)

	// Analyser l'engagement
	engagementMetrics := s.analyzeEngagement(analytics)

	result := &RetentionAnalysisResult{
		TrackID:           trackID,
		TrackDuration:     track.Duration,
		TotalSessions:     int64(len(analytics)),
		SegmentRetentions: segmentRetentions,
		ExitPoints:        exitPoints,
		EngagementMetrics: engagementMetrics,
		AnalyzedAt:        time.Now(),
	}

	s.logger.Info("Analyzed playback retention",
		zap.String("track_id", trackID.String()),
		zap.Int("total_sessions", len(analytics)),
		zap.Int("segments", segmentCount))

	return result, nil
}

// calculateSegmentRetention calcule la rétention par segment
func (s *PlaybackRetentionService) calculateSegmentRetention(analytics []models.PlaybackAnalytics, trackDuration int, segmentCount int) []SegmentRetention {
	segmentSize := 100.0 / float64(segmentCount)
	retentions := make([]SegmentRetention, segmentCount)
	totalSessions := float64(len(analytics))

	// Pour chaque segment
	for i := 0; i < segmentCount; i++ {
		segmentStart := float64(i) * segmentSize
		segmentEnd := segmentStart + segmentSize

		// Calculer le temps de lecture minimum pour atteindre ce segment
		segmentStartSeconds := (segmentStart / 100.0) * float64(trackDuration)
		segmentEndSeconds := (segmentEnd / 100.0) * float64(trackDuration)

		// Compter les sessions qui atteignent ce segment
		var reachedCount int64
		var exitCount int64
		var totalPlayTimeInSegment float64
		var sessionsInSegment int64

		for _, a := range analytics {
			playTimeSeconds := float64(a.PlayTime)

			// Vérifier si la session atteint le début du segment
			if playTimeSeconds >= segmentStartSeconds {
				reachedCount++

				// Vérifier si la session sort dans ce segment
				if playTimeSeconds >= segmentStartSeconds && playTimeSeconds < segmentEndSeconds {
					exitCount++
				}

				// Calculer le temps de lecture dans ce segment
				if playTimeSeconds >= segmentStartSeconds {
					segmentPlayTime := playTimeSeconds - segmentStartSeconds
					if segmentPlayTime > segmentSize/100.0*float64(trackDuration) {
						segmentPlayTime = segmentSize / 100.0 * float64(trackDuration)
					}
					totalPlayTimeInSegment += segmentPlayTime
					sessionsInSegment++
				}
			}
		}

		// Calculer les taux
		retentionRate := 0.0
		if totalSessions > 0 {
			retentionRate = float64(reachedCount) / totalSessions * 100.0
		}

		exitRate := 0.0
		if reachedCount > 0 {
			exitRate = float64(exitCount) / float64(reachedCount) * 100.0
		}

		averagePlayTime := 0.0
		if sessionsInSegment > 0 {
			averagePlayTime = totalPlayTimeInSegment / float64(sessionsInSegment)
		}

		retentions[i] = SegmentRetention{
			SegmentStart:    segmentStart,
			SegmentEnd:      segmentEnd,
			RetentionRate:   retentionRate,
			ExitCount:       exitCount,
			ExitRate:        exitRate,
			AveragePlayTime: averagePlayTime,
		}
	}

	return retentions
}

// identifyExitPoints identifie les points de sortie principaux
func (s *PlaybackRetentionService) identifyExitPoints(analytics []models.PlaybackAnalytics, segmentCount int) []ExitPoint {
	segmentSize := 100.0 / float64(segmentCount)
	exitPointsMap := make(map[int]*ExitPoint)
	totalSessions := int64(len(analytics))

	// Pour chaque session, identifier le segment où elle sort
	for _, a := range analytics {
		playTimeSeconds := float64(a.PlayTime)
		_ = playTimeSeconds // Utilisé pour les calculs futurs si nécessaire

		// Déterminer dans quel segment la session se termine
		segmentIndex := int((a.CompletionRate / 100.0) * float64(segmentCount))
		if segmentIndex >= segmentCount {
			segmentIndex = segmentCount - 1
		}

		if exitPointsMap[segmentIndex] == nil {
			segmentStart := float64(segmentIndex) * segmentSize
			segmentEnd := segmentStart + segmentSize
			exitPointsMap[segmentIndex] = &ExitPoint{
				SegmentStart:    segmentStart,
				SegmentEnd:      segmentEnd,
				ExitCount:       0,
				TotalSessions:   totalSessions,
				AveragePlayTime: 0.0,
			}
		}

		exitPoint := exitPointsMap[segmentIndex]
		exitPoint.ExitCount++
		exitPoint.AveragePlayTime += playTimeSeconds
	}

	// Calculer les moyennes et taux
	var exitPoints []ExitPoint
	for _, ep := range exitPointsMap {
		if ep.ExitCount > 0 {
			ep.AveragePlayTime = ep.AveragePlayTime / float64(ep.ExitCount)
			if ep.TotalSessions > 0 {
				ep.ExitRate = float64(ep.ExitCount) / float64(ep.TotalSessions) * 100.0
			}
			exitPoints = append(exitPoints, *ep)
		}
	}

	// Trier par taux de sortie décroissant
	for i := 0; i < len(exitPoints)-1; i++ {
		for j := i + 1; j < len(exitPoints); j++ {
			if exitPoints[i].ExitRate < exitPoints[j].ExitRate {
				exitPoints[i], exitPoints[j] = exitPoints[j], exitPoints[i]
			}
		}
	}

	// Retourner les 5 principaux points de sortie
	maxExitPoints := 5
	if len(exitPoints) < maxExitPoints {
		maxExitPoints = len(exitPoints)
	}

	return exitPoints[:maxExitPoints]
}

// analyzeEngagement analyse les métriques d'engagement
func (s *PlaybackRetentionService) analyzeEngagement(analytics []models.PlaybackAnalytics) EngagementMetrics {
	if len(analytics) == 0 {
		return EngagementMetrics{}
	}

	var totalCompletion float64
	var highEngagementCount int64
	var lowEngagementCount int64
	var totalPauses int64
	var totalSeeks int64

	for _, a := range analytics {
		totalCompletion += a.CompletionRate

		if a.CompletionRate >= 75.0 {
			highEngagementCount++
		}
		if a.CompletionRate < 25.0 {
			lowEngagementCount++
		}

		totalPauses += int64(a.PauseCount)
		totalSeeks += int64(a.SeekCount)
	}

	totalSessions := float64(len(analytics))

	// Calculer les métriques
	averageCompletion := totalCompletion / totalSessions
	overallRetentionRate := averageCompletion // Le taux de rétention global est le taux de complétion moyen

	highEngagementRate := float64(highEngagementCount) / totalSessions * 100.0
	lowEngagementRate := float64(lowEngagementCount) / totalSessions * 100.0

	averagePauses := float64(totalPauses) / totalSessions
	averageSeeks := float64(totalSeeks) / totalSessions

	// Calculer le score d'engagement (0-100)
	// Basé sur: completion rate (50%), pauses (25%), seeks (25%)
	// Moins de pauses et seeks = meilleur engagement
	engagementScore := averageCompletion * 0.5

	// Normaliser les pauses (0-10 pauses = 0-25 points)
	pauseScore := 25.0
	if averagePauses > 0 {
		pauseScore = 25.0 - (averagePauses / 10.0 * 25.0)
		if pauseScore < 0 {
			pauseScore = 0
		}
	}
	engagementScore += pauseScore

	// Normaliser les seeks (0-5 seeks = 0-25 points)
	seekScore := 25.0
	if averageSeeks > 0 {
		seekScore = 25.0 - (averageSeeks / 5.0 * 25.0)
		if seekScore < 0 {
			seekScore = 0
		}
	}
	engagementScore += seekScore

	// S'assurer que le score est entre 0 et 100
	if engagementScore > 100.0 {
		engagementScore = 100.0
	}
	if engagementScore < 0.0 {
		engagementScore = 0.0
	}

	return EngagementMetrics{
		OverallRetentionRate: overallRetentionRate,
		EngagementScore:      engagementScore,
		AverageCompletion:    averageCompletion,
		HighEngagementRate:   highEngagementRate,
		LowEngagementRate:    lowEngagementRate,
		AveragePauses:        averagePauses,
		AverageSeeks:         averageSeeks,
	}
}
