"""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}")