package races import ( "context" "errors" "fmt" "time" "github.com/jackc/pgx/v5" ) // LeaderboardEntry is a single row in the leaderboard. type LeaderboardEntry struct { DriverID string Name string Nickname string AvatarURL string ClanID string ClanTag string Points int BestPos *int // deprecated/optional BestTimeMs *int64 Rank int } // LeaderboardResult groups the paginated items, total count, and current driver. type LeaderboardResult struct { Items []LeaderboardEntry Total int CurrentDriver *LeaderboardEntry } func getYearTimestamps(year int) (int64, int64) { start := time.Date(year, time.January, 1, 0, 0, 0, 0, time.UTC) end := time.Date(year+1, time.January, 1, 0, 0, 0, 0, time.UTC) return start.UnixMilli(), end.UnixMilli() } // GetTrackLeaderboard returns track-specific leaderboard entries for the given track and year. func (s *PgStore) GetTrackLeaderboard(ctx context.Context, trackID string, year int, limit, offset int) ([]LeaderboardEntry, int, error) { startMs, endMs := getYearTimestamps(year) if limit <= 0 { limit = 50 } countQuery := ` WITH best_results AS ( SELECT rd.driver_id FROM race_drivers rd JOIN races r ON rd.race_id = r.id WHERE r.status = 'finished' AND r.track_id = $1 AND r.finished_ms >= $2 AND r.finished_ms < $3 GROUP BY rd.driver_id ) SELECT COUNT(*) FROM best_results` var total int err := s.pool.QueryRow(ctx, countQuery, trackID, startMs, endMs).Scan(&total) if err != nil { return nil, 0, fmt.Errorf("count track leaderboard: %w", err) } if total == 0 { return nil, 0, nil } query := ` WITH best_results AS ( SELECT r.track_id, rd.driver_id, MIN(CASE WHEN rd.best_lap_ms > 0 THEN rd.best_lap_ms ELSE NULL END) as best_time FROM race_drivers rd JOIN races r ON rd.race_id = r.id WHERE r.status = 'finished' AND r.finished_ms >= $1 AND r.finished_ms < $2 GROUP BY r.track_id, rd.driver_id ), ranked_times AS ( SELECT track_id, driver_id, best_time, ROW_NUMBER() OVER (PARTITION BY track_id ORDER BY best_time ASC NULLS LAST, driver_id ASC) as time_rank FROM best_results ), track_points AS ( SELECT track_id, driver_id, best_time, CASE WHEN best_time IS NULL THEN 1 WHEN time_rank <= 3 THEN 25 WHEN time_rank <= 10 THEN 18 WHEN time_rank <= 20 THEN 15 WHEN time_rank <= 30 THEN 12 WHEN time_rank <= 40 THEN 10 WHEN time_rank <= 50 THEN 8 WHEN time_rank <= 60 THEN 6 WHEN time_rank <= 70 THEN 4 WHEN time_rank <= 80 THEN 2 WHEN time_rank <= 90 THEN 1 ELSE 1 END as points FROM ranked_times ), ranked_leaderboard AS ( SELECT tp.driver_id, d.name, d.nickname, d.avatar_url, COALESCE(d.clan_id, '') as clan_id, COALESCE(c.tag, '') as clan_tag, tp.points, tp.best_time, ROW_NUMBER() OVER (ORDER BY tp.points DESC, tp.best_time ASC NULLS LAST, tp.driver_id ASC) as rank FROM track_points tp JOIN drivers d ON tp.driver_id = d.id LEFT JOIN clans c ON d.clan_id = c.id WHERE tp.track_id = $3 ) SELECT rank, driver_id, name, nickname, avatar_url, clan_id, clan_tag, points, best_time FROM ranked_leaderboard ORDER BY rank ASC LIMIT $4 OFFSET $5` rows, err := s.pool.Query(ctx, query, startMs, endMs, trackID, limit, offset) if err != nil { return nil, 0, fmt.Errorf("query track leaderboard: %w", err) } defer rows.Close() var entries []LeaderboardEntry for rows.Next() { var entry LeaderboardEntry if err := rows.Scan( &entry.Rank, &entry.DriverID, &entry.Name, &entry.Nickname, &entry.AvatarURL, &entry.ClanID, &entry.ClanTag, &entry.Points, &entry.BestTimeMs, ); err != nil { return nil, 0, err } entries = append(entries, entry) } return entries, total, nil } // GetTrackLeaderboardDriver returns a single driver's rank on a track. func (s *PgStore) GetTrackLeaderboardDriver(ctx context.Context, trackID string, year int, driverID string) (*LeaderboardEntry, error) { startMs, endMs := getYearTimestamps(year) query := ` WITH best_results AS ( SELECT r.track_id, rd.driver_id, MIN(CASE WHEN rd.best_lap_ms > 0 THEN rd.best_lap_ms ELSE NULL END) as best_time FROM race_drivers rd JOIN races r ON rd.race_id = r.id WHERE r.status = 'finished' AND r.finished_ms >= $1 AND r.finished_ms < $2 GROUP BY r.track_id, rd.driver_id ), ranked_times AS ( SELECT track_id, driver_id, best_time, ROW_NUMBER() OVER (PARTITION BY track_id ORDER BY best_time ASC NULLS LAST, driver_id ASC) as time_rank FROM best_results ), track_points AS ( SELECT track_id, driver_id, best_time, CASE WHEN best_time IS NULL THEN 1 WHEN time_rank <= 3 THEN 25 WHEN time_rank <= 10 THEN 18 WHEN time_rank <= 20 THEN 15 WHEN time_rank <= 30 THEN 12 WHEN time_rank <= 40 THEN 10 WHEN time_rank <= 50 THEN 8 WHEN time_rank <= 60 THEN 6 WHEN time_rank <= 70 THEN 4 WHEN time_rank <= 80 THEN 2 WHEN time_rank <= 90 THEN 1 ELSE 1 END as points FROM ranked_times ), ranked_leaderboard AS ( SELECT tp.driver_id, d.name, d.nickname, d.avatar_url, COALESCE(d.clan_id, '') as clan_id, COALESCE(c.tag, '') as clan_tag, tp.points, tp.best_time, ROW_NUMBER() OVER (ORDER BY tp.points DESC, tp.best_time ASC NULLS LAST, tp.driver_id ASC) as rank FROM track_points tp JOIN drivers d ON tp.driver_id = d.id LEFT JOIN clans c ON d.clan_id = c.id WHERE tp.track_id = $3 ) SELECT rank, driver_id, name, nickname, avatar_url, clan_id, clan_tag, points, best_time FROM ranked_leaderboard WHERE driver_id = $4` row := s.pool.QueryRow(ctx, query, startMs, endMs, trackID, driverID) var entry LeaderboardEntry if err := row.Scan( &entry.Rank, &entry.DriverID, &entry.Name, &entry.Nickname, &entry.AvatarURL, &entry.ClanID, &entry.ClanTag, &entry.Points, &entry.BestTimeMs, ); err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, nil } return nil, err } return &entry, nil } // GetOverallLeaderboard returns overall leaderboard entries for the given year. func (s *PgStore) GetOverallLeaderboard(ctx context.Context, year int, limit, offset int) ([]LeaderboardEntry, int, error) { startMs, endMs := getYearTimestamps(year) if limit <= 0 { limit = 50 } countQuery := ` WITH best_results AS ( SELECT rd.driver_id FROM race_drivers rd JOIN races r ON rd.race_id = r.id WHERE r.status = 'finished' AND r.finished_ms >= $1 AND r.finished_ms < $2 GROUP BY rd.driver_id ) SELECT COUNT(*) FROM best_results` var total int err := s.pool.QueryRow(ctx, countQuery, startMs, endMs).Scan(&total) if err != nil { return nil, 0, fmt.Errorf("count overall leaderboard: %w", err) } if total == 0 { return nil, 0, nil } query := ` WITH best_results AS ( SELECT r.track_id, rd.driver_id, MIN(CASE WHEN rd.best_lap_ms > 0 THEN rd.best_lap_ms ELSE NULL END) as best_time FROM race_drivers rd JOIN races r ON rd.race_id = r.id WHERE r.status = 'finished' AND r.finished_ms >= $1 AND r.finished_ms < $2 GROUP BY r.track_id, rd.driver_id ), ranked_times AS ( SELECT track_id, driver_id, best_time, ROW_NUMBER() OVER (PARTITION BY track_id ORDER BY best_time ASC NULLS LAST, driver_id ASC) as time_rank FROM best_results ), track_points AS ( SELECT track_id, driver_id, best_time, CASE WHEN best_time IS NULL THEN 1 WHEN time_rank <= 3 THEN 25 WHEN time_rank <= 10 THEN 18 WHEN time_rank <= 20 THEN 15 WHEN time_rank <= 30 THEN 12 WHEN time_rank <= 40 THEN 10 WHEN time_rank <= 50 THEN 8 WHEN time_rank <= 60 THEN 6 WHEN time_rank <= 70 THEN 4 WHEN time_rank <= 80 THEN 2 WHEN time_rank <= 90 THEN 1 ELSE 1 END as points FROM ranked_times ), overall_points AS ( SELECT driver_id, SUM(points) as points FROM track_points GROUP BY driver_id ), ranked_overall AS ( SELECT op.driver_id, d.name, d.nickname, d.avatar_url, COALESCE(d.clan_id, '') as clan_id, COALESCE(c.tag, '') as clan_tag, op.points, ROW_NUMBER() OVER (ORDER BY op.points DESC, op.driver_id ASC) as rank FROM overall_points op JOIN drivers d ON op.driver_id = d.id LEFT JOIN clans c ON d.clan_id = c.id ) SELECT rank, driver_id, name, nickname, avatar_url, clan_id, clan_tag, points FROM ranked_overall ORDER BY rank ASC LIMIT $3 OFFSET $4` rows, err := s.pool.Query(ctx, query, startMs, endMs, limit, offset) if err != nil { return nil, 0, fmt.Errorf("query overall leaderboard: %w", err) } defer rows.Close() var entries []LeaderboardEntry for rows.Next() { var entry LeaderboardEntry if err := rows.Scan( &entry.Rank, &entry.DriverID, &entry.Name, &entry.Nickname, &entry.AvatarURL, &entry.ClanID, &entry.ClanTag, &entry.Points, ); err != nil { return nil, 0, err } entries = append(entries, entry) } return entries, total, nil } // GetOverallLeaderboardDriver returns a single driver's overall rank. func (s *PgStore) GetOverallLeaderboardDriver(ctx context.Context, year int, driverID string) (*LeaderboardEntry, error) { startMs, endMs := getYearTimestamps(year) query := ` WITH best_results AS ( SELECT r.track_id, rd.driver_id, MIN(CASE WHEN rd.best_lap_ms > 0 THEN rd.best_lap_ms ELSE NULL END) as best_time FROM race_drivers rd JOIN races r ON rd.race_id = r.id WHERE r.status = 'finished' AND r.finished_ms >= $1 AND r.finished_ms < $2 GROUP BY r.track_id, rd.driver_id ), ranked_times AS ( SELECT track_id, driver_id, best_time, ROW_NUMBER() OVER (PARTITION BY track_id ORDER BY best_time ASC NULLS LAST, driver_id ASC) as time_rank FROM best_results ), track_points AS ( SELECT track_id, driver_id, best_time, CASE WHEN best_time IS NULL THEN 1 WHEN time_rank <= 3 THEN 25 WHEN time_rank <= 10 THEN 18 WHEN time_rank <= 20 THEN 15 WHEN time_rank <= 30 THEN 12 WHEN time_rank <= 40 THEN 10 WHEN time_rank <= 50 THEN 8 WHEN time_rank <= 60 THEN 6 WHEN time_rank <= 70 THEN 4 WHEN time_rank <= 80 THEN 2 WHEN time_rank <= 90 THEN 1 ELSE 1 END as points FROM ranked_times ), overall_points AS ( SELECT driver_id, SUM(points) as points FROM track_points GROUP BY driver_id ), ranked_overall AS ( SELECT op.driver_id, d.name, d.nickname, d.avatar_url, COALESCE(d.clan_id, '') as clan_id, COALESCE(c.tag, '') as clan_tag, op.points, ROW_NUMBER() OVER (ORDER BY op.points DESC, op.driver_id ASC) as rank FROM overall_points op JOIN drivers d ON op.driver_id = d.id LEFT JOIN clans c ON d.clan_id = c.id ) SELECT rank, driver_id, name, nickname, avatar_url, clan_id, clan_tag, points FROM ranked_overall WHERE driver_id = $3` row := s.pool.QueryRow(ctx, query, startMs, endMs, driverID) var entry LeaderboardEntry if err := row.Scan( &entry.Rank, &entry.DriverID, &entry.Name, &entry.Nickname, &entry.AvatarURL, &entry.ClanID, &entry.ClanTag, &entry.Points, ); err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, nil } return nil, err } return &entry, nil } // GetDriverLeaderboardProfile returns driver profile information. func (s *PgStore) GetDriverLeaderboardProfile(ctx context.Context, driverID string) (*LeaderboardEntry, error) { query := ` SELECT d.id, d.name, d.nickname, d.avatar_url, COALESCE(d.clan_id, ''), COALESCE(c.tag, '') FROM drivers d LEFT JOIN clans c ON d.clan_id = c.id WHERE d.id = $1` row := s.pool.QueryRow(ctx, query, driverID) var entry LeaderboardEntry if err := row.Scan( &entry.DriverID, &entry.Name, &entry.Nickname, &entry.AvatarURL, &entry.ClanID, &entry.ClanTag, ); err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, nil } return nil, err } return &entry, nil } // TrackCalendarInfo holds the basic details of a scheduled track. type TrackCalendarInfo struct { ID int TrackID string TrackName string StartAt time.Time EndAt time.Time } // TrackCalendarLeaderboard holds the leaderboard summary for a track calendar event. type TrackCalendarLeaderboard struct { TrackID string TrackName string StartAt time.Time EndAt time.Time Status string // finished, active, planned Podium []LeaderboardEntry CurrentDriver *LeaderboardEntry } // GetCalendarLeaderboardTracks returns all track calendar entries joined with their track names, sorted chronologically. func (s *PgStore) GetCalendarLeaderboardTracks(ctx context.Context) ([]TrackCalendarInfo, error) { rows, err := s.pool.Query(ctx, ` SELECT tc.id, tc.track_id, t.name, tc.start_at, tc.end_at FROM track_calendar tc JOIN tracks t ON tc.track_id = t.id ORDER BY tc.start_at DESC `) if err != nil { return nil, fmt.Errorf("query calendar tracks: %w", err) } defer rows.Close() var out []TrackCalendarInfo for rows.Next() { var info TrackCalendarInfo if err := rows.Scan(&info.ID, &info.TrackID, &info.TrackName, &info.StartAt, &info.EndAt); err != nil { return nil, fmt.Errorf("scan calendar track: %w", err) } out = append(out, info) } return out, nil }