package services

import (
	"context"
	"fmt"
	"time"

	"veza-backend-api/internal/models"

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

// PlaybackHeatmapService gère la génération de heatmap pour les analytics de lecture
// T0376: Create Playback Analytics Heatmap Generation
type PlaybackHeatmapService struct {
	db     *gorm.DB
	logger *zap.Logger
}

// NewPlaybackHeatmapService crée un nouveau service de génération de heatmap
func NewPlaybackHeatmapService(db *gorm.DB, logger *zap.Logger) *PlaybackHeatmapService {
	if logger == nil {
		logger = zap.NewNop()
	}
	return &PlaybackHeatmapService{
		db:     db,
		logger: logger,
	}
}

// HeatmapSegment représente un segment de la heatmap
type HeatmapSegment struct {
	StartTime       float64 `json:"start_time"`        // Temps de début du segment (secondes)
	EndTime         float64 `json:"end_time"`          // Temps de fin du segment (secondes)
	ListenCount     int64   `json:"listen_count"`      // Nombre de fois que ce segment a été écouté
	SkipCount       int64   `json:"skip_count"`        // Nombre de fois que ce segment a été sauté
	Intensity       float64 `json:"intensity"`         // Intensité d'écoute (0-1, normalisée)
	AveragePlayTime float64 `json:"average_play_time"` // Temps de lecture moyen dans ce segment (secondes)
}

// HeatmapData représente les données complètes de la heatmap
type HeatmapData struct {
	TrackID       uuid.UUID        `json:"track_id"`
	TrackDuration int              `json:"track_duration"` // secondes
	SegmentSize   int              `json:"segment_size"`   // Taille des segments (secondes)
	TotalSessions int64            `json:"total_sessions"`
	Segments      []HeatmapSegment `json:"segments"`
	MaxIntensity  float64          `json:"max_intensity"` // Intensité maximale (pour normalisation)
	GeneratedAt   time.Time        `json:"generated_at"`
}

// GenerateHeatmap génère les données de heatmap pour un track
// T0376: Create Playback Analytics Heatmap Generation
func (s *PlaybackHeatmapService) GenerateHeatmap(ctx context.Context, trackID uuid.UUID, segmentSize int) (*HeatmapData, error) {
	if trackID == uuid.Nil {
		return nil, fmt.Errorf("invalid track ID: %s", trackID)
	}

	if segmentSize <= 0 {
		segmentSize = 5 // Par défaut, segments de 5 secondes
	}
	if segmentSize > 60 {
		segmentSize = 60 // Maximum 60 secondes par segment
	}

	// 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)
	}

	// Calculer les zones écoutées et skip
	segments := s.calculateListenedZones(analytics, track.Duration, segmentSize)
	skipZones := s.calculateSkipZones(analytics, track.Duration, segmentSize)

	// Combiner les données et calculer l'intensité
	heatmapSegments := s.generateHeatmapSegments(segments, skipZones, track.Duration, segmentSize)

	// Trouver l'intensité maximale pour normalisation
	maxIntensity := 0.0
	for _, seg := range heatmapSegments {
		if seg.Intensity > maxIntensity {
			maxIntensity = seg.Intensity
		}
	}

	// Normaliser les intensités (0-1)
	if maxIntensity > 0 {
		for i := range heatmapSegments {
			heatmapSegments[i].Intensity = heatmapSegments[i].Intensity / maxIntensity
		}
	}

	result := &HeatmapData{
		TrackID:       trackID,
		TrackDuration: track.Duration,
		SegmentSize:   segmentSize,
		TotalSessions: int64(len(analytics)),
		Segments:      heatmapSegments,
		MaxIntensity:  maxIntensity,
		GeneratedAt:   time.Now(),
	}

	s.logger.Info("Generated playback heatmap",
		zap.String("track_id", trackID.String()),
		zap.Int("total_sessions", len(analytics)),
		zap.Int("segment_size", segmentSize),
		zap.Int("segments_count", len(heatmapSegments)))

	return result, nil
}

// ListenedZone représente une zone écoutée
type ListenedZone struct {
	StartTime     float64
	EndTime       float64
	ListenCount   int64
	TotalPlayTime float64
	SessionCount  int64
}

// calculateListenedZones calcule les zones écoutées
func (s *PlaybackHeatmapService) calculateListenedZones(analytics []models.PlaybackAnalytics, trackDuration int, segmentSize int) map[int]*ListenedZone {
	zones := make(map[int]*ListenedZone)
	totalSegments := (trackDuration + segmentSize - 1) / segmentSize // Arrondi supérieur

	// Initialiser tous les segments
	for i := 0; i < totalSegments; i++ {
		startTime := float64(i * segmentSize)
		endTime := float64((i + 1) * segmentSize)
		if endTime > float64(trackDuration) {
			endTime = float64(trackDuration)
		}
		zones[i] = &ListenedZone{
			StartTime:     startTime,
			EndTime:       endTime,
			ListenCount:   0,
			TotalPlayTime: 0.0,
			SessionCount:  0,
		}
	}

	// Pour chaque session, calculer les segments écoutés
	for _, a := range analytics {
		playTimeSeconds := float64(a.PlayTime)
		if playTimeSeconds <= 0 {
			continue
		}

		// Pour chaque segment, vérifier s'il a été écouté
		for i := 0; i < totalSegments; i++ {
			segmentStart := float64(i * segmentSize)
			segmentEnd := float64((i + 1) * segmentSize)
			if segmentEnd > float64(trackDuration) {
				segmentEnd = float64(trackDuration)
			}

			// Si la session a atteint ce segment
			if playTimeSeconds >= segmentStart {
				zones[i].ListenCount++

				// Calculer le temps passé dans ce segment
				segmentPlayTime := playTimeSeconds - segmentStart
				if segmentPlayTime > (segmentEnd - segmentStart) {
					segmentPlayTime = segmentEnd - segmentStart
				}
				zones[i].TotalPlayTime += segmentPlayTime
				zones[i].SessionCount++
			}
		}
	}

	return zones
}

