using Dapper; using GmRelay.Shared.Domain; using GmRelay.Shared.Rendering; using Npgsql; using Telegram.Bot; using Telegram.Bot.Types; namespace GmRelay.Bot.Features.Sessions.CreateSession; public sealed class CreateSessionHandler( NpgsqlDataSource dataSource, ITelegramBotClient botClient, ILogger logger) { public async Task HandleAsync(Message message, CancellationToken cancellationToken) { var parseResult = NewSessionCommandParser.Parse(message.Text, DateTimeOffset.UtcNow); foreach (var timeInput in parseResult.PastTimeInputs) { await botClient.SendMessage( message.Chat.Id, $"⚠️ Предупреждение: дата {timeInput} находится в прошлом и будет пропущена.", cancellationToken: cancellationToken); } foreach (var timeInput in parseResult.InvalidTimeInputs) { await botClient.SendMessage( message.Chat.Id, $"⚠️ Предупреждение: некорректный формат времени '{timeInput}'. Пропущено.", cancellationToken: cancellationToken); } foreach (var seatLimitInput in parseResult.InvalidSeatLimitInputs) { await botClient.SendMessage( message.Chat.Id, $"⚠️ Предупреждение: некорректный лимит мест '{seatLimitInput}'. Укажите целое число больше 0.", cancellationToken: cancellationToken); } if (!parseResult.IsValid) { await botClient.SendMessage( chatId: message.Chat.Id, text: "❌ Не удалось распознать формат. Пожалуйста, используйте шаблон:\n\n/newsession\nНазвание: My Game\nВремя: 15.05.2026 19:30\nВремя: 22.05.2026 19:30\nМест: 4\nСсылка: https://link", cancellationToken: cancellationToken); return; } var title = parseResult.Title!; var link = parseResult.Link!; var gmId = message.From!.Id; var gmName = message.From.FirstName + (string.IsNullOrEmpty(message.From.LastName) ? string.Empty : $" {message.From.LastName}"); var gmUsername = message.From.Username; var chatId = message.Chat.Id; var chatTitle = message.Chat.Title ?? "Private Chat"; await using var connection = await dataSource.OpenConnectionAsync(cancellationToken); await using var transaction = await connection.BeginTransactionAsync(cancellationToken); try { await connection.ExecuteAsync( """ INSERT INTO players (telegram_id, display_name, telegram_username) VALUES (@TgId, @Name, @Username) ON CONFLICT (telegram_id) DO UPDATE SET display_name = EXCLUDED.display_name, telegram_username = EXCLUDED.telegram_username; """, new { TgId = gmId, Name = gmName, Username = gmUsername }, transaction); var groupId = await connection.ExecuteScalarAsync( """ INSERT INTO game_groups (telegram_chat_id, name, gm_telegram_id) VALUES (@ChatId, @ChatName, @GmId) ON CONFLICT (telegram_chat_id) DO UPDATE SET name = EXCLUDED.name RETURNING id; """, new { ChatId = chatId, ChatName = chatTitle, GmId = gmId }, transaction); int? messageThreadId = null; if (message.Chat.IsForum) { var topic = await botClient.CreateForumTopic( chatId: chatId, name: $"🎲 Игры: {title}", cancellationToken: cancellationToken); messageThreadId = topic.MessageThreadId; } var batchId = Guid.NewGuid(); var sessions = new List(); foreach (var scheduledAt in parseResult.ScheduledTimes.OrderBy(value => value)) { var sessionId = await connection.ExecuteScalarAsync( """ INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, thread_id, max_players) VALUES (@BatchId, @GroupId, @Title, @Link, @ScheduledAt, @Status, @ThreadId, @MaxPlayers) RETURNING id; """, new { BatchId = batchId, GroupId = groupId, Title = title, Link = link, ScheduledAt = scheduledAt, ThreadId = messageThreadId, MaxPlayers = parseResult.MaxPlayers, Status = SessionStatus.Planned }, transaction); sessions.Add(new SessionBatchDto(sessionId, scheduledAt.UtcDateTime, SessionStatus.Planned, parseResult.MaxPlayers)); } await transaction.CommitAsync(cancellationToken); logger.LogInformation("Создан батч {BatchId} с {Count} сессиями в группе {GroupId}", batchId, sessions.Count, groupId); var renderResult = SessionBatchRenderer.Render(title, sessions, Array.Empty()); var batchMessage = await botClient.SendMessage( chatId: chatId, messageThreadId: messageThreadId, text: renderResult.Text, parseMode: Telegram.Bot.Types.Enums.ParseMode.Html, replyMarkup: renderResult.Markup, cancellationToken: cancellationToken); await connection.ExecuteAsync( "UPDATE sessions SET batch_message_id = @MsgId WHERE batch_id = @BatchId", new { MsgId = batchMessage.MessageId, BatchId = batchId }); try { await botClient.DeleteMessage( chatId: chatId, messageId: message.MessageId, cancellationToken: cancellationToken); } catch (Exception ex) { logger.LogWarning(ex, "Не удалось удалить исходное сообщение {MessageId} в чате {ChatId}", message.MessageId, chatId); } } catch (Exception ex) { logger.LogError(ex, "Ошибка при создании сессии"); await transaction.RollbackAsync(cancellationToken); await botClient.SendMessage(chatId, "💥 Произошла ошибка базы данных при создании сессии.", cancellationToken: cancellationToken); } } }