using Dapper; using GmRelay.Bot.Features.Notifications; using GmRelay.Shared.Domain; using Npgsql; using Telegram.Bot; namespace GmRelay.Bot.Features.Reminders.SendJoinLink; // ── DTOs ───────────────────────────────────────────────────────────── internal sealed record JoinLinkSession( Guid Id, string Title, string JoinLink, DateTime ScheduledAt, long TelegramChatId, string NotificationMode); internal sealed record ConfirmedPlayer( long TelegramId, string DisplayName, string? TelegramUsername); // ── Handler ────────────────────────────────────────────────────────── /// /// Sends the join link to the group chat at T-5min, tagging all confirmed players. /// Called by SessionSchedulerService. /// public sealed class SendJoinLinkHandler( NpgsqlDataSource dataSource, ITelegramBotClient bot, DirectSessionNotificationSender directSender, ILogger logger) { public async Task HandleAsync(Guid sessionId, CancellationToken ct) { await using var connection = await dataSource.OpenConnectionAsync(ct); // 1. Load session var session = await connection.QuerySingleOrDefaultAsync( """ SELECT s.id, s.title, s.join_link AS JoinLink, s.scheduled_at AS ScheduledAt, g.telegram_chat_id AS TelegramChatId, s.notification_mode AS NotificationMode FROM sessions s JOIN game_groups g ON g.id = s.group_id WHERE s.id = @SessionId AND s.status = @Confirmed AND s.link_message_id IS NULL """, new { SessionId = sessionId, Confirmed = SessionStatus.Confirmed }); if (session is null) { logger.LogWarning("Session {SessionId} not eligible for join link", sessionId); return; } // 2. Load confirmed players var players = (await connection.QueryAsync( """ SELECT p.telegram_id AS TelegramId, p.display_name AS DisplayName, p.telegram_username AS TelegramUsername FROM session_participants sp JOIN players p ON p.id = sp.player_id WHERE sp.session_id = @SessionId AND sp.rsvp_status = @Confirmed AND sp.registration_status = @Active """, new { SessionId = sessionId, Confirmed = RsvpStatus.Confirmed, Active = ParticipantRegistrationStatus.Active })).ToList(); // 3. Build message with player mentions var mentions = string.Join(", ", players.Select(p => p.TelegramUsername is not null ? $"@{p.TelegramUsername}" : p.DisplayName)); var text = $""" 🎮 Игра «{session.Title}» начинается через 5 минут! 🔗 Ссылка на подключение: {session.JoinLink} Участники: {mentions} Хорошей игры! 🎲 """; // 4. Send var message = await bot.SendMessage( chatId: session.TelegramChatId, text: text, cancellationToken: ct); // 5. Mark as sent (idempotent — link_message_id IS NULL guard in query) await connection.ExecuteAsync( """ UPDATE sessions SET link_message_id = @MessageId, updated_at = now() WHERE id = @SessionId AND link_message_id IS NULL """, new { SessionId = sessionId, MessageId = message.MessageId }); var mode = SessionNotificationModeExtensions.FromDatabaseValue(session.NotificationMode); if (mode.ShouldSendDirectMessages()) { var directText = $""" 🎮 Игра начинается через 5 минут 📌 {System.Net.WebUtility.HtmlEncode(session.Title)} 🔗 {System.Net.WebUtility.HtmlEncode(session.JoinLink)} """; await directSender.SendAsync( players.Select(p => new DirectNotificationRecipient(p.TelegramId, p.DisplayName)), directText, "join-link", sessionId, ct); } logger.LogInformation( "Join link sent for session {SessionId} ({Title}), message_id={MessageId}", sessionId, session.Title, message.MessageId); } }