package services

import (
	"context"
	"fmt"
	"github.com/google/uuid"
	"time"

	"veza-backend-api/internal/models"

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

// PlaybackComparisonService gère la comparaison des analytics de lecture
// T0373: Create Playback Analytics Comparison Service
type PlaybackComparisonService struct {
	db     *gorm.DB
	logger *zap.Logger
}

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

// ComparisonResult représente le résultat d'une comparaison
type ComparisonResult struct {
	Period1          *PlaybackStats    `json:"period1"`
	Period2          *PlaybackStats    `json:"period2"`
	Difference       *StatsDifference  `json:"difference"`
	PercentageChange *PercentageChange `json:"percentage_change"`
}

// StatsDifference représente la différence absolue entre deux statistiques
type StatsDifference 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
}

// PercentageChange représente le changement en pourcentage entre deux statistiques
type PercentageChange 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"`    // %
}

// getPeriodDates retourne les dates de début et de fin pour une période donnée
func (s *PlaybackComparisonService) getPeriodDates(period string) (time.Time, time.Time, error) {
	now := time.Now()
	var startDate, endDate time.Time

	switch period {
	case "today":
		startDate = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
		endDate = now
	case "week":
		startDate = now.AddDate(0, 0, -7)
		endDate = now
	case "month":
		startDate = now.AddDate(0, 0, -30)
		endDate = now
	case "year":
		startDate = now.AddDate(-1, 0, 0)
		endDate = now
	default:
		return time.Time{}, time.Time{}, fmt.Errorf("invalid period: %s (must be today, week, month, or year)", period)
	}

	return startDate, endDate, nil
}

// getStatsForPeriod récupère les statistiques pour une période donnée
func (s *PlaybackComparisonService) getStatsForPeriod(ctx context.Context, trackID uuid.UUID, startDate, endDate time.Time) (*PlaybackStats, error) {
	var stats PlaybackStats

	// Total sessions
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND created_at >= ? AND created_at <= ?", trackID, startDate, endDate).
		Count(&stats.TotalSessions).Error; err != nil {
		return nil, fmt.Errorf("failed to count sessions: %w", err)
	}

	if stats.TotalSessions == 0 {
		return &stats, nil
	}

	// Total play time
	var totalPlayTime int64
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND created_at >= ? AND created_at <= ?", trackID, startDate, endDate).
		Select("COALESCE(SUM(play_time), 0)").Scan(&totalPlayTime).Error; err != nil {
		return nil, fmt.Errorf("failed to calculate total play time: %w", err)
	}
	stats.TotalPlayTime = totalPlayTime
	stats.AveragePlayTime = float64(totalPlayTime) / float64(stats.TotalSessions)

	// Total pauses
	var totalPauses int64
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND created_at >= ? AND created_at <= ?", trackID, startDate, endDate).
		Select("COALESCE(SUM(pause_count), 0)").Scan(&totalPauses).Error; err != nil {
		return nil, fmt.Errorf("failed to calculate total pauses: %w", err)
	}
	stats.TotalPauses = totalPauses
	stats.AveragePauses = float64(totalPauses) / float64(stats.TotalSessions)

	// Total seeks
	var totalSeeks int64
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND created_at >= ? AND created_at <= ?", trackID, startDate, endDate).
		Select("COALESCE(SUM(seek_count), 0)").Scan(&totalSeeks).Error; err != nil {
		return nil, fmt.Errorf("failed to calculate total seeks: %w", err)
	}
	stats.TotalSeeks = totalSeeks
	stats.AverageSeeks = float64(totalSeeks) / float64(stats.TotalSessions)

	// Average completion rate
	var avgCompletion float64
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND created_at >= ? AND created_at <= ?", trackID, startDate, endDate).
		Select("COALESCE(AVG(completion_rate), 0)").Scan(&avgCompletion).Error; err != nil {
		return nil, fmt.Errorf("failed to calculate average completion: %w", err)
	}
	stats.AverageCompletion = avgCompletion

	// Completion rate (sessions with >90% completion)
	var completedSessions int64
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND created_at >= ? AND created_at <= ? AND completion_rate >= ?", trackID, startDate, endDate, 90.0).
		Count(&completedSessions).Error; err != nil {
		return nil, fmt.Errorf("failed to count completed sessions: %w", err)
	}
	if stats.TotalSessions > 0 {
		stats.CompletionRate = float64(completedSessions) / float64(stats.TotalSessions) * 100.0
	}

	return &stats, nil
}

