"""Handlers for creating reminders with FSM.""" from aiogram import Router, F from aiogram.types import Message, CallbackQuery from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession from bot.states.reminder_states import CreateReminderStates from bot.keyboards.reminders import ( get_interval_selection_keyboard, get_confirmation_keyboard, ReminderIntervalCallback, ConfirmCallback, ) from bot.keyboards.main_menu import get_main_menu_keyboard from bot.utils.validators import validate_time_format, validate_days_interval from bot.utils.formatting import format_interval_days from bot.services.user_service import UserService from bot.services.reminders_service import RemindersService from bot.services.time_service import get_time_service from bot.logging_config import get_logger logger = get_logger(__name__) router = Router(name="reminders_create") reminders_service = RemindersService() time_service = get_time_service() @router.message(F.text == "➕ Новое напоминание") async def start_create_reminder(message: Message, state: FSMContext) -> None: """ Start reminder creation flow. Args: message: Telegram message state: FSM state context """ await state.set_state(CreateReminderStates.waiting_for_text) await message.answer( "Что нужно напоминать?\n\nВведи текст напоминания:" ) @router.message(CreateReminderStates.waiting_for_text) async def process_reminder_text(message: Message, state: FSMContext) -> None: """ Process reminder text input. Args: message: Telegram message state: FSM state context """ text = message.text.strip() if not text or len(text) > 1000: await message.answer( "Текст напоминания должен быть от 1 до 1000 символов. Попробуй ещё раз:" ) return # Save text to state await state.update_data(text=text) await state.set_state(CreateReminderStates.waiting_for_interval) await message.answer( "Как часто напоминать? Выбери или введи количество дней:", reply_markup=get_interval_selection_keyboard(), ) @router.callback_query( CreateReminderStates.waiting_for_interval, ReminderIntervalCallback.filter() ) async def process_interval_button( callback: CallbackQuery, callback_data: ReminderIntervalCallback, state: FSMContext, ) -> None: """ Process interval selection via button. Args: callback: Callback query callback_data: Parsed callback data state: FSM state context """ if callback_data.days == 0: # User chose "Other" await callback.message.edit_text( "Введи количество дней (целое положительное число):" ) await callback.answer() return # Save interval and proceed await state.update_data(days_interval=callback_data.days) await state.set_state(CreateReminderStates.waiting_for_time) await callback.message.edit_text( f"Выбрано: {format_interval_days(callback_data.days)}\n\n" "В какое время напоминать?\n" "Введи время в формате ЧЧ:ММ (например, 09:00 или 18:30):" ) await callback.answer() @router.message(CreateReminderStates.waiting_for_interval) async def process_interval_text(message: Message, state: FSMContext) -> None: """ Process interval input as text. Args: message: Telegram message state: FSM state context """ days = validate_days_interval(message.text) if days is None: await message.answer( "Некорректное значение. Введи целое положительное число дней:" ) return # Save interval and proceed await state.update_data(days_interval=days) await state.set_state(CreateReminderStates.waiting_for_time) await message.answer( f"Отлично, {format_interval_days(days)}!\n\n" "В какое время напоминать?\n" "Введи время в формате ЧЧ:ММ (например, 09:00 или 18:30):" ) @router.message(CreateReminderStates.waiting_for_time) async def process_reminder_time(message: Message, state: FSMContext) -> None: """ Process reminder time input. Args: message: Telegram message state: FSM state context """ time_of_day = validate_time_format(message.text) if time_of_day is None: await message.answer( "Некорректный формат времени. Используй формат ЧЧ:ММ (например, 09:00):" ) return # Save time and show confirmation await state.update_data(time_of_day=time_of_day) await state.set_state(CreateReminderStates.waiting_for_confirmation) # Get all data for confirmation data = await state.get_data() text = data["text"] days_interval = data["days_interval"] confirmation_text = ( "Создать напоминание?\n\n" f"📝 Текст: {text}\n" f"🔄 Периодичность: {format_interval_days(days_interval)}\n" f"🕐 Время: {time_of_day.strftime('%H:%M')}" ) await message.answer( confirmation_text, reply_markup=get_confirmation_keyboard(entity="create"), parse_mode="HTML", ) @router.callback_query( CreateReminderStates.waiting_for_confirmation, ConfirmCallback.filter(F.entity == "create") ) async def process_create_confirmation( callback: CallbackQuery, callback_data: ConfirmCallback, state: FSMContext, session: AsyncSession, ) -> None: """ Process create reminder confirmation. Args: callback: Callback query callback_data: Parsed callback data state: FSM state context session: Database session """ if callback_data.action == "no": await state.clear() await callback.message.edit_text("Создание напоминания отменено.") await callback.message.answer( "Выбери действие из меню:", reply_markup=get_main_menu_keyboard(), ) await callback.answer() return # Get user user = await UserService.ensure_user_exists(session, callback.from_user) # Get data from state data = await state.get_data() text = data["text"] days_interval = data["days_interval"] time_of_day = data["time_of_day"] # Create reminder reminder = await reminders_service.create_new_reminder( session=session, user_id=user.id, text=text, days_interval=days_interval, time_of_day=time_of_day, ) # Clear state await state.clear() # Format next run time next_run_str = time_service.format_next_run(reminder.next_run_at) await callback.message.edit_text( f"✅ Напоминание создано!\n\n" f"Первое напоминание будет {next_run_str}." ) await callback.message.answer( "Выбери действие из меню:", reply_markup=get_main_menu_keyboard(), ) await callback.answer("Напоминание создано!") logger.info(f"User {user.tg_user_id} created reminder {reminder.id}")