using Dapper; using GmRelay.Bot.Infrastructure.Telegram; using GmRelay.Shared.Domain; using GmRelay.Shared.Platform; using Npgsql; namespace GmRelay.Bot.Features.Sessions.RescheduleSession; // ── Command ────────────────────────────────────────────────────────── public sealed record InitiateRescheduleCommand( Guid SessionId, long TelegramUserId, string CallbackQueryId, long ChatId, int? MessageThreadId, int MessageId); // ── DTOs ───────────────────────────────────────────────────────────── internal sealed record RescheduleSessionInfoDto(string Title, bool CanManage); // ── Handler ────────────────────────────────────────────────────────── /// /// Handles the "⏰ Перенести" button press from the batch message. /// Creates a reschedule proposal in AwaitingTime status and prompts /// the GM to enter 2-3 new time options and a voting deadline. /// public sealed class InitiateRescheduleHandler( NpgsqlDataSource dataSource, IPlatformMessenger messenger, ILogger logger) { public async Task HandleAsync(InitiateRescheduleCommand command, CancellationToken ct) { await using var connection = await dataSource.OpenConnectionAsync(ct); // 1. Verify group management access. var session = await connection.QuerySingleOrDefaultAsync( """ SELECT s.title AS Title, EXISTS ( SELECT 1 FROM group_managers gm JOIN players p ON p.id = gm.player_id WHERE gm.group_id = s.group_id AND p.telegram_id = @TelegramUserId ) AS CanManage FROM sessions s WHERE s.id = @SessionId AND s.status != @Cancelled """, new { command.SessionId, command.TelegramUserId, Cancelled = SessionStatus.Cancelled }); if (session is null) { await AnswerAsync(command.CallbackQueryId, "Сессия не найдена.", ct); return; } if (!session.CanManage) { await AnswerAsync(command.CallbackQueryId, "Только owner или co-GM может переносить сессию.", ct, showAlert: true); return; } // 2. Check no active proposal exists var hasActive = await connection.ExecuteScalarAsync( """ SELECT EXISTS ( SELECT 1 FROM reschedule_proposals WHERE session_id = @SessionId AND status IN ('AwaitingTime', 'Voting') ) """, new { command.SessionId }); if (hasActive) { await AnswerAsync(command.CallbackQueryId, "Уже есть активный запрос на перенос этой сессии.", ct, showAlert: true); return; } // 3. Create proposal in AwaitingTime status await connection.ExecuteAsync( """ INSERT INTO reschedule_proposals (session_id, proposed_by, source_platform, status) VALUES (@SessionId, @GmId, 'Telegram', 'AwaitingTime') """, new { command.SessionId, GmId = command.TelegramUserId }); logger.LogInformation("Reschedule initiated for session {SessionId} by GM {GmId}", command.SessionId, command.TelegramUserId); // 4. Prompt GM in chat await AnswerAsync(command.CallbackQueryId, "Введите 2-3 варианта времени и дедлайн голосования.", ct); var prompt = string.Join( "\n", new[] { $"⏰ Укажите 2-3 варианта времени для сессии «{session.Title}» и дедлайн голосования.", "", "Формат:", "25.04.2026 19:30", "26.04.2026 18:00", "Дедлайн: 25.04.2026 12:00", "", "Дедлайн должен быть в будущем и раньше первого предложенного времени." }); await messenger.SendGroupMessageAsync( TelegramPlatformIds.Group(command.ChatId, command.MessageThreadId), prompt, ct); } private Task AnswerAsync(string callbackQueryId, string text, CancellationToken ct, bool showAlert = false) => messenger.AnswerInteractionAsync(new PlatformInteractionReply(callbackQueryId, text, showAlert), ct); }