// calculateDifference calcule la différence absolue entre deux statistiques
func (s *PlaybackComparisonService) calculateDifference(stats1, stats2 *PlaybackStats) *StatsDifference {
	return &StatsDifference{
		TotalSessions:     stats2.TotalSessions - stats1.TotalSessions,
		TotalPlayTime:     stats2.TotalPlayTime - stats1.TotalPlayTime,
		AveragePlayTime:   stats2.AveragePlayTime - stats1.AveragePlayTime,
		TotalPauses:       stats2.TotalPauses - stats1.TotalPauses,
		AveragePauses:     stats2.AveragePauses - stats1.AveragePauses,
		TotalSeeks:        stats2.TotalSeeks - stats1.TotalSeeks,
		AverageSeeks:      stats2.AverageSeeks - stats1.AverageSeeks,
		AverageCompletion: stats2.AverageCompletion - stats1.AverageCompletion,
		CompletionRate:    stats2.CompletionRate - stats1.CompletionRate,
	}
}

// calculatePercentageChange calcule le changement en pourcentage entre deux statistiques
func (s *PlaybackComparisonService) calculatePercentageChange(stats1, stats2 *PlaybackStats) *PercentageChange {
	change := &PercentageChange{}

	// Total sessions
	if stats1.TotalSessions > 0 {
		change.TotalSessions = float64(stats2.TotalSessions-stats1.TotalSessions) / float64(stats1.TotalSessions) * 100.0
	} else if stats2.TotalSessions > 0 {
		change.TotalSessions = 100.0 // 100% increase from 0
	}

	// Total play time
	if stats1.TotalPlayTime > 0 {
		change.TotalPlayTime = float64(stats2.TotalPlayTime-stats1.TotalPlayTime) / float64(stats1.TotalPlayTime) * 100.0
	} else if stats2.TotalPlayTime > 0 {
		change.TotalPlayTime = 100.0
	}

	// Average play time
	if stats1.AveragePlayTime > 0 {
		change.AveragePlayTime = (stats2.AveragePlayTime - stats1.AveragePlayTime) / stats1.AveragePlayTime * 100.0
	} else if stats2.AveragePlayTime > 0 {
		change.AveragePlayTime = 100.0
	}

	// Total pauses
	if stats1.TotalPauses > 0 {
		change.TotalPauses = float64(stats2.TotalPauses-stats1.TotalPauses) / float64(stats1.TotalPauses) * 100.0
	} else if stats2.TotalPauses > 0 {
		change.TotalPauses = 100.0
	}

	// Average pauses
	if stats1.AveragePauses > 0 {
		change.AveragePauses = (stats2.AveragePauses - stats1.AveragePauses) / stats1.AveragePauses * 100.0
	} else if stats2.AveragePauses > 0 {
		change.AveragePauses = 100.0
	}

	// Total seeks
	if stats1.TotalSeeks > 0 {
		change.TotalSeeks = float64(stats2.TotalSeeks-stats1.TotalSeeks) / float64(stats1.TotalSeeks) * 100.0
	} else if stats2.TotalSeeks > 0 {
		change.TotalSeeks = 100.0
	}

	// Average seeks
	if stats1.AverageSeeks > 0 {
		change.AverageSeeks = (stats2.AverageSeeks - stats1.AverageSeeks) / stats1.AverageSeeks * 100.0
	} else if stats2.AverageSeeks > 0 {
		change.AverageSeeks = 100.0
	}

	// Average completion
	if stats1.AverageCompletion > 0 {
		change.AverageCompletion = (stats2.AverageCompletion - stats1.AverageCompletion) / stats1.AverageCompletion * 100.0
	} else if stats2.AverageCompletion > 0 {
		change.AverageCompletion = 100.0
	}

	// Completion rate
	if stats1.CompletionRate > 0 {
		change.CompletionRate = (stats2.CompletionRate - stats1.CompletionRate) / stats1.CompletionRate * 100.0
	} else if stats2.CompletionRate > 0 {
		change.CompletionRate = 100.0
	}

	return change
}

