package valueobjects import ( "fmt" "time" "github.com/knowfoolery/backend/shared/errors" ) type LeaderboardStatistics struct { totalEntries int32 activeEntries int32 totalScoreSum int64 averageScore float32 highestScore int32 lowestScore int32 medianScore float32 totalGamesPlayed int64 uniquePlayers int32 lastUpdated time.Time createdAt time.Time resetCount int32 lastResetAt time.Time topScoreStreaks map[string]int32 competitiveness float32 activityLevel float32 growthRate float32 retentionRate float32 metadata map[string]interface{} } func NewLeaderboardStatistics() *LeaderboardStatistics { now := time.Now() return &LeaderboardStatistics{ totalEntries: 0, activeEntries: 0, totalScoreSum: 0, averageScore: 0.0, highestScore: 0, lowestScore: 0, medianScore: 0.0, totalGamesPlayed: 0, uniquePlayers: 0, lastUpdated: now, createdAt: now, resetCount: 0, lastResetAt: time.Time{}, topScoreStreaks: make(map[string]int32), competitiveness: 0.0, activityLevel: 0.0, growthRate: 0.0, retentionRate: 0.0, metadata: make(map[string]interface{}), } } func (ls *LeaderboardStatistics) TotalEntries() int32 { return ls.totalEntries } func (ls *LeaderboardStatistics) ActiveEntries() int32 { return ls.activeEntries } func (ls *LeaderboardStatistics) TotalScoreSum() int64 { return ls.totalScoreSum } func (ls *LeaderboardStatistics) AverageScore() float32 { return ls.averageScore } func (ls *LeaderboardStatistics) HighestScore() int32 { return ls.highestScore } func (ls *LeaderboardStatistics) LowestScore() int32 { return ls.lowestScore } func (ls *LeaderboardStatistics) MedianScore() float32 { return ls.medianScore } func (ls *LeaderboardStatistics) TotalGamesPlayed() int64 { return ls.totalGamesPlayed } func (ls *LeaderboardStatistics) UniquePlayers() int32 { return ls.uniquePlayers } func (ls *LeaderboardStatistics) LastUpdated() time.Time { return ls.lastUpdated } func (ls *LeaderboardStatistics) CreatedAt() time.Time { return ls.createdAt } func (ls *LeaderboardStatistics) ResetCount() int32 { return ls.resetCount } func (ls *LeaderboardStatistics) LastResetAt() time.Time { return ls.lastResetAt } func (ls *LeaderboardStatistics) TopScoreStreaks() map[string]int32 { result := make(map[string]int32) for k, v := range ls.topScoreStreaks { result[k] = v } return result } func (ls *LeaderboardStatistics) Competitiveness() float32 { return ls.competitiveness } func (ls *LeaderboardStatistics) ActivityLevel() float32 { return ls.activityLevel } func (ls *LeaderboardStatistics) GrowthRate() float32 { return ls.growthRate } func (ls *LeaderboardStatistics) RetentionRate() float32 { return ls.retentionRate } func (ls *LeaderboardStatistics) Metadata() map[string]interface{} { result := make(map[string]interface{}) for k, v := range ls.metadata { result[k] = v } return result } func (ls *LeaderboardStatistics) UpdateScoreStatistics(scores []int32) error { if len(scores) == 0 { return nil } ls.totalEntries = int32(len(scores)) ls.activeEntries = ls.totalEntries var sum int64 highest := scores[0] lowest := scores[0] for _, score := range scores { sum += int64(score) if score > highest { highest = score } if score < lowest { lowest = score } } ls.totalScoreSum = sum ls.averageScore = float32(sum) / float32(len(scores)) ls.highestScore = highest ls.lowestScore = lowest ls.medianScore = ls.calculateMedian(scores) ls.lastUpdated = time.Now() return nil } func (ls *LeaderboardStatistics) calculateMedian(scores []int32) float32 { if len(scores) == 0 { return 0 } sorted := make([]int32, len(scores)) copy(sorted, scores) for i := 0; i < len(sorted)-1; i++ { for j := i + 1; j < len(sorted); j++ { if sorted[i] > sorted[j] { sorted[i], sorted[j] = sorted[j], sorted[i] } } } n := len(sorted) if n%2 == 0 { return float32(sorted[n/2-1]+sorted[n/2]) / 2.0 } return float32(sorted[n/2]) } func (ls *LeaderboardStatistics) IncrementGamesPlayed(count int64) error { if count < 0 { return errors.ErrInvalidGameCount } ls.totalGamesPlayed += count ls.lastUpdated = time.Now() return nil } func (ls *LeaderboardStatistics) SetUniquePlayers(count int32) error { if count < 0 { return errors.ErrInvalidPlayerCount } ls.uniquePlayers = count ls.lastUpdated = time.Now() return nil } func (ls *LeaderboardStatistics) IncrementResetCount() { ls.resetCount++ ls.lastResetAt = time.Now() ls.lastUpdated = time.Now() } func (ls *LeaderboardStatistics) UpdateTopScoreStreak(playerID string, streak int32) error { if playerID == "" { return errors.ErrInvalidPlayerID } if streak < 0 { return errors.ErrInvalidStreak } if streak == 0 { delete(ls.topScoreStreaks, playerID) } else { ls.topScoreStreaks[playerID] = streak } ls.lastUpdated = time.Now() return nil } func (ls *LeaderboardStatistics) CalculateCompetitiveness() { if ls.totalEntries <= 1 { ls.competitiveness = 0.0 return } if ls.highestScore == ls.lowestScore { ls.competitiveness = 0.0 return } scoreRange := float32(ls.highestScore - ls.lowestScore) normalizedRange := scoreRange / float32(ls.highestScore) participationFactor := float32(ls.activeEntries) / float32(ls.totalEntries) ls.competitiveness = normalizedRange * participationFactor if ls.competitiveness > 1.0 { ls.competitiveness = 1.0 } ls.lastUpdated = time.Now() } func (ls *LeaderboardStatistics) CalculateActivityLevel(recentGames int64, timeWindow time.Duration) { if timeWindow <= 0 { ls.activityLevel = 0.0 return } hoursInWindow := float64(timeWindow) / float64(time.Hour) if hoursInWindow == 0 { ls.activityLevel = 0.0 return } ls.activityLevel = float32(float64(recentGames) / hoursInWindow) ls.lastUpdated = time.Now() } func (ls *LeaderboardStatistics) CalculateGrowthRate(previousPeriodPlayers int32, currentPeriodPlayers int32) { if previousPeriodPlayers <= 0 { if currentPeriodPlayers > 0 { ls.growthRate = 1.0 } else { ls.growthRate = 0.0 } return } ls.growthRate = float32(currentPeriodPlayers-previousPeriodPlayers) / float32(previousPeriodPlayers) ls.lastUpdated = time.Now() } func (ls *LeaderboardStatistics) CalculateRetentionRate(returningPlayers int32, totalPlayers int32) { if totalPlayers <= 0 { ls.retentionRate = 0.0 return } ls.retentionRate = float32(returningPlayers) / float32(totalPlayers) if ls.retentionRate > 1.0 { ls.retentionRate = 1.0 } ls.lastUpdated = time.Now() } func (ls *LeaderboardStatistics) SetMetadata(key string, value interface{}) error { if key == "" { return errors.ErrInvalidMetadata } ls.metadata[key] = value ls.lastUpdated = time.Now() return nil } func (ls *LeaderboardStatistics) RemoveMetadata(key string) { delete(ls.metadata, key) ls.lastUpdated = time.Now() } func (ls *LeaderboardStatistics) Reset() { ls.totalEntries = 0 ls.activeEntries = 0 ls.totalScoreSum = 0 ls.averageScore = 0.0 ls.highestScore = 0 ls.lowestScore = 0 ls.medianScore = 0.0 ls.totalGamesPlayed = 0 ls.uniquePlayers = 0 ls.topScoreStreaks = make(map[string]int32) ls.competitiveness = 0.0 ls.activityLevel = 0.0 ls.growthRate = 0.0 ls.retentionRate = 0.0 ls.IncrementResetCount() } func (ls *LeaderboardStatistics) GetAge() time.Duration { return time.Since(ls.createdAt) } func (ls *LeaderboardStatistics) GetTimeSinceLastUpdate() time.Duration { return time.Since(ls.lastUpdated) } func (ls *LeaderboardStatistics) GetTimeSinceLastReset() time.Duration { if ls.lastResetAt.IsZero() { return ls.GetAge() } return time.Since(ls.lastResetAt) } func (ls *LeaderboardStatistics) IsHealthy() bool { timeSinceUpdate := ls.GetTimeSinceLastUpdate() return timeSinceUpdate < time.Hour*24 } func (ls *LeaderboardStatistics) String() string { return fmt.Sprintf("LeaderboardStatistics{TotalEntries: %d, AvgScore: %.2f, Highest: %d, Lowest: %d, Players: %d}", ls.totalEntries, ls.averageScore, ls.highestScore, ls.lowestScore, ls.uniquePlayers) } type LeaderboardStatisticsSnapshot struct { TotalEntries int32 `json:"total_entries"` ActiveEntries int32 `json:"active_entries"` TotalScoreSum int64 `json:"total_score_sum"` AverageScore float32 `json:"average_score"` HighestScore int32 `json:"highest_score"` LowestScore int32 `json:"lowest_score"` MedianScore float32 `json:"median_score"` TotalGamesPlayed int64 `json:"total_games_played"` UniquePlayers int32 `json:"unique_players"` LastUpdated time.Time `json:"last_updated"` CreatedAt time.Time `json:"created_at"` ResetCount int32 `json:"reset_count"` LastResetAt time.Time `json:"last_reset_at"` TopScoreStreaks map[string]int32 `json:"top_score_streaks"` Competitiveness float32 `json:"competitiveness"` ActivityLevel float32 `json:"activity_level"` GrowthRate float32 `json:"growth_rate"` RetentionRate float32 `json:"retention_rate"` Metadata map[string]interface{} `json:"metadata"` } func (ls *LeaderboardStatistics) ToSnapshot() LeaderboardStatisticsSnapshot { return LeaderboardStatisticsSnapshot{ TotalEntries: ls.totalEntries, ActiveEntries: ls.activeEntries, TotalScoreSum: ls.totalScoreSum, AverageScore: ls.averageScore, HighestScore: ls.highestScore, LowestScore: ls.lowestScore, MedianScore: ls.medianScore, TotalGamesPlayed: ls.totalGamesPlayed, UniquePlayers: ls.uniquePlayers, LastUpdated: ls.lastUpdated, CreatedAt: ls.createdAt, ResetCount: ls.resetCount, LastResetAt: ls.lastResetAt, TopScoreStreaks: ls.TopScoreStreaks(), Competitiveness: ls.competitiveness, ActivityLevel: ls.activityLevel, GrowthRate: ls.growthRate, RetentionRate: ls.retentionRate, Metadata: ls.Metadata(), } } func (ls *LeaderboardStatistics) FromSnapshot(snapshot LeaderboardStatisticsSnapshot) error { ls.totalEntries = snapshot.TotalEntries ls.activeEntries = snapshot.ActiveEntries ls.totalScoreSum = snapshot.TotalScoreSum ls.averageScore = snapshot.AverageScore ls.highestScore = snapshot.HighestScore ls.lowestScore = snapshot.LowestScore ls.medianScore = snapshot.MedianScore ls.totalGamesPlayed = snapshot.TotalGamesPlayed ls.uniquePlayers = snapshot.UniquePlayers ls.lastUpdated = snapshot.LastUpdated ls.createdAt = snapshot.CreatedAt ls.resetCount = snapshot.ResetCount ls.lastResetAt = snapshot.LastResetAt ls.competitiveness = snapshot.Competitiveness ls.activityLevel = snapshot.ActivityLevel ls.growthRate = snapshot.GrowthRate ls.retentionRate = snapshot.RetentionRate ls.topScoreStreaks = make(map[string]int32) for k, v := range snapshot.TopScoreStreaks { ls.topScoreStreaks[k] = v } ls.metadata = make(map[string]interface{}) for k, v := range snapshot.Metadata { ls.metadata[k] = v } return nil }