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(Guid playerId) { var appState = await db.AppState.AsNoTracking().SingleAsync(); if (!appState.ResultsOpen) return ServiceResult>.Failure(ServiceError.BadRequest("Results are locked until the admin enables them.")); var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, playerId); if (phase != Phase.Results) return ServiceResult>.Failure(ServiceError.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(), VoterNames = s.Votes .Select(v => v.Player!.DisplayName ?? v.Player!.Username) .ToList(), MyVote = s.Votes .Where(v => v.PlayerId == playerId) .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); IReadOnlyList 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(); var voterNames = r.VoterNames .Where(name => !string.IsNullOrWhiteSpace(name)) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(name => name, StringComparer.OrdinalIgnoreCase) .ToList(); return new ResultItemDto( r.Id, r.Name, r.Author, r.MinPlayers, r.MaxPlayers, r.Total, r.Count, r.Average, r.Votes, voterNames, r.MyVote, r.ScreenshotUrl, r.YoutubeUrl, r.GameUrl, r.Description, r.Genre, r.ParentSuggestionId, linkedIds, linkedTitles ); }).ToList(); return ServiceResult>.Success(shaped); } }