// ComparePeriods compare les analytics entre deux périodes pour un track
// T0373: Create Playback Analytics Comparison Service
func (s *PlaybackComparisonService) ComparePeriods(ctx context.Context, trackID uuid.UUID, period1, period2 string) (*ComparisonResult, error) {
	if trackID == uuid.Nil {
		return nil, fmt.Errorf("invalid track ID: %s", trackID)
	}

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

	// Obtenir les dates pour chaque période
	startDate1, endDate1, err := s.getPeriodDates(period1)
	if err != nil {
		return nil, fmt.Errorf("invalid period1: %w", err)
	}

	startDate2, endDate2, err := s.getPeriodDates(period2)
	if err != nil {
		return nil, fmt.Errorf("invalid period2: %w", err)
	}

	// Récupérer les statistiques pour chaque période
	stats1, err := s.getStatsForPeriod(ctx, trackID, startDate1, endDate1)
	if err != nil {
		return nil, fmt.Errorf("failed to get stats for period1: %w", err)
	}

	stats2, err := s.getStatsForPeriod(ctx, trackID, startDate2, endDate2)
	if err != nil {
		return nil, fmt.Errorf("failed to get stats for period2: %w", err)
	}

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

	result := &ComparisonResult{
		Period1:          stats1,
		Period2:          stats2,
		Difference:       difference,
		PercentageChange: percentageChange,
	}

	s.logger.Info("Compared playback analytics periods",
		zap.String("track_id", trackID.String()),
		zap.String("period1", period1),
		zap.String("period2", period2))

	return result, nil
}

// CompareTracks compare les analytics entre deux tracks
// T0373: Create Playback Analytics Comparison Service
func (s *PlaybackComparisonService) CompareTracks(ctx context.Context, trackID1, trackID2 uuid.UUID, startDate, endDate time.Time) (*ComparisonResult, error) {
	if trackID1 == uuid.Nil {
		return nil, fmt.Errorf("invalid track ID 1: %s", trackID1)
	}
	if trackID2 == uuid.Nil {
		return nil, fmt.Errorf("invalid track ID 2: %s", trackID2)
	}

	// Vérifier que les tracks existent
	var track1, track2 models.Track
	if err := s.db.WithContext(ctx).First(&track1, trackID1).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("track not found: %s", trackID1)
		}
		return nil, fmt.Errorf("failed to get track 1: %w", err)
	}
	if err := s.db.WithContext(ctx).First(&track2, trackID2).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("track not found: %s", trackID2)
		}
		return nil, fmt.Errorf("failed to get track 2: %w", err)
	}

	// Récupérer les statistiques pour chaque track
	stats1, err := s.getStatsForPeriod(ctx, trackID1, startDate, endDate)
	if err != nil {
		return nil, fmt.Errorf("failed to get stats for track 1: %w", err)
	}

	stats2, err := s.getStatsForPeriod(ctx, trackID2, startDate, endDate)
	if err != nil {
		return nil, fmt.Errorf("failed to get stats for track 2: %w", err)
	}

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

	result := &ComparisonResult{
		Period1:          stats1,
		Period2:          stats2,
		Difference:       difference,
		PercentageChange: percentageChange,
	}

	s.logger.Info("Compared playback analytics tracks",
		zap.String("track_id1", trackID1.String()),
		zap.String("track_id2", trackID2.String()))

	return result, nil
}

