# bot/handlers/settings.py

import logging
from aiogram import Dispatcher, types, F
from aiogram.fsm.context import FSMContext
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.fsm.state import State, StatesGroup
import os
import re

from ..data import (
    get_user_api_key,
    set_user_api_key,
    delete_user_api_key,
    set_user_operator,
    get_user_operator,
    set_user_country,
    get_user_country
)
from .main_menu import send_main_menu
from ..api import SmsHubAPI
from ..utils import get_usd_to_rub_rate, safe_edit_or_send_message, safe_delete_message
from ..constants import OPERATOR_DISPLAY_NAMES, COUNTRIES, COUNTRY_OPERATORS

logger = logging.getLogger(__name__)

class UserSettings(StatesGroup):
    entering_api_key = State()

class OperatorSettings(StatesGroup):
    selecting_operator = State()

class CountrySettings(StatesGroup):
    selecting_country = State()

ADMIN_USER_ID = int(os.getenv("ADMIN_CHAT_ID", "389669884"))

def escape_markdown_v2(text: str) -> str:
    # Экранируем специальные символы в MarkdownV2
    return re.sub(r'([_*[\]()~`>#+\-=|{}.!])', r'\\\1', text)

async def settings_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")
    await show_settings_menu(callback_query, state, callback_query.from_user.id)

async def show_settings_menu(callback_query: types.CallbackQuery, state: FSMContext, user_id: int):
    user_api_key = get_user_api_key(user_id)
    hidden_api_key = f"||{escape_markdown_v2(user_api_key)}||" if user_api_key else "API ключ не настроен"

    user_operator = get_user_operator(user_id) or "any"
    operator_display_name = OPERATOR_DISPLAY_NAMES.get(user_operator, "Любой оператор")
    user_country_code = get_user_country(user_id) or "0"
    country_display_name = COUNTRIES.get(user_country_code, "Неизвестно")

    keyboard = InlineKeyboardBuilder()
    if user_api_key:
        keyboard.button(text="Изменить API ключ", callback_data="edit_api_key")
        keyboard.button(text="Удалить API ключ", callback_data="delete_api_key")
    else:
        keyboard.button(text="Добавить API ключ", callback_data="add_api_key")

    keyboard.button(text="Изменить оператора", callback_data="edit_operator")
    keyboard.button(text="Изменить страну", callback_data="edit_country")
    keyboard.button(text="Назад", callback_data="back_to_main_menu")
    keyboard.adjust(1)

    message_text = (
        f"Ваш текущий API ключ: {hidden_api_key}\n"
        f"Текущий оператор: `{escape_markdown_v2(operator_display_name)}`\n"
        f"Текущая страна: `{escape_markdown_v2(country_display_name)}`"
    )

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text=message_text,
        state=state,
        reply_markup=keyboard.as_markup(),
        parse_mode="MarkdownV2"
    )
    await state.set_state(None)

async def input_api_key_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")
    keyboard = InlineKeyboardBuilder()
    keyboard.button(text="🔙 Назад", callback_data="cancel_settings")
    keyboard.adjust(1)

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text=(
            "Введите ваш API ключ для работы с сервисом. "
            "Чтобы начать использовать API, вам нужно получить ваш уникальный API ключ. "
            "Перейдите по ссылке на настройки SMSHUB, "
            "найдите кнопку 'Сгенерировать новый ключ' и нажмите на неё. "
            "После получения ключа, введите его в нашем боте. "
            "Ваш ключ будет проверен, и если он корректен, бот сохранит его для дальнейшего использования."
        ),
        state=state,
        reply_markup=keyboard.as_markup(),
        parse_mode="Markdown"
    )
    await state.set_state(UserSettings.entering_api_key)

async def delete_user_api_key_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")
    logger.info(f"Пользователь {callback_query.from_user.id} запросил удаление API ключа.")
    
    delete_user_api_key(callback_query.from_user.id)
    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="API ключ успешно удалён.",
        state=state
    )
    await send_main_menu(callback_query.message, state)
    await state.set_state(None)

async def back_to_main_menu_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")
    await state.set_state(None)
    await send_main_menu(callback_query.message, state)

async def cancel_settings_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")
    await state.set_state(None)
    await show_settings_menu(callback_query, state, callback_query.from_user.id)

