using Microsoft.EntityFrameworkCore; using SlipItIn.Server.Data; using SlipItIn.Shared.DTOs; using SlipItIn.Shared.Models; namespace SlipItIn.Server.Services; public class GameService : IGameService { private readonly IDbContextFactory _contextFactory; private readonly ILogger _logger; public GameService(IDbContextFactory contextFactory, ILogger logger) { _contextFactory = contextFactory; _logger = logger; } public async Task CreateGameAsync(int hostUserId, string? connectionId = null) { using var context = _contextFactory.CreateDbContext(); var host = await context.Users.FirstOrDefaultAsync(u => u.Id == hostUserId) ?? throw new InvalidOperationException("Host not found"); var lobbyCode = GenerateLobbyCode(); var game = new Game { LobbyCode = lobbyCode, HostId = host.Id, Status = GameStatus.Lobby }; context.Games.Add(game); // Host wird automatisch als erster Spieler hinzugefügt var hostPlayer = new Player { UserId = host.Id, GameId = game.Id, ConnectionId = connectionId }; context.Players.Add(hostPlayer); await context.SaveChangesAsync(); return game; } public async Task JoinGameAsync(string lobbyCode, int userId, string? connectionId = null) { using var context = _contextFactory.CreateDbContext(); var game = await context.Games .Include(g => g.Players) .FirstOrDefaultAsync(g => g.LobbyCode == lobbyCode && g.Status == GameStatus.Lobby) ?? throw new InvalidOperationException("Game not found or already started"); if (game.Players.Count >= game.MaxPlayers) throw new InvalidOperationException("Game is full"); var user = await context.Users.FirstOrDefaultAsync(u => u.Id == userId) ?? throw new InvalidOperationException("User not found"); // Prüfen ob User bereits im Spiel ist if (game.Players.Any(p => p.UserId == userId)) throw new InvalidOperationException("User already in game"); var player = new Player { UserId = user.Id, GameId = game.Id, ConnectionId = connectionId }; context.Players.Add(player); await context.SaveChangesAsync(); return game; } public async Task StartGameAsync(int gameId) { using var context = _contextFactory.CreateDbContext(); var game = await context.Games.FirstOrDefaultAsync(g => g.Id == gameId) ?? throw new InvalidOperationException("Game not found"); game.Status = GameStatus.InProgress; game.StartedAt = DateTime.UtcNow; var round = new GameRound { GameId = gameId, RoundNumber = 1 }; context.GameRounds.Add(round); await context.SaveChangesAsync(); return game; } public async Task SetPlayerReadyAsync(int gameId, int playerId) { using var context = _contextFactory.CreateDbContext(); var player = await context.Players.FirstOrDefaultAsync(p => p.Id == playerId) ?? throw new InvalidOperationException("Player not found"); player.IsReady = true; await context.SaveChangesAsync(); return await context.Games.FirstOrDefaultAsync(g => g.Id == gameId) ?? throw new InvalidOperationException("Game not found"); } public async Task> DealCardsAsync(int gameId) { using var context = _contextFactory.CreateDbContext(); var players = await context.Players.Where(p => p.GameId == gameId).ToListAsync(); var currentRound = await context.GameRounds .Where(gr => gr.GameId == gameId && gr.Status == RoundStatus.Waiting) .FirstOrDefaultAsync() ?? throw new InvalidOperationException("No active round found"); var allPhrases = await context.Phrases.Where(p => p.IsActive).ToListAsync(); if (allPhrases.Count < 5) throw new InvalidOperationException("Not enough phrases in database"); var dealtCards = new List(); foreach (var player in players) { var selectedPhrases = allPhrases.OrderBy(_ => Random.Shared.Next()).Take(5); foreach (var phrase in selectedPhrases) { var card = new PlayerCard { PlayerId = player.Id, PhraseId = phrase.Id, GameRoundId = currentRound.Id, IsUsed = false }; context.PlayerCards.Add(card); dealtCards.Add(card); } } currentRound.Status = RoundStatus.Active; await context.SaveChangesAsync(); return dealtCards; } public async Task SubmitSlipAsync(int gameId, int playerId, int cardId) { using var context = _contextFactory.CreateDbContext(); var card = await context.PlayerCards .Include(pc => pc.Player) .FirstOrDefaultAsync(pc => pc.Id == cardId) ?? throw new InvalidOperationException("Card not found"); if (card.PlayerId != playerId) throw new UnauthorizedAccessException("Card does not belong to player"); card.IsUsed = true; await context.SaveChangesAsync(); return true; } public async Task CreateChallengeAsync(int gameId, int challengingPlayerId, int targetCardId) { using var context = _contextFactory.CreateDbContext(); var targetCard = await context.PlayerCards.FirstOrDefaultAsync(pc => pc.Id == targetCardId) ?? throw new InvalidOperationException("Target card not found"); var currentRound = await context.GameRounds .Where(gr => gr.GameId == gameId && gr.Status == RoundStatus.Active) .FirstOrDefaultAsync() ?? throw new InvalidOperationException("No active round found"); if (currentRound == null) throw new InvalidOperationException("No active round found"); var challenge = new SlipChallenge { GameRoundId = currentRound.Id, ChallengingPlayerId = challengingPlayerId, TargetPlayerId = targetCard.PlayerId, TargetCardId = targetCardId, Status = ChallengeStatus.Pending }; context.SlipChallenges.Add(challenge); await context.SaveChangesAsync(); return challenge; } public async Task ResolveChallengeAsync(int challengeId, bool approved) { using var context = _contextFactory.CreateDbContext(); var challenge = await context.SlipChallenges .Include(sc => sc.GameRound) .Include(sc => sc.TargetCard) .FirstOrDefaultAsync(sc => sc.Id == challengeId) ?? throw new InvalidOperationException("Challenge not found"); // approved = Beschuldigung stimmt (Phrase war falsch) // rejected = Beschuldigung stimmt nicht (Phrase war richtig) if (!approved) { // FALSCHE BESCHULDIGUNG: Beschuldiger erhält die Phrase des Beschuldigten var targetCard = challenge.TargetCard; targetCard.PlayerId = challenge.ChallengingPlayerId; context.PlayerCards.Update(targetCard); } // Wenn approved: Phrase bleibt bei Beschuldigtem (= Phrase war tatsächlich ein SlipIn) challenge.Status = approved ? ChallengeStatus.Approved : ChallengeStatus.Rejected; challenge.ResolvedAt = DateTime.UtcNow; await context.SaveChangesAsync(); return challenge; } public async Task GetGameStateAsync(int gameId) { using var context = _contextFactory.CreateDbContext(); var game = await context.Games .Include(g => g.Players) .ThenInclude(p => p.User) .Include(g => g.Players) .ThenInclude(p => p.Cards) .FirstOrDefaultAsync(g => g.Id == gameId) ?? throw new InvalidOperationException("Game not found"); var playerInfos = game.Players.Select(p => new PlayerInfoDto { PlayerId = p.Id, Username = p.User.Username, Score = p.Score, IsReady = p.IsReady, CardCount = p.Cards.Count // Nur Anzahl, nicht die Karten selbst! }).ToList(); return new GameStateDto { GameId = game.Id, LobbyCode = game.LobbyCode, Status = game.Status.ToString(), Players = playerInfos, CurrentRound = game.Rounds?.Count ?? 0 }; } public async Task GetPlayerHandAsync(int playerId) { using var context = _contextFactory.CreateDbContext(); var cards = await context.PlayerCards .Include(pc => pc.Phrase) .Where(pc => pc.PlayerId == playerId) .Select(pc => new PlayerCardDto { CardId = pc.Id, PhraseId = pc.PhraseId, Text = pc.Phrase.Text, IsUsed = pc.IsUsed }) .ToListAsync(); return new PlayerHandDto { PlayerId = playerId, Cards = cards }; } public async Task GetGameAsync(int gameId) { using var context = _contextFactory.CreateDbContext(); return await context.Games.FirstOrDefaultAsync(g => g.Id == gameId) ?? throw new InvalidOperationException("Game not found"); } private string GenerateLobbyCode() { const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; var code = new string([.. Enumerable.Range(0, 6).Select(_ => chars[Random.Shared.Next(chars.Length)])]); return code; } }