// CompareUsers compare les analytics entre deux users pour un track
// T0373: Create Playback Analytics Comparison Service
func (s *PlaybackComparisonService) CompareUsers(ctx context.Context, trackID uuid.UUID, userID1, userID2 uuid.UUID, startDate, endDate time.Time) (*ComparisonResult, error) {
	if trackID == uuid.Nil {
		return nil, fmt.Errorf("invalid track ID: %s", trackID)
	}
	if userID1 == uuid.Nil {
		return nil, fmt.Errorf("invalid user ID 1: nil UUID")
	}
	if userID2 == uuid.Nil {
		return nil, fmt.Errorf("invalid user ID 2: nil UUID")
	}

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

	// Vérifier que les users existent
	var user1, user2 models.User
	if err := s.db.WithContext(ctx).First(&user1, userID1).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("user not found: %s", userID1)
		}
		return nil, fmt.Errorf("failed to get user 1: %w", err)
	}
	if err := s.db.WithContext(ctx).First(&user2, userID2).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("user not found: %s", userID2)
		}
		return nil, fmt.Errorf("failed to get user 2: %w", err)
	}

	// Récupérer les statistiques pour chaque user
	stats1, err := s.getStatsForUser(ctx, trackID, userID1, startDate, endDate)
	if err != nil {
		return nil, fmt.Errorf("failed to get stats for user 1: %w", err)
	}

	stats2, err := s.getStatsForUser(ctx, trackID, userID2, startDate, endDate)
	if err != nil {
		return nil, fmt.Errorf("failed to get stats for user 2: %w", err)
	}

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

	result := &ComparisonResult{
		Period1:          stats1,
		Period2:          stats2,
		Difference:       difference,
		PercentageChange: percentageChange,
	}

	s.logger.Info("Compared playback analytics users",
		zap.String("track_id", trackID.String()),
		zap.String("user_id1", userID1.String()),
		zap.String("user_id2", userID2.String()))

	return result, nil
}

// getStatsForUser récupère les statistiques pour un utilisateur spécifique
// MIGRATION UUID: userID en uuid.UUID, trackID reste int64
func (s *PlaybackComparisonService) getStatsForUser(ctx context.Context, trackID uuid.UUID, userID uuid.UUID, startDate, endDate time.Time) (*PlaybackStats, error) {
	var stats PlaybackStats

	// Total sessions
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND user_id = ? AND created_at >= ? AND created_at <= ?", trackID, userID, startDate, endDate).
		Count(&stats.TotalSessions).Error; err != nil {
		return nil, fmt.Errorf("failed to count sessions: %w", err)
	}

	if stats.TotalSessions == 0 {
		return &stats, nil
	}

	// Total play time
	var totalPlayTime int64
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND user_id = ? AND created_at >= ? AND created_at <= ?", trackID, userID, startDate, endDate).
		Select("COALESCE(SUM(play_time), 0)").Scan(&totalPlayTime).Error; err != nil {
		return nil, fmt.Errorf("failed to calculate total play time: %w", err)
	}
	stats.TotalPlayTime = totalPlayTime
	stats.AveragePlayTime = float64(totalPlayTime) / float64(stats.TotalSessions)

	// Total pauses
	var totalPauses int64
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND user_id = ? AND created_at >= ? AND created_at <= ?", trackID, userID, startDate, endDate).
		Select("COALESCE(SUM(pause_count), 0)").Scan(&totalPauses).Error; err != nil {
		return nil, fmt.Errorf("failed to calculate total pauses: %w", err)
	}
	stats.TotalPauses = totalPauses
	stats.AveragePauses = float64(totalPauses) / float64(stats.TotalSessions)

	// Total seeks
	var totalSeeks int64
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND user_id = ? AND created_at >= ? AND created_at <= ?", trackID, userID, startDate, endDate).
		Select("COALESCE(SUM(seek_count), 0)").Scan(&totalSeeks).Error; err != nil {
		return nil, fmt.Errorf("failed to calculate total seeks: %w", err)
	}
	stats.TotalSeeks = totalSeeks
	stats.AverageSeeks = float64(totalSeeks) / float64(stats.TotalSessions)

	// Average completion rate
	var avgCompletion float64
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND user_id = ? AND created_at >= ? AND created_at <= ?", trackID, userID, startDate, endDate).
		Select("COALESCE(AVG(completion_rate), 0)").Scan(&avgCompletion).Error; err != nil {
		return nil, fmt.Errorf("failed to calculate average completion: %w", err)
	}
	stats.AverageCompletion = avgCompletion

	// Completion rate (sessions with >90% completion)
	var completedSessions int64
	if err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND user_id = ? AND created_at >= ? AND created_at <= ? AND completion_rate >= ?", trackID, userID, startDate, endDate, 90.0).
		Count(&completedSessions).Error; err != nil {
		return nil, fmt.Errorf("failed to count completed sessions: %w", err)
	}
	if stats.TotalSessions > 0 {
		stats.CompletionRate = float64(completedSessions) / float64(stats.TotalSessions) * 100.0
	}

	return &stats, nil
}
