using AutoMapper; using GameIdeas.Shared.Constants; using GameIdeas.Shared.Dto; using GameIdeas.Shared.Exceptions; using GameIdeas.Shared.Model; using GameIdeas.WebAPI.Context; using Microsoft.EntityFrameworkCore; using System.Linq; using System.Linq.Expressions; namespace GameIdeas.WebAPI.Services.Games; public class GameReadService(GameIdeasContext context, IMapper mapper) : IGameReadService { public async Task> GetGames(GameFilterDto filter) { var query = context.Games .Include(g => g.GamePlatforms).ThenInclude(gp => gp.Platform) .Include(g => g.GameProperties) .Include(g => g.GameTags).ThenInclude(gt => gt.Tag) .Include(g => g.GamePublishers) .Include(g => g.GameDevelopers) .AsQueryable(); ApplyFilter(ref query, filter); ApplyOrder(ref query, filter); var games = await query.Skip((filter.CurrentPage - 1) * GlobalConstants.NUMBER_PER_PAGE) .Take(GlobalConstants.NUMBER_PER_PAGE) .ToListAsync(); if (!string.IsNullOrWhiteSpace(filter.Title)) { games = [.. ApplySearchGameFilter(games, filter)]; } return mapper.Map>(games); } public async Task GetGameById(int gameId) { var game = await context.Games .Include(g => g.CreationUser) .Include(g => g.ModificationUser) .Include(g => g.GamePlatforms).ThenInclude(p => p.Platform) .Include(g => g.GameProperties).ThenInclude(p => p.Property) .Include(g => g.GameTags).ThenInclude(p => p.Tag) .Include(g => g.GamePublishers).ThenInclude(p => p.Publisher) .Include(g => g.GameDevelopers).ThenInclude(p => p.Developer) .FirstOrDefaultAsync(g => g.Id == gameId); return game == null ? throw new NotFoundException($"[{typeof(Game).FullName}] with ID {gameId} has not been found in context") : mapper.Map(game); } private static void ApplyOrder(ref IQueryable query, GameFilterDto filter) { if (filter.SortType != null && filter.SortPropertyName != null) { var param = Expression.Parameter(typeof(Game), "x"); Expression propertyAccess = Expression.PropertyOrField(param, filter.SortPropertyName); var converted = Expression.Convert(propertyAccess, typeof(object)); var lambda = Expression.Lambda>(converted, param); if (filter.SortType == Shared.Enum.SortType.Ascending) { query = Queryable.OrderBy(query, lambda); } else { query = Queryable.OrderByDescending(query, lambda); } } } private static void ApplyFilter(ref IQueryable query, GameFilterDto filter) { if (filter.PlatformIds != null) { query = query.Where(game => filter.PlatformIds.All(plat => game.GamePlatforms.Any(gp => gp.PlatformId == plat))); } if (filter.PropertyIds != null) { query = query.Where(game => filter.PropertyIds.All(prop => game.GameProperties.Any(gp => gp.PropertyId == prop))); } if (filter.TagIds != null) { query = query.Where(game => filter.TagIds.All(tag => game.GameTags.Any(gt => gt.TagId == tag))); } if (filter.PublisherIds != null) { query = query.Where(game => filter.PublisherIds.All(pub => game.GamePublishers.Any(gp => gp.PublisherId == pub))); } if (filter.DeveloperIds != null) { query = query.Where(game => filter.DeveloperIds.All(dev => game.GameDevelopers.Any(gd => gd.DeveloperId == dev))); } if (filter.MinInterest != null) { query = query.Where(game => game.Interest >= filter.MinInterest); } if (filter.MaxInterest != null) { query = query.Where(game => game.Interest <= filter.MaxInterest); } if (filter.StorageSpaces != null) { query = query.Where(game => filter.StorageSpaces.Where(stor => stor.MinSize <= game.StorageSpace && stor.MaxSize > game.StorageSpace).Count() != 0); } if (filter.ReleaseYears != null) { query = query.Where(game => game.ReleaseDate != null && filter.ReleaseYears.Contains(game.ReleaseDate.Value.Year)); } } private static IEnumerable ApplySearchGameFilter(IEnumerable query, GameFilterDto filter) { var keywords = filter.Title? .Split([' '], StringSplitOptions.RemoveEmptyEntries) .Select(k => k.Trim()) .ToArray() ?? []; query = query .Where(game => keywords.All( kw => game.Title.Contains(kw, StringComparison.OrdinalIgnoreCase) )) .OrderBy(game => keywords.Min(kw => game.Title.IndexOf(kw, StringComparison.OrdinalIgnoreCase) )) .ThenBy(game => game.Title.Length); return query; } }