import logging
from datetime import date, timedelta
from typing import Optional, Literal, Dict, Any
from aiogram import Router, types, F, Bot
from aiogram.filters import Command, or_f
from bot.database.models import User as DbUser, Game as DbGame, GameChoice
from bot.database.db import (
create_or_get_today_game,
update_game_choice,
get_user_by_id,
finish_game,
update_streak,
get_game_by_id,
get_game_on_date
)
from bot.keyboards.game_keyboard import get_game_choice_keyboard, GameChoiceCallback
from bot.keyboards.reply_keyboards import PLAY_BUTTON_TEXT
from bot.utils.game_logic import determine_winner, CHOICE_NAMES_RU
from bot.utils.helpers import get_partner
from bot.display_names import DISPLAY_NAMES
router = Router()
async def check_and_resolve_yesterdays_game(user_id: int, partner_id: int, bot: Bot) -> bool:
"""
Проверяет наличие незавершенной игры за вчерашний день и завершает ее по правилам.
Возвращает True, если игра была найдена и обработана, иначе False.
"""
yesterday = date.today() - timedelta(days=1)
game = await get_game_on_date(user_id, partner_id, yesterday)
if not game or game.is_finished:
return False
logging.info(f"Found unfinished game from yesterday ({yesterday}) for users {user_id} and {partner_id}. Resolving...")
p1_id = game.player1_id
p2_id = game.player2_id
p1_choice = game.player1_choice
p2_choice = game.player2_choice
p1_user = await get_user_by_id(p1_id)
p2_user = await get_user_by_id(p2_id)
if not p1_user or not p2_user:
logging.error(f"Could not find user data for yesterday's game {game.id}")
await finish_game(game.id, None)
return True
p1_name = DISPLAY_NAMES.get(p1_user.telegram_id, p1_user.username or f"Пользователь {p1_user.telegram_id}")
p2_name = DISPLAY_NAMES.get(p2_user.telegram_id, p2_user.username or f"Пользователь {p2_user.telegram_id}")
winner_db_id: Optional[int] = None
result_message: str = ""
p1_win: bool = False
p2_win: bool = False
if p1_choice and not p2_choice:
winner_db_id = p1_id
p1_choice_ru = CHOICE_NAMES_RU.get(p1_choice, p1_choice)
result_message = (
f"⏳ Вчерашняя игра ({yesterday}):\n"
f"{p1_name} ({p1_choice_ru}) побеждает! 🎉\n"
f"({p2_name} не сделал(а) свой ход)"
)
p1_win = True
p2_win = False
logging.info(f"Yesterday's game {game.id}: Player 1 ({p1_id}) wins by default.")
elif not p1_choice and p2_choice:
winner_db_id = p2_id
p2_choice_ru = CHOICE_NAMES_RU.get(p2_choice, p2_choice)
result_message = (
f"⏳ Вчерашняя игра ({yesterday}):\n"
f"{p2_name} ({p2_choice_ru}) побеждает! 🎉\n"
f"({p1_name} не сделал(а) свой ход)"
)
p1_win = False
p2_win = True
logging.info(f"Yesterday's game {game.id}: Player 2 ({p2_id}) wins by default.")
else:
winner_db_id = None
result_message = (
f"⏳ Вчерашняя игра ({yesterday}):\n"
f"Ничья! Игра аннулирована, так как не все сделали ход. 🤷♀️🤷♂️"
)
p1_win = False
p2_win = False
logging.info(f"Yesterday's game {game.id}: Draw (at least one player did not choose).")
await finish_game(game.id, winner_db_id)
await update_streak(p1_id, win=p1_win)
await update_streak(p2_id, win=p2_win)
full_result_message = result_message + "\n\nНачинаем игру на сегодня! /play"
try:
await bot.send_message(p1_user.telegram_id, full_result_message, parse_mode="HTML")
except Exception as e:
logging.error(f"Failed to send yesterday result to user {p1_user.telegram_id}: {e}")
try:
await bot.send_message(p2_user.telegram_id, full_result_message, parse_mode="HTML")
except Exception as e:
logging.error(f"Failed to send yesterday result to user {p2_user.telegram_id}: {e}")
return True
@router.message(or_f(Command("play"), F.text == PLAY_BUTTON_TEXT))
async def handle_play(
message: types.Message,
bot: Bot,
**kwargs
):
"""Обработчик команды /play. Проверяет вчерашнюю игру и предлагает сделать ход."""
user_db_obj: Optional[DbUser] = kwargs.get('user_db_obj')
if not user_db_obj:
logging.error(f"Error in handle_play: user_db_obj not found in kwargs for user {message.from_user.id}")
await message.answer("Произошла ошибка при получении ваших данных.")
return
partner = await get_partner(user_db_obj.id)
if not partner:
await message.answer("Для игры нужен партнер. Дождитесь, пока второй пользователь присоединится.")
return
try:
await check_and_resolve_yesterdays_game(user_db_obj.id, partner.id, bot)
except Exception as e:
logging.exception(f"Error checking/resolving yesterday's game for {user_db_obj.id} and {partner.id}")
game = await create_or_get_today_game(user_db_obj.id, partner.id)
if not game:
await message.answer("Не удалось начать игру. Попробуйте позже.")
logging.error(f"Failed to create/get game for users {user_db_obj.id} and {partner.id}")
return
if game.is_finished:
await message.answer("Вы уже сыграли сегодня! Приходите завтра ❤️")
return
current_player_choice = game.player1_choice if game.player1_id == user_db_obj.id else game.player2_choice
if current_player_choice:
choice_name_ru = CHOICE_NAMES_RU.get(current_player_choice, current_player_choice)
await message.answer(f"Вы уже сделали свой ход сегодня ({choice_name_ru}). Ожидаем хода партнера! 😉")
return
await message.answer("Кто больше любит сегодня? 😉 Сделайте свой выбор:", reply_markup=get_game_choice_keyboard())
@router.callback_query(GameChoiceCallback.filter())
async def handle_game_choice(
callback: types.CallbackQuery,
callback_data: GameChoiceCallback,
bot: Bot,
**kwargs
):
"""Обработчик нажатия на кнопку выбора хода."""
user_db_obj: Optional[DbUser] = kwargs.get('user_db_obj')
if not user_db_obj:
logging.error(f"Error in handle_game_choice: user_db_obj not found in kwargs for user {callback.from_user.id}")
await callback.answer("Произошла ошибка при получении ваших данных.", show_alert=True)
try:
await callback.message.edit_text("Произошла ошибка при обработке вашего выбора.")
except Exception:
pass
return
choice: GameChoice = callback_data.choice
choice_name_ru = CHOICE_NAMES_RU.get(choice, choice)
partner = await get_partner(user_db_obj.id)
if not partner:
await callback.answer("Ошибка: не найден партнер.", show_alert=True)
await callback.message.edit_text("Не удалось обработать ваш ход: партнер не найден.")
return
game = await create_or_get_today_game(user_db_obj.id, partner.id)
if not game:
await callback.answer("Ошибка: не найдена текущая игра.", show_alert=True)
await callback.message.edit_text("Не удалось обработать ваш ход: игра не найдена.")
logging.error(f"Game not found for users {user_db_obj.id} and {partner.id} during callback")
return
if game.is_finished:
await callback.answer("Игра уже завершена.", show_alert=True)
await callback.message.edit_text("Вы опоздали, игра на сегодня уже закончилась!" )
return
player_field = "player1_choice" if game.player1_id == user_db_obj.id else "player2_choice"
current_choice = getattr(game, player_field)
if current_choice is not None:
current_choice_ru = CHOICE_NAMES_RU.get(current_choice, current_choice)
await callback.answer("Вы уже сделали свой ход.", show_alert=True)
await callback.message.edit_text(f"Вы уже выбрали: {current_choice_ru}\nОжидаем ход партнера... ✨")
return
updated = await update_game_choice(game.id, user_db_obj.id, choice)
if not updated:
await callback.answer("Ошибка: не удалось сохранить ваш выбор.", show_alert=True)
await callback.message.edit_text("Произошла ошибка при сохранении вашего хода. Попробуйте еще раз.")
logging.error(f"Failed to update choice for game {game.id}, user {user_db_obj.id}")
return
await callback.answer(f"Вы выбрали: {choice_name_ru}")
await callback.message.edit_text(f"Вы выбрали: {choice_name_ru}\nОжидаем ход партнера... ✨")
updated_game = await get_game_by_id(game.id)
if not updated_game:
logging.error(f"Failed to fetch updated game {game.id} after choice update.")
return
if updated_game.player1_choice and updated_game.player2_choice:
winner_relation = determine_winner(updated_game.player1_choice, updated_game.player2_choice)
winner_db_id: Optional[int] = None
result_text = ""
p1_choice_ru = CHOICE_NAMES_RU[updated_game.player1_choice]
p2_choice_ru = CHOICE_NAMES_RU[updated_game.player2_choice]
p1_user = await get_user_by_id(updated_game.player1_id)
p2_user = await get_user_by_id(updated_game.player2_id)
if not p1_user or not p2_user:
logging.error(f"Could not find user data for game {updated_game.id}")
await bot.send_message(user_db_obj.telegram_id, "Ошибка: не удалось получить данные игроков для завершения игры.")
await bot.send_message(partner.telegram_id, "Ошибка: не удалось получить данные игроков для завершения игры.")
return
p1_name = DISPLAY_NAMES.get(p1_user.telegram_id, p1_user.username or f"Пользователь {p1_user.telegram_id}")
p2_name = DISPLAY_NAMES.get(p2_user.telegram_id, p2_user.username or f"Пользователь {p2_user.telegram_id}")
if winner_relation == 1:
winner_db_id = updated_game.player1_id
result_text = f"{p1_name} ({p1_choice_ru}) побеждает {p2_name} ({p2_choice_ru})!\n{p1_name} сегодня любит больше! ❤️"
elif winner_relation == 2:
winner_db_id = updated_game.player2_id
result_text = f"{p2_name} ({p2_choice_ru}) побеждает {p1_name} ({p1_choice_ru})!\n{p2_name} сегодня любит больше! ❤️"
else:
result_text = f"Ничья! Оба выбрали {p1_choice_ru}.\nСегодня вы любите друг друга одинаково сильно! 🥰"
await finish_game(updated_game.id, winner_db_id)
await update_streak(updated_game.player1_id, win=(winner_relation == 1))
await update_streak(updated_game.player2_id, win=(winner_relation == 2))
final_message = f"Игра за {updated_game.game_date.strftime('%d.%m.%Y')} завершена! 🎉\n\n{result_text}\n\nПосмотреть статистику: /stats"
try:
await bot.send_message(p1_user.telegram_id, final_message, parse_mode="HTML")
except Exception as e:
logging.error(f"Failed to send result to user {p1_user.telegram_id}: {e}")
try:
await bot.send_message(p2_user.telegram_id, final_message, parse_mode="HTML")
except Exception as e:
logging.error(f"Failed to send result to user {p2_user.telegram_id}: {e}")
else:
partner_user = await get_user_by_id(partner.id)
if partner_user:
current_user_name = DISPLAY_NAMES.get(user_db_obj.telegram_id, user_db_obj.username or f"Пользователь {user_db_obj.telegram_id}")
try:
await bot.send_message(
partner_user.telegram_id,
f"Ваш партнер, {current_user_name}, сделал свой ход! 😉\nТеперь ваша очередь решить, кто любит больше! Используйте команду /play, чтобы сделать ход."
)
except Exception as e:
logging.error(f"Failed to notify partner {partner_user.telegram_id}: {e}")
else:
logging.error(f"Could not find partner user data for notification (partner_id: {partner.id})")