// SkipZone représente une zone skip
type SkipZone struct {
	StartTime float64
	EndTime   float64
	SkipCount int64
}

// calculateSkipZones calcule les zones skip (basées sur les seeks)
func (s *PlaybackHeatmapService) calculateSkipZones(analytics []models.PlaybackAnalytics, trackDuration int, segmentSize int) map[int]*SkipZone {
	zones := make(map[int]*SkipZone)
	totalSegments := (trackDuration + segmentSize - 1) / segmentSize

	// Initialiser tous les segments
	for i := 0; i < totalSegments; i++ {
		startTime := float64(i * segmentSize)
		endTime := float64((i + 1) * segmentSize)
		if endTime > float64(trackDuration) {
			endTime = float64(trackDuration)
		}
		zones[i] = &SkipZone{
			StartTime: startTime,
			EndTime:   endTime,
			SkipCount: 0,
		}
	}

	// Pour chaque session avec des seeks, considérer que les segments non écoutés sont skip
	for _, a := range analytics {
		playTimeSeconds := float64(a.PlayTime)
		seekCount := a.SeekCount

		// Si la session a des seeks, cela indique des sauts
		// On considère que les segments entre le début et le temps de lecture final sont potentiellement skip
		// si le seek count est élevé par rapport au temps de lecture
		if seekCount > 0 {
			// Calculer un ratio de skip basé sur les seeks
			// Plus il y a de seeks, plus il y a de zones skip potentielles
			skipRatio := float64(seekCount) / (playTimeSeconds + 1.0) // +1 pour éviter division par zéro

			// Pour chaque segment avant le temps de lecture final
			for i := 0; i < totalSegments; i++ {
				segmentStart := float64(i * segmentSize)
				segmentEnd := float64((i + 1) * segmentSize)
				if segmentEnd > float64(trackDuration) {
					segmentEnd = float64(trackDuration)
				}

				// Si le segment est avant le temps de lecture final et qu'il y a des seeks
				if segmentEnd <= playTimeSeconds {
					// Probabilité de skip basée sur le ratio
					if skipRatio > 0.1 { // Seuil pour considérer comme skip
						zones[i].SkipCount++
					}
				} else if segmentStart < playTimeSeconds && segmentEnd > playTimeSeconds {
					// Segment partiellement écouté avec seeks = probablement skip
					if seekCount > 1 {
						zones[i].SkipCount++
					}
				}
			}
		}
	}

	return zones
}

// generateHeatmapSegments génère les segments de heatmap en combinant les zones écoutées et skip
func (s *PlaybackHeatmapService) generateHeatmapSegments(listenedZones map[int]*ListenedZone, skipZones map[int]*SkipZone, trackDuration int, segmentSize int) []HeatmapSegment {
	totalSegments := (trackDuration + segmentSize - 1) / segmentSize
	segments := make([]HeatmapSegment, 0, totalSegments)

	for i := 0; i < totalSegments; i++ {
		listenedZone := listenedZones[i]
		skipZone := skipZones[i]

		if listenedZone == nil {
			continue
		}

		startTime := float64(i * segmentSize)
		endTime := float64((i + 1) * segmentSize)
		if endTime > float64(trackDuration) {
			endTime = float64(trackDuration)
		}

		// Calculer l'intensité d'écoute
		// Basée sur : nombre d'écoutes, temps moyen passé, et inverse des skips
		intensity := 0.0
		if listenedZone.SessionCount > 0 {
			// Intensité basée sur le nombre d'écoutes et le temps moyen
			avgPlayTime := listenedZone.TotalPlayTime / float64(listenedZone.SessionCount)
			segmentDuration := endTime - startTime
			completionRatio := avgPlayTime / segmentDuration
			if completionRatio > 1.0 {
				completionRatio = 1.0
			}

			// Intensité = (nombre d'écoutes * ratio de complétion) - (skips * pénalité)
			intensity = float64(listenedZone.ListenCount) * completionRatio
			if skipZone != nil && skipZone.SkipCount > 0 {
				// Pénalité pour les skips (réduit l'intensité)
				skipPenalty := float64(skipZone.SkipCount) * 0.5
				intensity = intensity - skipPenalty
				if intensity < 0 {
					intensity = 0
				}
			}
		}

		// Calculer le temps de lecture moyen
		averagePlayTime := 0.0
		if listenedZone.SessionCount > 0 {
			averagePlayTime = listenedZone.TotalPlayTime / float64(listenedZone.SessionCount)
		}

		skipCount := int64(0)
		if skipZone != nil {
			skipCount = skipZone.SkipCount
		}

		segments = append(segments, HeatmapSegment{
			StartTime:       startTime,
			EndTime:         endTime,
			ListenCount:     listenedZone.ListenCount,
			SkipCount:       skipCount,
			Intensity:       intensity,
			AveragePlayTime: averagePlayTime,
		})
	}

	return segments
}

// GetHeatmapIntensityArray retourne un tableau simple d'intensités pour visualisation
// Utile pour les graphiques de heatmap simples
func (s *PlaybackHeatmapService) GetHeatmapIntensityArray(ctx context.Context, trackID uuid.UUID, segmentSize int) ([]float64, error) {
	heatmap, err := s.GenerateHeatmap(ctx, trackID, segmentSize)
	if err != nil {
		return nil, err
	}

	intensities := make([]float64, len(heatmap.Segments))
	for i, seg := range heatmap.Segments {
		intensities[i] = seg.Intensity
	}

	return intensities, nil
}
