using GameList.Contracts; using GameList.Data; using GameList.Domain; using Microsoft.EntityFrameworkCore; namespace GameList.Endpoints; internal sealed class ResultsWorkflowService(AppDbContext db) { public async Task GetResultsAsync(Player player) { var appState = await db.AppState.AsNoTracking().FirstAsync(); if (!appState.ResultsOpen) return EndpointHelpers.BadRequestError("Results are locked until the admin enables them."); var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id); if (phase != Phase.Results) return EndpointHelpers.PhaseMismatch(Phase.Results, phase); var results = await db .Suggestions.AsNoTracking() .Include(s => s.Player) .Include(s => s.Votes) .Select(s => new { s.Id, s.Name, Author = s.Player!.DisplayName, s.MinPlayers, s.MaxPlayers, Total = s.Votes.Sum(v => v.Score), s.Votes.Count, Average = s.Votes.Count == 0 ? 0 : s.Votes.Average(v => v.Score), Votes = s.Votes.Select(v => v.Score).ToList(), MyVote = s.Votes .Where(v => v.PlayerId == player.Id) .Select(v => (int?)v.Score) .FirstOrDefault(), s.ScreenshotUrl, s.YoutubeUrl, s.GameUrl, s.Description, s.Genre, s.ParentSuggestionId }) .OrderByDescending(r => r.Average) .ToListAsync(); var rootIndex = EndpointHelpers.BuildLinkRoots(results.Select(r => (r.Id, r.ParentSuggestionId))); var nameLookup = results.ToDictionary(r => r.Id, r => r.Name); var shaped = results.Select(r => { var linkedIds = EndpointHelpers.LinkedIdsFor(r.Id, rootIndex) .Where(id => id != r.Id) .ToList(); var linkedTitles = linkedIds .Where(nameLookup.ContainsKey) .Select(id => nameLookup[id]) .ToList(); return new ResultItemDto( r.Id, r.Name, r.Author, r.MinPlayers, r.MaxPlayers, r.Total, r.Count, r.Average, r.Votes, r.MyVote, r.ScreenshotUrl, r.YoutubeUrl, r.GameUrl, r.Description, r.Genre, r.ParentSuggestionId, linkedIds, linkedTitles ); }); return Results.Ok(shaped); } }