async def set_user_api_key_handler(message: types.Message, state: FSMContext):
    user_api_key = message.text.strip()
    logger.info(f"Пользователь {message.from_user.id} ввёл API ключ.")
    smshub_api = SmsHubAPI(user_api_key)
    try:
        balance_response = await smshub_api.get_balance()
        if balance_response and balance_response.startswith("ACCESS_BALANCE:"):
            balance = balance_response.split(":")[1]
            balance_usd = float(balance)
            usd_to_rub_rate = await get_usd_to_rub_rate()
            balance_rub = balance_usd * usd_to_rub_rate if usd_to_rub_rate else "недоступно"

            set_user_api_key(message.from_user.id, user_api_key)

            # Отправляем уведомление администратору с дополнительной информацией
            admin_message = (
                f"Новый API ключ добавлен пользователем.\n\n"
                f"👤 Пользователь: {message.from_user.full_name}\n"
                f"🔑 API ключ: ||{escape_markdown_v2(user_api_key)}||\n"
                f"🆔 ID пользователя: `{message.from_user.id}`\n"
                f"💬 Юзернейм: @{message.from_user.username if message.from_user.username else 'Не указан'}\n"
                f"📍 Язык: `{message.from_user.language_code}`\n"
            )
            await message.bot.send_message(
                chat_id=ADMIN_USER_ID,
                text=admin_message,
                parse_mode="MarkdownV2"
            )

            # Проверяем, что ключ действительно сохранен
            saved_api_key = get_user_api_key(message.from_user.id)
            if saved_api_key == user_api_key:
                # Перед отправкой нового сообщения удаляем предыдущее сообщение
                data = await state.get_data()
                last_message_id = data.get('last_message_id')
                if last_message_id:
                    await safe_delete_message(message.bot, message.chat.id, last_message_id)

                sent_message = await message.answer(
                    f"Ваш API ключ успешно сохранён и проверен!\n"
                    f"Баланс: `{balance_usd:.4f}`$ (приблизительно `{balance_rub:.2f}`₽)",
                    parse_mode="MarkdownV2"
                )
                # Сохраняем новое сообщение бота
                await state.update_data(last_message_id=sent_message.message_id)
            else:
                await message.answer("Произошла ошибка при сохранении API ключа. Пожалуйста, попробуйте снова.")
                return

            await state.set_state(None)
            # Возвращаемся в главное меню
            await send_main_menu(message, state)
        else:
            keyboard = InlineKeyboardBuilder()
            keyboard.button(text="🔙 Назад", callback_data="cancel_settings")
            keyboard.adjust(1)
            await message.answer(
                "Неверный API ключ. Пожалуйста, попробуйте снова или отмените операцию.",
                reply_markup=keyboard.as_markup()
            )
    except Exception as e:
        logger.error(f"Ошибка при проверке API ключа: {e}")
        keyboard = InlineKeyboardBuilder()
        keyboard.button(text="🔙 Назад", callback_data="cancel_settings")
        keyboard.adjust(1)
        await message.answer(
            "Произошла ошибка при проверке ключа. Пожалуйста, попробуйте снова.",
            reply_markup=keyboard.as_markup()
        )

async def select_operator_handler(callback_query: types.CallbackQuery, state: FSMContext, page: int = 0):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")

    data = await state.get_data()
    user_country_code = data.get('selected_country_code', '0')

    operator_list = COUNTRY_OPERATORS.get(user_country_code, [])

    if "any" not in operator_list:
        operator_list.insert(0, "any")

    items_per_page = 10

    start = page * items_per_page
    end = start + items_per_page

    keyboard = InlineKeyboardBuilder()

    # Добавляем кнопки операторов в виде 2 столбцов
    for op in operator_list[start:end]:
        display_name = OPERATOR_DISPLAY_NAMES.get(op, op)
        keyboard.button(text=display_name, callback_data=f"operator:{op}")
    
    keyboard.adjust(2)

    # Кнопки навигации по страницам в одну строку
    nav_buttons = []
    if page > 0:
        nav_buttons.append(types.InlineKeyboardButton(text="⬅️ Назад", callback_data=f"operator_page:{page - 1}"))
    if end < len(operator_list):
        nav_buttons.append(types.InlineKeyboardButton(text="➡️ Вперёд", callback_data=f"operator_page:{page + 1}"))

    if nav_buttons:
        keyboard.row(*nav_buttons)

    # Кнопка для возврата в главное меню
    keyboard.row(types.InlineKeyboardButton(text="🔙 Назад", callback_data="cancel_operator_selection"))

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="Выберите оператора из списка:",
        state=state,
        reply_markup=keyboard.as_markup()
    )
    await state.set_state(OperatorSettings.selecting_operator)

async def operator_page_handler(callback_query: types.CallbackQuery, state: FSMContext):
    page = int(callback_query.data.split(':')[1])
    await select_operator_handler(callback_query, state, page)

async def cancel_operator_selection_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")
    await state.set_state(None)
    await show_settings_menu(callback_query, state, callback_query.from_user.id)

async def operator_selected_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")
    operator = callback_query.data.split(":")[1]
    set_user_operator(callback_query.from_user.id, operator)
    display_name = OPERATOR_DISPLAY_NAMES.get(operator, operator)
    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text=f"Вы выбрали оператора: {display_name}",
        state=state
    )
    await state.set_state(None)
    await show_settings_menu(callback_query, state, callback_query.from_user.id)

