package backend import ( "encoding/json" "time" "music-server/internal/logging" "go.uber.org/zap" ) // SoundtrackWithSongs represents a soundtrack with its songs for statistics type SoundtrackWithSongs struct { SoundtrackID int32 `json:"soundtrack_id"` SoundtrackName string `json:"soundtrack_name"` SoundtrackPlayed int32 `json:"soundtrack_played"` SoundtrackLastPlayed *time.Time `json:"soundtrack_last_played,omitempty"` Songs []SongInfoForStats `json:"songs"` } // SongInfoForStats represents a song with soundtrack info for statistics type SongInfoForStats struct { SoundtrackID int32 `json:"soundtrack_id"` SoundtrackName string `json:"soundtrack_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 { TotalSoundtracks int64 `json:"total_soundtracks"` PlayedSoundtracks int64 `json:"played_soundtracks"` NeverPlayedSoundtracks int64 `json:"never_played_soundtracks"` TotalSoundtrackPlays int64 `json:"total_soundtrack_plays"` AvgSoundtrackPlays float64 `json:"avg_soundtrack_plays"` MaxSoundtrackPlays int64 `json:"max_soundtrack_plays"` MinSoundtrackPlays int64 `json:"min_soundtrack_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{} } // GetMostPlayedSoundtracksWithSongs returns the top N most played soundtracks with their songs func (h *StatisticsHandler) GetMostPlayedSoundtracksWithSongs(limit int32) ([]SoundtrackWithSongs, error) { queries := BackendRepo() ctx := BackendCtx() // Get raw results rows, err := queries.GetMostPlayedSoundtracksWithSongs(ctx, limit) if err != nil { return nil, err } // Convert to SoundtrackWithSongs var result []SoundtrackWithSongs 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, SoundtrackWithSongs{ SoundtrackID: row.SoundtrackID, SoundtrackName: row.SoundtrackName, SoundtrackPlayed: row.SoundtrackPlayed, SoundtrackLastPlayed: row.SoundtrackLastPlayed, Songs: songs, }) } return result, nil } // GetLeastPlayedSoundtracksWithSongs returns the top N least played soundtracks with their songs func (h *StatisticsHandler) GetLeastPlayedSoundtracksWithSongs(limit int32) ([]SoundtrackWithSongs, error) { queries := BackendRepo() ctx := BackendCtx() rows, err := queries.GetLeastPlayedSoundtracksWithSongs(ctx, limit) if err != nil { return nil, err } var result []SoundtrackWithSongs 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, SoundtrackWithSongs{ SoundtrackID: row.SoundtrackID, SoundtrackName: row.SoundtrackName, SoundtrackPlayed: row.SoundtrackPlayed, SoundtrackLastPlayed: row.SoundtrackLastPlayed, Songs: songs, }) } return result, nil } // GetMostPlayedSongsWithSoundtrack returns the top N most played songs with their soundtrack info func (h *StatisticsHandler) GetMostPlayedSongsWithSoundtrack(limit int32) ([]SongInfoForStats, error) { queries := BackendRepo() ctx := BackendCtx() rows, err := queries.GetMostPlayedSongsWithSoundtrack(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 } // GetLeastPlayedSongsWithSoundtrack returns the top N least played songs with their soundtrack info func (h *StatisticsHandler) GetLeastPlayedSongsWithSoundtrack(limit int32) ([]SongInfoForStats, error) { queries := BackendRepo() ctx := BackendCtx() rows, err := queries.GetLeastPlayedSongsWithSoundtrack(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 } // GetNeverPlayedSoundtracks returns soundtracks that have never been played func (h *StatisticsHandler) GetNeverPlayedSoundtracks() ([]SoundtrackWithSongs, error) { queries := BackendRepo() ctx := BackendCtx() rows, err := queries.GetNeverPlayedSoundtracks(ctx) if err != nil { return nil, err } var result []SoundtrackWithSongs 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, SoundtrackWithSongs{ SoundtrackID: row.SoundtrackID, SoundtrackName: row.SoundtrackName, SoundtrackPlayed: row.SoundtrackPlayed, SoundtrackLastPlayed: nil, Songs: songs, }) } return result, nil } // GetLastPlayedSoundtracks returns the most recently played soundtracks func (h *StatisticsHandler) GetLastPlayedSoundtracks(limit int32) ([]SoundtrackWithSongs, error) { queries := BackendRepo() ctx := BackendCtx() rows, err := queries.GetLastPlayedSoundtracks(ctx, limit) if err != nil { return nil, err } var result []SoundtrackWithSongs 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, SoundtrackWithSongs{ SoundtrackID: row.SoundtrackID, SoundtrackName: row.SoundtrackName, SoundtrackPlayed: row.SoundtrackPlayed, SoundtrackLastPlayed: row.SoundtrackLastPlayed, Songs: songs, }) } return result, nil } // GetOldestPlayedSoundtracks returns the least recently played soundtracks func (h *StatisticsHandler) GetOldestPlayedSoundtracks(limit int32) ([]SoundtrackWithSongs, error) { queries := BackendRepo() ctx := BackendCtx() rows, err := queries.GetOldestPlayedSoundtracks(ctx, limit) if err != nil { return nil, err } var result []SoundtrackWithSongs 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, SoundtrackWithSongs{ 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{ TotalSoundtracks: int64(row.TotalSoundtracks), PlayedSoundtracks: int64(row.PlayedSoundtracks), NeverPlayedSoundtracks: int64(row.NeverPlayedSoundtracks), TotalSoundtrackPlays: int64(row.TotalSoundtrackPlays), AvgSoundtrackPlays: float64(row.AvgSoundtrackPlays), MaxSoundtrackPlays: int64(row.MaxSoundtrackPlays), MinSoundtrackPlays: 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())) } }