package backend import ( "encoding/json" "time" "music-server/internal/logging" "go.uber.org/zap" ) // GameWithSongs represents a game with its songs for statistics type GameWithSongs struct { SoundtrackID int32 `json:"game_id"` SoundtrackName string `json:"game_name"` SoundtrackPlayed int32 `json:"game_played"` SoundtrackLastPlayed *time.Time `json:"game_last_played,omitempty"` Songs []SongInfoForStats `json:"songs"` } // SongInfoForStats represents a song with game info for statistics type SongInfoForStats struct { SoundtrackID int32 `json:"game_id"` SoundtrackName string `json:"game_name"` SongName string `json:"song_name"` Path string `json:"path"` TimesPlayed int32 `json:"times_played"` FileName *string `json:"file_name,omitempty"` } // StatisticsSummary holds overall statistics type StatisticsSummary struct { TotalGames int64 `json:"total_games"` PlayedGames int64 `json:"played_games"` NeverPlayedGames int64 `json:"never_played_games"` TotalGamePlays int64 `json:"total_game_plays"` AvgGamePlays float64 `json:"avg_game_plays"` MaxGamePlays int64 `json:"max_game_plays"` MinGamePlays int64 `json:"min_game_plays"` } // StatisticsHandler manages statistics operations type StatisticsHandler struct { // Uses the global backend repo initialized via InitBackend } // NewStatisticsHandler creates a new StatisticsHandler func NewStatisticsHandler() *StatisticsHandler { return &StatisticsHandler{} } // GetMostPlayedGamesWithSongs returns the top N most played games with their songs func (h *StatisticsHandler) GetMostPlayedGamesWithSongs(limit int32) ([]GameWithSongs, error) { queries := BackendRepo() ctx := BackendCtx() // Get raw results rows, err := queries.GetMostPlayedGamesWithSongs(ctx, limit) if err != nil { return nil, err } // Convert to GameWithSongs var result []GameWithSongs for _, row := range rows { var songs []SongInfoForStats if row.Songs != nil { // Parse JSON songs array if err := json.Unmarshal(row.Songs, &songs); err != nil { // Fallback: if JSON parsing fails, create empty song entries songs = make([]SongInfoForStats, 0) } } result = append(result, GameWithSongs{ SoundtrackID: row.SoundtrackID, SoundtrackName: row.SoundtrackName, SoundtrackPlayed: row.SoundtrackPlayed, SoundtrackLastPlayed: row.SoundtrackLastPlayed, Songs: songs, }) } return result, nil } // GetLeastPlayedGamesWithSongs returns the top N least played games with their songs func (h *StatisticsHandler) GetLeastPlayedGamesWithSongs(limit int32) ([]GameWithSongs, error) { queries := BackendRepo() ctx := BackendCtx() rows, err := queries.GetLeastPlayedGamesWithSongs(ctx, limit) if err != nil { return nil, err } var result []GameWithSongs for _, row := range rows { var songs []SongInfoForStats if row.Songs != nil { if err := json.Unmarshal(row.Songs, &songs); err != nil { songs = make([]SongInfoForStats, 0) } } result = append(result, GameWithSongs{ SoundtrackID: row.SoundtrackID, SoundtrackName: row.SoundtrackName, SoundtrackPlayed: row.SoundtrackPlayed, SoundtrackLastPlayed: row.SoundtrackLastPlayed, Songs: songs, }) } return result, nil } // GetMostPlayedSongsWithGame returns the top N most played songs with their game info func (h *StatisticsHandler) GetMostPlayedSongsWithGame(limit int32) ([]SongInfoForStats, error) { queries := BackendRepo() ctx := BackendCtx() rows, err := queries.GetMostPlayedSongsWithGame(ctx, limit) if err != nil { return nil, err } var result []SongInfoForStats for _, row := range rows { result = append(result, SongInfoForStats{ SoundtrackID: row.SoundtrackID, SoundtrackName: row.SoundtrackName, SongName: row.SongName, Path: row.Path, TimesPlayed: row.TimesPlayed, FileName: row.FileName, }) } return result, nil } // GetLeastPlayedSongsWithGame returns the top N least played songs with their game info func (h *StatisticsHandler) GetLeastPlayedSongsWithGame(limit int32) ([]SongInfoForStats, error) { queries := BackendRepo() ctx := BackendCtx() rows, err := queries.GetLeastPlayedSongsWithGame(ctx, limit) if err != nil { return nil, err } var result []SongInfoForStats for _, row := range rows { result = append(result, SongInfoForStats{ SoundtrackID: row.SoundtrackID, SoundtrackName: row.SoundtrackName, SongName: row.SongName, Path: row.Path, TimesPlayed: row.TimesPlayed, FileName: row.FileName, }) } return result, nil } // GetNeverPlayedGames returns games that have never been played func (h *StatisticsHandler) GetNeverPlayedGames() ([]GameWithSongs, error) { queries := BackendRepo() ctx := BackendCtx() rows, err := queries.GetNeverPlayedGames(ctx) if err != nil { return nil, err } var result []GameWithSongs for _, row := range rows { var songs []SongInfoForStats if row.Songs != nil { if err := json.Unmarshal(row.Songs, &songs); err != nil { songs = make([]SongInfoForStats, 0) } } result = append(result, GameWithSongs{ SoundtrackID: row.SoundtrackID, SoundtrackName: row.SoundtrackName, SoundtrackPlayed: row.SoundtrackPlayed, SoundtrackLastPlayed: nil, Songs: songs, }) } return result, nil } // GetLastPlayedGames returns the most recently played games func (h *StatisticsHandler) GetLastPlayedGames(limit int32) ([]GameWithSongs, error) { queries := BackendRepo() ctx := BackendCtx() rows, err := queries.GetLastPlayedGames(ctx, limit) if err != nil { return nil, err } var result []GameWithSongs for _, row := range rows { var songs []SongInfoForStats if row.Songs != nil { if err := json.Unmarshal(row.Songs, &songs); err != nil { songs = make([]SongInfoForStats, 0) } } result = append(result, GameWithSongs{ SoundtrackID: row.SoundtrackID, SoundtrackName: row.SoundtrackName, SoundtrackPlayed: row.SoundtrackPlayed, SoundtrackLastPlayed: row.SoundtrackLastPlayed, Songs: songs, }) } return result, nil } // GetOldestPlayedGames returns the least recently played games func (h *StatisticsHandler) GetOldestPlayedGames(limit int32) ([]GameWithSongs, error) { queries := BackendRepo() ctx := BackendCtx() rows, err := queries.GetOldestPlayedGames(ctx, limit) if err != nil { return nil, err } var result []GameWithSongs for _, row := range rows { var songs []SongInfoForStats if row.Songs != nil { if err := json.Unmarshal(row.Songs, &songs); err != nil { songs = make([]SongInfoForStats, 0) } } result = append(result, GameWithSongs{ SoundtrackID: row.SoundtrackID, SoundtrackName: row.SoundtrackName, SoundtrackPlayed: row.SoundtrackPlayed, SoundtrackLastPlayed: row.SoundtrackLastPlayed, Songs: songs, }) } return result, nil } // GetStatisticsSummary returns overall statistics func (h *StatisticsHandler) GetStatisticsSummary() (*StatisticsSummary, error) { queries := BackendRepo() ctx := BackendCtx() row, err := queries.GetStatisticsSummary(ctx) if err != nil { return nil, err } return &StatisticsSummary{ TotalGames: int64(row.TotalSoundtracks), PlayedGames: int64(row.PlayedSoundtracks), NeverPlayedGames: int64(row.NeverPlayedSoundtracks), TotalGamePlays: int64(row.TotalSoundtrackPlays), AvgGamePlays: float64(row.AvgSoundtrackPlays), MaxGamePlays: int64(row.MaxSoundtrackPlays), MinGamePlays: int64(row.MinSoundtrackPlays), }, nil } // Log helper for statistics operations func logStatisticsError(err error, operation string) { if err != nil { logging.GetLogger().Error("Statistics error", zap.String("operation", operation), zap.String("error", err.Error())) } }