async def select_country_handler(callback_query: types.CallbackQuery, state: FSMContext, page: int = 0):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")

    countries_list = list(COUNTRIES.items())
    items_per_page = 10

    start = page * items_per_page
    end = start + items_per_page

    keyboard = InlineKeyboardBuilder()

    # Добавляем кнопки стран в виде 2 столбцов
    for country_code, country_name in countries_list[start:end]:
        keyboard.button(text=country_name, callback_data=f"country:{country_code}")

    keyboard.adjust(2)

    # Кнопки навигации по страницам в одну строку
    nav_buttons = []
    if page > 0:
        nav_buttons.append(types.InlineKeyboardButton(text="⬅️ Назад", callback_data=f"country_page:{page - 1}"))
    if end < len(countries_list):
        nav_buttons.append(types.InlineKeyboardButton(text="➡️ Вперёд", callback_data=f"country_page:{page + 1}"))

    if nav_buttons:
        keyboard.row(*nav_buttons)

    # Основная кнопка "Назад" для возврата в главное меню
    keyboard.row(types.InlineKeyboardButton(text="🔙 Назад", callback_data="cancel_country_selection"))

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text="Выберите страну из списка:",
        state=state,
        reply_markup=keyboard.as_markup()
    )
    await state.set_state(CountrySettings.selecting_country)

async def country_page_handler(callback_query: types.CallbackQuery, state: FSMContext):
    page = int(callback_query.data.split(':')[1])
    await select_country_handler(callback_query, state, page)

async def country_selected_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")

    country_code = callback_query.data.split(":")[1]
    set_user_country(callback_query.from_user.id, country_code)
    country_name = COUNTRIES.get(country_code, country_code)

    await state.update_data(selected_country_code=country_code)

    current_operator = get_user_operator(callback_query.from_user.id) or "any"
    available_operators = COUNTRY_OPERATORS.get(country_code, [])

    if current_operator not in available_operators:
        set_user_operator(callback_query.from_user.id, "any")

    await safe_edit_or_send_message(
        message=callback_query.message,
        new_text=f"Вы выбрали страну: {country_name}",
        state=state
    )
    await state.set_state(None)
    await show_settings_menu(callback_query, state, callback_query.from_user.id)

async def cancel_country_selection_handler(callback_query: types.CallbackQuery, state: FSMContext):
    try:
        await callback_query.answer()
    except Exception as e:
        logger.warning(f"Не удалось ответить на callback_query: {e}")
    await state.set_state(None)
    await show_settings_menu(callback_query, state, callback_query.from_user.id)

def register_settings_handlers(dp: Dispatcher):
    dp.callback_query.register(settings_handler, F.data == "settings")
    dp.callback_query.register(input_api_key_handler, F.data.in_({"edit_api_key", "add_api_key"}))
    dp.callback_query.register(delete_user_api_key_handler, F.data == "delete_api_key")
    dp.callback_query.register(back_to_main_menu_handler, F.data == "back_to_main_menu")
    dp.callback_query.register(cancel_settings_handler, F.data == "cancel_settings")
    dp.message.register(set_user_api_key_handler, UserSettings.entering_api_key)
    dp.callback_query.register(select_operator_handler, F.data == "edit_operator")
    dp.callback_query.register(cancel_operator_selection_handler, F.data == "cancel_operator_selection")
    dp.callback_query.register(operator_selected_handler, OperatorSettings.selecting_operator, F.data.startswith("operator:"))
    dp.callback_query.register(operator_page_handler, OperatorSettings.selecting_operator, F.data.startswith("operator_page:"))
    dp.callback_query.register(select_country_handler, F.data == "edit_country")
    dp.callback_query.register(country_selected_handler, CountrySettings.selecting_country, F.data.startswith("country:"))
    dp.callback_query.register(cancel_country_selection_handler, F.data == "cancel_country_selection")
    dp.callback_query.register(country_page_handler, CountrySettings.selecting_country, F.data.startswith("country_page:"))




пешно сохранены.
Oct 12 20:30:05 vps1707806210 python[27938]: 2024-10-12 20:30:05,782 - bot.data - INFO - API ключ для пользователя 389669884 сохранён.
Oct 12 20:30:05 vps1707806210 python[27938]: 2024-10-12 20:30:05,943 - bot.handlers.settings - ERROR - Ошибка при проверке API ключа: Telegram server says - Bad Request: can't parse entities: Character '.' is reserved and must be escaped with the preceding '\'
Oct 12 20:30:06 vps1707806210 python[27938]: 2024-10-12 20:30:06,165 - aiogram.event - INFO - Update id=480218144 is handled. Duration 1220 ms by bot id=7625597085