package services

import (
	"context"
	"fmt"
	"time"

	"github.com/google/uuid"

	"veza-backend-api/internal/models"

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

// PlaybackAggregationService gère l'agrégation des analytics de lecture
// T0365: Create Playback Analytics Aggregation Service
type PlaybackAggregationService struct {
	db     *gorm.DB
	logger *zap.Logger
}

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

// PeriodType représente le type de période d'agrégation
type PeriodType string

const (
	PeriodDay   PeriodType = "day"
	PeriodWeek  PeriodType = "week"
	PeriodMonth PeriodType = "month"
)

// PeriodAggregation représente les données agrégées pour une période
type PeriodAggregation struct {
	Period            string  `json:"period"` // Format: YYYY-MM-DD, YYYY-WW, YYYY-MM
	Sessions          int64   `json:"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 of sessions with >90% completion
}

// AggregationResult représente le résultat d'une agrégation
type AggregationResult struct {
	Periods         []PeriodAggregation `json:"periods"`
	TotalSessions   int64               `json:"total_sessions"`
	TotalPlayTime   int64               `json:"total_play_time"`   // seconds
	AveragePlayTime float64             `json:"average_play_time"` // seconds
	Trends          *TrendsData         `json:"trends,omitempty"`
}

// TrendsData représente les tendances calculées
type TrendsData struct {
	SessionsTrend   float64 `json:"sessions_trend"`   // % de changement
	PlayTimeTrend   float64 `json:"play_time_trend"`  // % de changement
	CompletionTrend float64 `json:"completion_trend"` // % de changement
	PausesTrend     float64 `json:"pauses_trend"`     // % de changement
	SeeksTrend      float64 `json:"seeks_trend"`      // % de changement
}

// AggregateByPeriod agrège les analytics par période (day, week, month)
// T0365: Create Playback Analytics Aggregation Service
func (s *PlaybackAggregationService) AggregateByPeriod(ctx context.Context, trackID uuid.UUID, period PeriodType, startDate, endDate time.Time) (*AggregationResult, error) {
	if trackID == uuid.Nil {
		return nil, fmt.Errorf("invalid track ID: %s", trackID)
	}

	// Valider le type de période
	if period != PeriodDay && period != PeriodWeek && period != PeriodMonth {
		return nil, fmt.Errorf("invalid period type: %s (must be day, week, or month)", period)
	}

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

	// Récupérer toutes les sessions dans la plage de dates
	var sessions []models.PlaybackAnalytics
	err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND created_at >= ? AND created_at <= ?", trackID, startDate, endDate).
		Order("created_at ASC").
		Find(&sessions).Error

	if err != nil {
		return nil, fmt.Errorf("failed to get sessions: %w", err)
	}

	// Grouper par période
	periodMap := make(map[string]*PeriodAggregation)

	for _, session := range sessions {
		periodKey := s.getPeriodKey(session.CreatedAt, period)

		if periodMap[periodKey] == nil {
			periodMap[periodKey] = &PeriodAggregation{
				Period: periodKey,
			}
		}

		agg := periodMap[periodKey]
		agg.Sessions++
		agg.TotalPlayTime += int64(session.PlayTime)
		agg.TotalPauses += int64(session.PauseCount)
		agg.TotalSeeks += int64(session.SeekCount)
		agg.AverageCompletion += session.CompletionRate

		// Compter les sessions complétées
		if session.CompletionRate >= 90 {
			agg.CompletionRate += 1.0
		}
	}

	// Calculer les moyennes pour chaque période
	var periods []PeriodAggregation
	var totalSessions int64
	var totalPlayTime int64
	var totalPauses int64
	var totalSeeks int64
	var totalCompletion float64

	for _, agg := range periodMap {
		if agg.Sessions > 0 {
			agg.AveragePlayTime = float64(agg.TotalPlayTime) / float64(agg.Sessions)
			agg.AveragePauses = float64(agg.TotalPauses) / float64(agg.Sessions)
			agg.AverageSeeks = float64(agg.TotalSeeks) / float64(agg.Sessions)
			agg.AverageCompletion = agg.AverageCompletion / float64(agg.Sessions)
			agg.CompletionRate = (agg.CompletionRate / float64(agg.Sessions)) * 100.0
		}

		periods = append(periods, *agg)
		totalSessions += agg.Sessions
		totalPlayTime += agg.TotalPlayTime
		totalPauses += agg.TotalPauses
		totalSeeks += agg.TotalSeeks
		totalCompletion += agg.AverageCompletion * float64(agg.Sessions)
	}

	// Trier les périodes par ordre chronologique
	periods = s.sortPeriods(periods)

	// Calculer les moyennes globales
	var averagePlayTime float64
	if totalSessions > 0 {
		averagePlayTime = float64(totalPlayTime) / float64(totalSessions)
	}

	// Calculer les tendances (comparaison entre la première et la dernière période)
	var trends *TrendsData
	if len(periods) >= 2 {
		trends = s.calculateTrends(periods)
	}

	result := &AggregationResult{
		Periods:         periods,
		TotalSessions:   totalSessions,
		TotalPlayTime:   totalPlayTime,
		AveragePlayTime: averagePlayTime,
		Trends:          trends,
	}

	return result, nil
}

// getPeriodKey génère une clé de période basée sur la date et le type de période
func (s *PlaybackAggregationService) getPeriodKey(date time.Time, period PeriodType) string {
	switch period {
	case PeriodDay:
		return date.Format("2006-01-02")
	case PeriodWeek:
		year, week := date.ISOWeek()
		return fmt.Sprintf("%d-W%02d", year, week)
	case PeriodMonth:
		return date.Format("2006-01")
	default:
		return date.Format("2006-01-02")
	}
}

// sortPeriods trie les périodes par ordre chronologique
func (s *PlaybackAggregationService) sortPeriods(periods []PeriodAggregation) []PeriodAggregation {
	// Utiliser un tri simple basé sur la clé de période (qui est déjà formatée)
	for i := 0; i < len(periods)-1; i++ {
		for j := i + 1; j < len(periods); j++ {
			if periods[i].Period > periods[j].Period {
				periods[i], periods[j] = periods[j], periods[i]
			}
		}
	}
	return periods
}

// calculateTrends calcule les tendances entre la première et la dernière période
func (s *PlaybackAggregationService) calculateTrends(periods []PeriodAggregation) *TrendsData {
	if len(periods) < 2 {
		return nil
	}

	first := periods[0]
	last := periods[len(periods)-1]

	trends := &TrendsData{}

	// Tendance des sessions
	if first.Sessions > 0 {
		trends.SessionsTrend = float64(last.Sessions-first.Sessions) / float64(first.Sessions) * 100.0
	} else if last.Sessions > 0 {
		trends.SessionsTrend = 100.0
	}

	// Tendance du temps de lecture
	if first.AveragePlayTime > 0 {
		trends.PlayTimeTrend = (last.AveragePlayTime - first.AveragePlayTime) / first.AveragePlayTime * 100.0
	} else if last.AveragePlayTime > 0 {
		trends.PlayTimeTrend = 100.0
	}

	// Tendance du taux de complétion
	if first.AverageCompletion > 0 {
		trends.CompletionTrend = (last.AverageCompletion - first.AverageCompletion) / first.AverageCompletion * 100.0
	} else if last.AverageCompletion > 0 {
		trends.CompletionTrend = 100.0
	}

	// Tendance des pauses
	if first.AveragePauses > 0 {
		trends.PausesTrend = (last.AveragePauses - first.AveragePauses) / first.AveragePauses * 100.0
	} else if last.AveragePauses > 0 {
		trends.PausesTrend = 100.0
	}

	// Tendance des seeks
	if first.AverageSeeks > 0 {
		trends.SeeksTrend = (last.AverageSeeks - first.AverageSeeks) / first.AverageSeeks * 100.0
	} else if last.AverageSeeks > 0 {
		trends.SeeksTrend = 100.0
	}

	return trends
}

// AggregateByDateRange agrège les analytics dans une plage de dates sans groupement par période
func (s *PlaybackAggregationService) AggregateByDateRange(ctx context.Context, trackID uuid.UUID, startDate, endDate time.Time) (*PeriodAggregation, 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)
	}

	// Récupérer toutes les sessions dans la plage de dates
	var sessions []models.PlaybackAnalytics
	err := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Where("track_id = ? AND created_at >= ? AND created_at <= ?", trackID, startDate, endDate).
		Find(&sessions).Error

	if err != nil {
		return nil, fmt.Errorf("failed to get sessions: %w", err)
	}

	agg := &PeriodAggregation{
		Period: fmt.Sprintf("%s to %s", startDate.Format("2006-01-02"), endDate.Format("2006-01-02")),
	}

	for _, session := range sessions {
		agg.Sessions++
		agg.TotalPlayTime += int64(session.PlayTime)
		agg.TotalPauses += int64(session.PauseCount)
		agg.TotalSeeks += int64(session.SeekCount)
		agg.AverageCompletion += session.CompletionRate

		if session.CompletionRate >= 90 {
			agg.CompletionRate += 1.0
		}
	}

	if agg.Sessions > 0 {
		agg.AveragePlayTime = float64(agg.TotalPlayTime) / float64(agg.Sessions)
		agg.AveragePauses = float64(agg.TotalPauses) / float64(agg.Sessions)
		agg.AverageSeeks = float64(agg.TotalSeeks) / float64(agg.Sessions)
		agg.AverageCompletion = agg.AverageCompletion / float64(agg.Sessions)
		agg.CompletionRate = (agg.CompletionRate / float64(agg.Sessions)) * 100.0
	}

	return agg, nil
}

// GetTopTracksByPlayback récupère les tracks les plus écoutés
func (s *PlaybackAggregationService) GetTopTracksByPlayback(ctx context.Context, limit int, startDate, endDate *time.Time) ([]map[string]interface{}, error) {
	if limit <= 0 {
		limit = 10
	}

	query := s.db.WithContext(ctx).Model(&models.PlaybackAnalytics{}).
		Select("track_id, COUNT(*) as sessions, SUM(play_time) as total_play_time, AVG(completion_rate) as avg_completion").
		Group("track_id").
		Order("sessions DESC").
		Limit(limit)

	if startDate != nil && endDate != nil {
		query = query.Where("created_at >= ? AND created_at <= ?", *startDate, *endDate)
	}

	var results []struct {
		TrackID       uuid.UUID `gorm:"column:track_id"`
		Sessions      int64     `gorm:"column:sessions"`
		TotalPlayTime int64     `gorm:"column:total_play_time"`
		AvgCompletion float64   `gorm:"column:avg_completion"`
	}

	if err := query.Scan(&results).Error; err != nil {
		return nil, fmt.Errorf("failed to get top tracks: %w", err)
	}

	var topTracks []map[string]interface{}
	for _, result := range results {
		topTracks = append(topTracks, map[string]interface{}{
			"track_id":        result.TrackID,
			"sessions":        result.Sessions,
			"total_play_time": result.TotalPlayTime,
			"avg_completion":  result.AvgCompletion,
		})
	}

	return topTracks, nil
}
