using Koogle.Application.DTOs; using Koogle.Application.Interfaces; using Koogle.Domain.Entities; using Koogle.Domain.Enums; using KoogleApp.Data; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace Koogle.Application.Services; /// /// Service for persisting and recovering game state. /// public class GamePersistenceService : IGamePersistenceService { private readonly IDbContextFactory _contextFactory; private readonly ILogger _logger; /// /// Initializes a new instance of the GamePersistenceService class. /// public GamePersistenceService( IDbContextFactory contextFactory, ILogger logger) { _contextFactory = contextFactory; _logger = logger; } /// public async Task CreateGameAsync(CreateGameDto dto) { await using var context = await _contextFactory.CreateDbContextAsync(); var game = new Game { Id = Guid.NewGuid(), DayId = dto.DayId, ClubId = dto.ClubId, GameType = dto.GameType, GameData = dto.InitialGameStateJson, Status = GameStatus.Active, StartedAt = DateTime.UtcNow, CreatedAt = DateTime.UtcNow }; // Add game participants foreach (var playerId in dto.PlayerIds) { game.GamePersons.Add(new GamePerson { GameId = game.Id, PersonId = playerId, ClubId = dto.ClubId }); } context.Games.Add(game); await context.SaveChangesAsync(); _logger.LogInformation( "Game created: {GameId}, Type: {GameType}, Day: {DayId}, Players: {PlayerCount}", game.Id, dto.GameType, dto.DayId, dto.PlayerIds.Length); return game.Id; } /// public async Task SaveGameStateAsync(Guid gameId, string gameStateJson) { await using var context = await _contextFactory.CreateDbContextAsync(); var game = await context.Games.FindAsync(gameId); if (game == null) { _logger.LogWarning("Game not found: {GameId}", gameId); return false; } game.GameData = gameStateJson; game.ModifiedAt = DateTime.UtcNow; try { await context.SaveChangesAsync(); _logger.LogDebug("Game state saved: {GameId}", gameId); return true; } catch (DbUpdateConcurrencyException ex) { _logger.LogWarning(ex, "Concurrency conflict saving game: {GameId}", gameId); return false; } } /// public async Task LoadActiveGameAsync(Guid dayId) { await using var context = await _contextFactory.CreateDbContextAsync(); var game = await context.Games .Include(g => g.GamePersons) .Where(g => g.DayId == dayId && g.Status == GameStatus.Active && !g.IsDeleted) .FirstOrDefaultAsync(); if (game == null) { _logger.LogDebug("No active game found for day: {DayId}", dayId); return null; } _logger.LogInformation( "Active game loaded: {GameId}, Type: {GameType}", game.Id, game.GameType); return new ActiveGameDto { Id = game.Id, DayId = game.DayId, GameType = game.GameType, StartedAt = game.StartedAt, PlayerIds = game.GamePersons.Select(gp => gp.PersonId).ToArray(), GameStateJson = game.GameData }; } /// public async Task EndGameAsync(Guid gameId, GameStatus status) { if (status == GameStatus.Active) { throw new ArgumentException("Cannot end game with Active status", nameof(status)); } await using var context = await _contextFactory.CreateDbContextAsync(); var game = await context.Games.FindAsync(gameId); if (game == null) { _logger.LogWarning("Game not found for ending: {GameId}", gameId); return; } game.Status = status; game.CompletedAt = DateTime.UtcNow; game.ModifiedAt = DateTime.UtcNow; await context.SaveChangesAsync(); _logger.LogInformation( "Game ended: {GameId}, Status: {Status}", gameId, status); } /// public async Task> GetCompletedGamesAsync(Guid dayId) { await using var context = await _contextFactory.CreateDbContextAsync(); var games = await context.Games .Include(g => g.GamePersons) .Where(g => g.DayId == dayId && g.Status != GameStatus.Active && !g.IsDeleted) .OrderByDescending(g => g.CompletedAt) .ToListAsync(); return games.Select(g => new GameSummaryDto { Id = g.Id, GameTypeName = g.GameType, Status = g.Status, StartedAt = g.StartedAt, CompletedAt = g.CompletedAt, ParticipantCount = g.GamePersons.Count }).ToList(); } }