ВЫШЛИ ПОЛНЫЙ КОД. Я хочу чтобы ты в этом коде реализовал пополнение баланса через Cryptomus.
Название проекта
CoGrid
URL-адрес проекта
https://t.me/cogrid_rentbot
ID мерчанта
9399ac43-6c1e-4c46-9e3a-4357833982df
Платежный API ключ
WnDMMoQyVMhd7POx1yuGlMu6g51PvDM5m7knE4KZ3P0LXv5hiU3ltemscPxcUepoIp02lowM4iSkGVCNTnY1mt6hhdC3U1s0W4YK9da8troDHd6B2ugX4UjNpzacAIVt
```
import json
import os
import random
import threading
import paramiko
import telebot
import string
import textwrap
import logging
import time
import traceback
from datetime import datetime, timedelta
from dotenv import load_dotenv
from collections import defaultdict

# ---------------------- Загрузка Переменных Окружения ----------------------
load_dotenv()

# ---------------------- Настройка Логирования ----------------------
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    handlers=[logging.FileHandler("bot.log"), logging.StreamHandler()],
)

# ---------------------- Конфигурация и Данные Настроек ----------------------
TOKEN = os.getenv("BOT_TOKEN")
if not TOKEN:
    logging.error(
        "Bot token is not set. Please set BOT_TOKEN in your environment variables."
    )
    raise ValueError(
        "Bot token is not set. Please set BOT_TOKEN in your environment variables."
    )



bot = telebot.TeleBot(TOKEN)

# Загрузка конфигурации из config.json
try:
    with open("config.json", "r", encoding="utf-8") as file:
        config = json.load(file)
        logging.info("Configuration loaded successfully.")
except FileNotFoundError:
    logging.error("config.json file not found.")
    raise
except json.JSONDecodeError as e:
    logging.error(f"JSON decoding error: {e}")
    raise

plan_names = list(config["plans"].keys())

# Файл базы данных
db_file = "db.json"

if not os.path.exists(db_file):
    with open(db_file, "w") as file:
        json.dump(
            {"users": [], "server_counter": 0, "support_tickets": []}, file, indent=4
        )
        logging.info("Created new db.json file.")

with open(db_file, "r") as file:
    users_db = json.load(file)

# ---------------------- Структура для временного хранения данных о создаваемых серверах ----------------------
pending_server_creations = {}


# ---------------------- Функции Утилиты ----------------------
def load_users():
    global users_db
    try:
        with open(db_file, "r") as file:
            users_db = json.load(file)
            logging.info("User database loaded.")
    except FileNotFoundError:
        save_users()
        logging.info("User database not found. Created new db.json.")


def add_user(user_id, username):
    if user_id not in [user["id"] for user in users_db["users"]]:
        users_db["users"].append(
            {
                "id": user_id,
                "username": username,
                "is_admin": False,
                "servers": [],
                "balance": 0,
            }
        )
        save_users()
        logging.info(f"Added new user: {user_id} ({username})")
        bot.send_message(
            user_id,
            "👋 Добро пожаловать в наш сервис по аренде серверов! Используйте меню ниже, чтобы начать.",
        )
        send_main_keyboard(user_id)


def set_admin(user_id, status=True):
    for user in users_db["users"]:
        if user["id"] == user_id:
            user["is_admin"] = status
            save_users()
            logging.info(f"Set admin status for user {user_id} to {status}")
            return True
    logging.warning(f"Attempted to set admin for non-existent user {user_id}")
    return False


def is_admin(user_id):
    admin_status = any(
        user["id"] == user_id and user.get("is_admin", False)
        for user in users_db["users"]
    )
    logging.debug(f"User {user_id} admin status: {admin_status}")
    return admin_status


def get_unique_port():
    while True:
        port = random.randint(4000, 10000)
        existing_ports = config.get("rdp_ports", [])
        if port not in existing_ports:
            config["rdp_ports"].append(port)
            save_config()
            logging.debug(f"Assigned unique port: {port}")
            return port


def generate_random_password(length=12):
    characters = string.ascii_uppercase + "123456789"
    if length < 2:
        raise ValueError(
            "Password length must be at least 2 to include one letter and one digit."
        )
    password = [random.choice(string.ascii_uppercase), random.choice("123456789")]
    password += random.choices(characters, k=length - 2)
    random.shuffle(password)
    generated_password = "".join(password)
    logging.debug(f"Generated password: {generated_password}")
    return generated_password


def create_docker_compose(plan, server_id, version, rdp_port, vnc_port, password):
    plan_config = config["plans"][plan]

    docker_compose_content = textwrap.dedent(
        f"""
    version: '3'
    services:
      windows:
        image: dockurr/windows
        container_name: windows{server_id}
        environment:
          VERSION: "{version}"
          DISK_SIZE: "{plan_config['DISK']}"
          RAM_SIZE: "{plan_config['RAM']}"
          CPU_CORES: "{plan_config['CPU']}"
          USERNAME: "admin"
          PASSWORD: "{password}"
          KEYBOARD: "ru-RU"
        devices:
          - /dev/kvm
        cap_add:
          - NET_ADMIN
        ports:
          - "{vnc_port}:8006/tcp"
          - "{rdp_port}:3389/tcp"
          - "{rdp_port}:3389/udp"
        stop_grace_period: 2m
        restart: unless-stopped
    """
    )

    logging.debug(
        f"Generated docker-compose.yml for server {server_id}:\n{docker_compose_content}"
    )
    return docker_compose_content


def save_docker_compose(ssh_client, docker_compose_content, server_id):
    try:
        sftp = ssh_client.open_sftp()
        folder_path = f"/root/windows{server_id}"
        try:
            sftp.mkdir(folder_path)
            logging.info(f"Created directory {folder_path} on server.")
        except IOError as e:
            if "Failure" in str(e):
                logging.warning(f"Directory {folder_path} already exists.")
            else:
                logging.error(f"Failed to create directory {folder_path}: {e}")
                raise
        with sftp.open(f"{folder_path}/docker-compose.yml", "w") as file:
            file.write(docker_compose_content)
        sftp.close()
        logging.info(f"docker-compose.yml saved successfully for server {server_id}")
    except Exception as e:
        logging.error(f"Error saving docker-compose.yml: {e}")
        raise


def connect_via_ssh(server):
    ssh_client = paramiko.SSHClient()
    ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    try:
        ssh_client.connect(
            server["host"],
            username=server["username"],
            password=server["password"],
            timeout=10,
        )
        logging.info(f"SSH connection established to {server['host']}")
    except Exception as e:
        logging.error(f"SSH connection failed to {server['host']}: {e}")
        return None
    return ssh_client


def execute_docker_command(server_id, command, node_name=None):
    # Retrieve node based on node_name
    if node_name is None:
        logging.error("Node name is required for executing docker command.")
        return False, "Node name not specified."
    node = next((n for n in config["nodes"] if n["name"] == node_name), None)
    if not node:
        logging.error(f"Node '{node_name}' not found in configuration.")
        return False, "Node not found."

    ssh_client = connect_via_ssh(node)
    if ssh_client:
        try:
            full_command = f"docker {command} windows{server_id}"
            logging.info(f"Executing command: {full_command}")
            stdin, stdout, stderr = ssh_client.exec_command(full_command)
            exit_status = stdout.channel.recv_exit_status()
            output = stdout.read().decode().strip()
            error = stderr.read().decode().strip()
            if exit_status != 0:
                logging.error(f"Error executing '{full_command}': {error}")
                return False, error
            else:
                logging.info(
                    f"Command '{full_command}' executed successfully: {output}"
                )
                return True, output
        except Exception as e:
            logging.error(
                f"Error executing docker command '{command}' on server {server_id}: {e}"
            )
            return False, str(e)
        finally:
            ssh_client.close()
            logging.info(f"SSH connection closed for server {node['host']}")
    else:
        logging.error(
            f"Cannot execute docker command '{command}' on server {node['host']} because SSH connection failed."
        )
        return False, "SSH connection failed."


def validate_integer(value):
    try:
        return int(value)
    except ValueError:
        return None


def validate_float(value):
    try:
        return float(value)
    except ValueError:
        return None


def validate_docker_compose(ssh_client, server_id):
    try:
        command = (
            f"docker-compose -f /root/windows{server_id}/docker-compose.yml config"
        )
        logging.info(f"Validating docker-compose.yml for server {server_id}")
        stdin, stdout, stderr = ssh_client.exec_command(command)
        exit_status = stdout.channel.recv_exit_status()
        if exit_status != 0:
            error = stderr.read().decode().strip()
            logging.error(f"Validation failed for server {server_id}: {error}")
            return False
        logging.info(
            f"Docker Compose file validated successfully for server {server_id}"
        )
        return True
    except Exception as e:
        logging.error(
            f"Error validating docker-compose.yml for server {server_id}: {e}"
        )
        return False


def save_config():
    try:
        with open("config.json", "w") as file:
            json.dump(config, file, indent=4)
            logging.info("Configuration saved.")
    except Exception as e:
        logging.error(f"Failed to save configuration: {e}")


def create_server_id():
    users_db["server_counter"] += 1
    save_users()
    return users_db["server_counter"]


def parse_size(size_str):
    if size_str.endswith('G'):
        return float(size_str[:-1])
    elif size_str.endswith('GB'):
        return float(size_str[:-2])
    elif size_str.endswith('M'):
        return float(size_str[:-1]) / 1024
    elif size_str.endswith('MB'):
        return float(size_str[:-2]) / 1024
    else:
        # Assume GB
        return float(size_str)


def calculate_node_available_resources(node):
    total_CPU = int(node["CPU"])
    total_RAM = parse_size(node["RAM"])
    total_DISK = parse_size(node["DISK"])

    used_CPU = 0
    used_RAM = 0.0
    used_DISK = 0.0

    for user in users_db["users"]:
        for server in user["servers"]:
            if server.get("node_name") == node["name"]:
                plan = server["plan"]
                plan_config = config["plans"][plan]
                used_CPU += int(plan_config["CPU"])
                used_RAM += parse_size(plan_config["RAM"])
                used_DISK += parse_size(plan_config["DISK"])

    available_CPU = total_CPU - used_CPU
    available_RAM = total_RAM - used_RAM
    available_DISK = total_DISK - used_DISK

    return {
        "CPU": available_CPU,
        "RAM": available_RAM,
        "DISK": available_DISK
    }


# ---------------------- Основная проверка биллинга ----------------------
def monthly_billing_check():
    logging.info("Checking for monthly billing...")
    for user in users_db["users"]:
        for server in user["servers"]:
            current_time = datetime.now()
            if server.get(
                "next_payment_due"
            ) and current_time >= datetime.fromisoformat(server["next_payment_due"]):
                handle_billing_cycle(user, server)


def handle_billing_cycle(user, server):
    plan = server["plan"]
    plan_price = config["plans"][plan]["price"]

    if user["balance"] >= plan_price:
        # Списание средств и обновление следующей даты оплаты
        user["balance"] -= plan_price
        server["next_payment_due"] = (datetime.now() + timedelta(days=30)).isoformat()
        save_users()
        logging.info(
            f"Successfully billed user {user['id']} for server {server['name']}. Next payment due on {server['next_payment_due']}."
        )
    else:
        if server["status"] == "Заморожен":
            return
        else:
            # Заморозка сервера, если недостаточно средств
            server["status"] = "Заморожен"
            server["freeze_reason"] = "Недостаточно средств."
            server["freeze_date"] = datetime.now().isoformat()
            save_users()
            bot.send_message(
                user["id"],
                f"❄️ Ваш сервер '{server['name']}' заморожен из-за отсутствия достаточного баланса. Пополните баланс в течении 5-ти дней.",
            )
            logging.info(
                f"Server '{server['name']}' for user {user['id']} frozen due to insufficient funds."
            )



# ---------------------- Проверка замороженных серверов ----------------------
def check_frozen_servers():
    logging.info("Checking for frozen servers...")
    for user in users_db["users"]:
        for server in user["servers"]:
            if server["status"] == "Заморожен":
                freeze_date = datetime.fromisoformat(server["freeze_date"])
                if datetime.now() > freeze_date + timedelta(days=5):
                    # Удаляем сервер, так как он оставался замороженным более трех дней
                    logging.info(
                        f"Deleting server '{server['name']}' for user {user['id']} after 5 days of being frozen."
                    )
                    execute_docker_command(server["id"], "kill", server["node_name"])
                    execute_docker_command(server["id"], "rm", server["node_name"])
                    remove_server_files(server["id"], server["node_name"])
                    user["servers"].remove(server)
                    save_users()
                    bot.send_message(
                        user["id"],
                        f"🗑️ Ваш сервер '{server['name']}' удален после более чем 5 дней заморозки.",
                    )
                    logging.info(
                        f"Server '{server['name']}' for user {user['id']} has been deleted after remaining frozen for too long."
                    )


# ---------------------- Ограничение по Частоте Запросов ----------------------
user_command_times = defaultdict(list)
RATE_LIMIT = 10  # Количество разрешенных запросов
TIME_WINDOW = 60  # Временное окно в секундах


def is_rate_limited(user_id):
    current_time = time.time()
    user_command_times[user_id] = [
        cmd_time
        for cmd_time in user_command_times[user_id]
        if current_time - cmd_time < TIME_WINDOW
    ]
    if len(user_command_times[user_id]) >= RATE_LIMIT:
        return True
    user_command_times[user_id].append(current_time)
    return False


def rate_limit_decorator(handler):
    def wrapper(message, *args, **kwargs):
        if is_rate_limited(message.chat.id):
            bot.send_message(
                message.chat.id,
                "⚠️ Вы совершаете слишком много запросов. Пожалуйста, подождите и попробуйте снова позже.",
            )
            logging.warning(f"User {message.chat.id} is rate limited.")
            return
        return handler(message, *args, **kwargs)

    return wrapper


# ---------------------- Функции Клавиатур ----------------------
def send_main_keyboard(chat_id):
    markup = telebot.types.ReplyKeyboardMarkup(resize_keyboard=True, row_width=2)
    markup.add(
        telebot.types.KeyboardButton("🛒 Товары"),
        telebot.types.KeyboardButton("📂 Мои сервера"),
    )
    markup.add(
        telebot.types.KeyboardButton("👤 Профиль"),
        telebot.types.KeyboardButton("📞 Поддержка"),
    )
    bot.send_message(
        chat_id,
        "🏠 *Главное меню*: Пожалуйста, выберите действие из меню ниже.",
        reply_markup=markup,
        parse_mode="Markdown",
    )


# ---------------------- Обработчики Команд Бота ----------------------
@bot.message_handler(commands=["start"])
def start_command(message):
    add_user(message.chat.id, message.from_user.username)
    logging.info(f"User {message.chat.id} started the bot.")
    send_main_keyboard(message.chat.id)


@bot.message_handler(commands=["help"])
def help_command(message):
    help_text = (
        "🛠️ *Доступные команды:*\n\n"
        "• /start - Перезапустить бота\n"
        "• /shop - Открыть магазин и приобрести сервер\n"
        "• /menu - Управление вашими серверами\n"
        "• /profile - Просмотр вашего профиля\n"
        "• /support - Связаться со службой поддержки\n"
        "• /admin - Админ-панель (доступна только администраторам)"
    )
    bot.send_message(message.chat.id, help_text, parse_mode="Markdown")
    send_main_keyboard(message.chat.id)
    logging.info(f"Sent help to user {message.chat.id}.")


@bot.message_handler(commands=["profile"])
def profile_command(message):
    user = next(
        (user for user in users_db["users"] if user["id"] == message.chat.id), None
    )
    if user:
        server_count = len(user["servers"])
        if server_count > 0:
            servers = "\n".join(
                [
                    f"• `{server['name']}` (Статус: {server['status']})"
                    for server in user["servers"]
                ]
            )
        else:
            servers = "*У вас нет активных серверов.*"

        profile_text = (
            f"👤 *Ваш профиль*\n"
            f"——————————————\n"
            f"🆔 *ID пользователя:* `{user['id']}`\n"
            f"💰 *Баланс:* `${user['balance']:.2f}`\n"
            f"💻 *Кол-во серверов:* `{server_count}`\n\n"
            f"{servers}"
        )

        # Добавляем кнопку "Передать баланс"
        markup = telebot.types.InlineKeyboardMarkup(row_width=1)
        markup.add(
            telebot.types.InlineKeyboardButton(
                text="💸 Передать баланс", callback_data="transfer_balance"
            ),
        )

        bot.send_message(
            message.chat.id, profile_text, reply_markup=markup, parse_mode="Markdown"
        )
        logging.info(f"Displayed profile for user {message.chat.id}.")
    else:
        bot.send_message(message.chat.id, "🚫 Пользователь не найден.")
        logging.warning(f"Profile check for non-existent user {message.chat.id}.")


@bot.callback_query_handler(func=lambda call: call.data == "transfer_balance")
def start_transfer_balance(call):
    bot.send_message(
        call.message.chat.id,
        "💸 Введите через пробел @username и сумму, которую хотите передать (например, @friend 10):",
    )
    bot.register_next_step_handler(call.message, handle_transfer_request)
    bot.answer_callback_query(call.id)


def handle_transfer_request(message):
    try:
        username, amount_str = message.text.split()
        if not username.startswith("@"):
            raise ValueError("Invalid username format")

        amount = float(amount_str)

        # Проверка, что сумма положительная
        if amount <= 0:
            bot.send_message(
                message.chat.id,
                "⚠️ Сумма должна быть положительным числом. Пожалуйста, введите корректную сумму.",
            )
            logging.warning(
                f"User {message.chat.id} попытался передать некорректную сумму: {amount}"
            )
            return

        giver = next(
            (user for user in users_db["users"] if user["id"] == message.chat.id), None
        )

        if giver["balance"] < amount:
            bot.send_message(
                message.chat.id, "🚫 У вас недостаточно средств для этой операции."
            )
            return

        receiver = next(
            (user for user in users_db["users"] if user["username"] == username[1:]),
            None,
        )

        if not receiver:
            bot.send_message(message.chat.id, "🚫 Пользователь не найден.")
            return

        if giver["id"] == receiver["id"]:
            bot.send_message(
                message.chat.id, "🚫 Вы не можете передать средства самому себе."
            )
            return

        giver["balance"] -= amount
        receiver["balance"] += amount
        save_users()

        bot.send_message(
            message.chat.id,
            f"✅ Вы передали `${amount}` пользователю @{receiver['username']}.",
        )
        bot.send_message(
            receiver["id"],
            f"💸 Вам передали `${amount}` от пользователя @{giver['username']}.",
        )

        logging.info(
            f"User {giver['id']} transferred ${amount} to user {receiver['id']}."
        )
    except ValueError as e:
        bot.send_message(
            message.chat.id,
            "⚠️ Формат ввода некорректен. Используйте: @username сумма",
        )
        logging.error(f"ValueError in transfer: {e}")
    except Exception as e:
        bot.send_message(
            message.chat.id, "⚠️ Произошла ошибка. Пожалуйста, попробуйте еще раз."
        )
        logging.error(f"Error in handle_transfer_request: {e}")


@bot.message_handler(commands=["balance"])
def balance_redirect(message):
    bot.send_message(
        message.chat.id,
        "⚠️ Эта команда была заменена на /profile. Используйте /profile для просмотра вашего баланса.",
    )
    logging.info(
        f"User {message.chat.id} attempted to use deprecated /balance command."
    )


@bot.message_handler(commands=["shop"])
def shop_command(message):
    user = next(
        (user for user in users_db["users"] if user["id"] == message.chat.id), None
    )
    if not user:
        add_user(message.chat.id, message.from_user.username)
        user = next(
            (user for user in users_db["users"] if user["id"] == message.chat.id), None
        )

    markup = telebot.types.InlineKeyboardMarkup(row_width=1)
    for plan in plan_names:
        markup.add(
            telebot.types.InlineKeyboardButton(
                text=f"{plan.capitalize()} 🛡️", callback_data=f"plan:{plan}"
            )
        )
    bot.send_message(
        message.chat.id,
        "🛒 *Выберите тарифный план:*",
        reply_markup=markup,
        parse_mode="Markdown",
    )
    logging.info(f"Displayed shop to user {message.chat.id}.")


@bot.message_handler(commands=["admin"])
def admin_command(message):
    if is_admin(message.chat.id):
        admin_text = (
            "🔧 *Админ-панель:*\n\n"
            "👥 `/setadmin [ID]` - Назначить пользователя админом\n"
            "🚫 `/removeadmin [ID]` - Удалить пользователя из админов\n"
            "📋 `/listusers` - Показать всех пользователей\n"
            "💸 `/givebalance [ID] [AMOUNT]` - Выдать баланс пользователю\n"
            "🧸 `/setbalance [ID] [AMOUNT]` - Установить конкретный баланс пользователю\n"
            "📑 `/tickets` - Панель тикетов поддержки\n"
            "🖥️ `/viewservers` - Просмотреть все сервера пользователей\n"
            "❄️ `/freeze [SERVER_NAME] [REASON]` - Заморозить сервер\n"
            "🔓 `/unfreeze [SERVER_NAME]` - Разморозить сервер\n"
        )
        bot.send_message(message.chat.id, admin_text, parse_mode="Markdown")
        logging.info(f"Displayed admin panel to admin user {message.chat.id}.")
    else:
        bot.send_message(message.chat.id, "🚫 У вас нет доступа к админ-панели")
        logging.warning(
            f"User {message.chat.id} attempted to access admin panel without permissions."
        )


@bot.message_handler(commands=["setadmin"])
def set_admin_command(message):
    if is_admin(message.chat.id):
        try:
            _, user_id = message.text.split()
            user_id = int(user_id)
        except (ValueError, IndexError):
            bot.send_message(
                message.chat.id, "⚠️ Используйте команду в формате /setadmin [ID]"
            )
            return

        if set_admin(user_id):
            bot.send_message(
                message.chat.id, f"✅ Пользователь {user_id} назначен администратором."
            )
        else:
            bot.send_message(
                message.chat.id,
                f"🚫 Не удалось назначить пользователя {user_id} администратором.",
            )
    else:
        bot.send_message(message.chat.id, "🚫 У вас нет доступа к этой команде.")


@bot.message_handler(commands=["removeadmin"])
def remove_admin_command(message):
    if is_admin(message.chat.id):
        try:
            _, user_id = message.text.split()
            user_id = int(user_id)
        except (ValueError, IndexError):
            bot.send_message(
                message.chat.id, "⚠️ Используйте команду в формате /removeadmin [ID]"
            )
            return

        if set_admin(user_id, status=False):
            bot.send_message(
                message.chat.id, f"✅ Пользователь {user_id} удален из администраторов."
            )
        else:
            bot.send_message(
                message.chat.id,
                f"🚫 Не удалось удалить пользователя {user_id} из администраторов.",
            )
    else:
        bot.send_message(message.chat.id, "🚫 У вас нет доступа к этой команде.")


@bot.message_handler(commands=["listusers"])
def list_users_command(message):
    if is_admin(message.chat.id):
        user_list = ["👥 *Список пользователей:*"]
        for user in users_db["users"]:
            user_info = f"• ID: `{user['id']}`, Имя: @{user.get('username', 'нет')}, Баланс: `${user['balance']}`"
            user_list.append(user_info)
        bot.send_message(
            message.chat.id, "\n".join(user_list), parse_mode="Markdown"
        )
    else:
        bot.send_message(message.chat.id, "🚫 У вас нет доступа к этой команде.")


def save_users():
    try:
        with open(db_file, "w") as file:
            json.dump(users_db, file, indent=4)
            logging.info("User database saved.")
    except Exception as e:
        logging.error(f"Failed to save user database: {e}")


@bot.message_handler(commands=["setbalance"])
def set_balance_command(message):
    if not is_admin(message.chat.id):
        bot.send_message(message.chat.id, "🚫 У вас нет доступа к этой команде.")
        logging.warning(
            f"User {message.chat.id} attempted to use setbalance command without permissions."
        )
        return

    try:
        _, user_id, amount_str = message.text.split()
        user_id = int(user_id)
        amount = float(amount_str)
    except (ValueError, IndexError):
        bot.send_message(
            message.chat.id,
            "⚠️ Используйте команду в формате /setbalance [ID] [AMOUNT]",
        )
        return

    user = next((user for user in users_db["users"] if user["id"] == user_id), None)

    if user:
        user["balance"] = amount
        save_users()
        bot.send_message(
            message.chat.id,
            f"✅ Баланс пользователя {user_id} установлен на `${amount}`.",
        )
        logging.info(
            f"Admin {message.chat.id} set balance of user {user_id} to ${amount}."
        )
    else:
        bot.send_message(message.chat.id, f"🚫 Пользователь {user_id} не найден.")
        logging.warning(
            f"Admin {message.chat.id} attempted to set balance of non-existent user {user_id}."
        )


@bot.message_handler(commands=["givebalance"])
def give_balance_command(message):
    if is_admin(message.chat.id):
        try:
            _, user_id, amount_str = message.text.split()
            user_id = int(user_id)
            amount = float(amount_str)
        except (ValueError, IndexError):
            bot.send_message(
                message.chat.id,
                "⚠️ Используйте команду в формате /givebalance [ID] [AMOUNT]",
            )
            return

        user = next((user for user in users_db["users"] if user["id"] == user_id), None)
        if user:
            user["balance"] += amount
            save_users()
            bot.send_message(
                message.chat.id,
                f"✅ Баланс пользователя {user_id} увеличен на `${amount}`.",
            )
        else:
            bot.send_message(message.chat.id, f"🚫 Пользователь {user_id} не найден.")
    else:
        bot.send_message(message.chat.id, "🚫 У вас нет доступа к этой команде.")


@bot.message_handler(commands=["tickets"])
def view_tickets_command(message):
    if not is_admin(message.chat.id):
        bot.send_message(message.chat.id, "🚫 У вас нет доступа к этой команде.")
        return

    tickets_markup = telebot.types.InlineKeyboardMarkup(row_width=1)
    for ticket in users_db["support_tickets"]:
        tickets_markup.add(
            telebot.types.InlineKeyboardButton(
                text=f"Тикет {ticket['id']} от @{ticket['username']}",
                callback_data=f"admin_ticket_view:{ticket['id']}",
            )
        )
    bot.send_message(
        message.chat.id, "🔎 Все тикеты поддержки:", reply_markup=tickets_markup
    )


@bot.callback_query_handler(
    func=lambda call: call.data.startswith("admin_ticket_view:")
)
def admin_view_ticket(call):
    ticket_id = validate_integer(call.data.split(":")[1])
    admin_id = call.message.chat.id

    ticket = next(
        (t for t in users_db["support_tickets"] if t["id"] == ticket_id), None
    )
    if not ticket:
        bot.send_message(admin_id, "⚠️ Тикет не найден.")
    else:
        markup = telebot.types.InlineKeyboardMarkup(row_width=1)
        markup.add(
            telebot.types.InlineKeyboardButton(
                text="Ответить", callback_data=f"reply_admin_ticket:{ticket_id}"
            ),
            telebot.types.InlineKeyboardButton(
                text="Удалить", callback_data=f"delete_ticket:{ticket_id}"
            ),
        )
        bot.send_message(
            admin_id,
            f"🔹 Тикет ID: {ticket['id']}\n"
            f"👤 Пользователь: @{ticket['username']}\n"
            f"📅 Дата: {ticket['date']}\n"
            f"💬 Сообщение: {ticket['message']}\n\n"
            f"Ответы:\n"
            + "\n".join(
                [
                    f'🗨️ {reply["from"]}: {reply["text"]} ({reply["date"]})'
                    for reply in ticket["replies"]
                ]
            ),
            reply_markup=markup,
        )

    bot.answer_callback_query(call.id)


@bot.callback_query_handler(
    func=lambda call: call.data.startswith("reply_admin_ticket:")
)
def reply_admin_ticket(call):
    ticket_id = validate_integer(call.data.split(":")[1])
    admin_id = call.message.chat.id

    ticket = next(
        (t for t in users_db["support_tickets"] if t["id"] == ticket_id), None
    )

    if ticket:
        bot.send_message(admin_id, f"✍️ Введите ваш ответ для тикета ID {ticket_id}:")
        bot.register_next_step_handler(
            call.message, handle_admin_reply_to_ticket, ticket_id
        )
    else:
        bot.send_message(admin_id, "⚠️ Тикет не найден.")

    bot.answer_callback_query(call.id)


def handle_admin_reply_to_ticket(message, ticket_id):
    ticket = next(
        (t for t in users_db["support_tickets"] if t["id"] == ticket_id), None
    )

    if ticket:
        reply = {
            "from": "Администратор",
            "text": message.text,
            "date": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
        }
        ticket["replies"].append(reply)
        save_users()

        bot.send_message(
            ticket["user_id"],
            f"💬 Новый ответ на ваш тикет ID {ticket_id} от администратора.",
        )
        bot.send_message(
            ticket["user_id"], f"🗨️ {reply['from']}: {reply['text']} ({reply['date']})"
        )

        bot.send_message(
            message.chat.id, f"✅ Ваш ответ добавлен в тикет ID {ticket_id}."
        )
        logging.info(f"Admin {message.chat.id} replied to ticket {ticket_id}.")
    else:
        bot.send_message(message.chat.id, "⚠️ Тикет не найден.")


@bot.callback_query_handler(func=lambda call: call.data.startswith("delete_ticket:"))
def admin_delete_ticket(call):
    ticket_id = validate_integer(call.data.split(":")[1])
    admin_id = call.message.chat.id

    ticket = next(
        (t for t in users_db["support_tickets"] if t["id"] == ticket_id), None
    )
    if ticket:
        users_db["support_tickets"].remove(ticket)
        save_users()
        bot.send_message(admin_id, f"🗑️ Тикет {ticket_id} удален.")
        logging.info(f"Admin {admin_id} deleted ticket {ticket_id}.")
    else:
        bot.send_message(admin_id, "⚠️ Тикет не найден.")

    bot.answer_callback_query(call.id)


@bot.message_handler(commands=["viewservers"])
def view_servers_command(message):
    if is_admin(message.chat.id):
        servers = []
        for user in users_db["users"]:
            if user["servers"]:
                for server in user["servers"]:
                    servers.append((user["username"], server))

        if servers:
            markup = telebot.types.InlineKeyboardMarkup(row_width=1)
            for username, server in servers:
                markup.add(
                    telebot.types.InlineKeyboardButton(
                        text=f"{username}'s {server['name']} 🖥️",
                        callback_data=f"admin_server:{server['id']}",
                    )
                )
            bot.send_message(
                message.chat.id,
                "🖥️ *Список всех серверов пользователей:*",
                reply_markup=markup,
                parse_mode="Markdown",
            )
            logging.info(f"Admin {message.chat.id} requested to view servers.")
        else:
            bot.send_message(message.chat.id, "Нет активных серверов у пользователей.")
            logging.info(
                f"Admin {message.chat.id} requested to view servers but none were found."
            )
    else:
        bot.send_message(message.chat.id, "🚫 У вас нет доступа к этой команде.")
        logging.warning(
            f"User {message.chat.id} attempted to access server view without permissions."
        )


@bot.message_handler(commands=["freeze"])
def freeze_command(message):
    if not is_admin(message.chat.id):
        bot.send_message(message.chat.id, "🚫 У вас нет доступа к этой команде.")
        logging.warning(
            f"User {message.chat.id} attempted to use freeze command without permissions."
        )
        return

    try:
        _, server_name, *reason_parts = message.text.split()
        reason = " ".join(reason_parts)  # Сбор всех частей причины в одно сообщение
    except ValueError:
        bot.send_message(
            message.chat.id,
            "⚠️ Используйте команду в формате /freeze [SERVER_NAME] [REASON]",
        )
        return

    # Поиск сервера и выполнение заморозки
    for user in users_db["users"]:
        server = next((s for s in user["servers"] if s["name"] == server_name), None)
        if server:
            # Заморозка сервера
            server["status"] = "Заморожен"
            server["freeze_reason"] = reason  # Сохранение причины
            save_users()
            bot.send_message(
                user["id"],
                f"❄️ Ваш сервер '{server['name']}' был заморожен по причине: {reason}.",
            )
            bot.send_message(
                message.chat.id,
                f"❄️ Сервер '{server['name']}' успешно заморожен по причине: {reason}.",
            )
            logging.info(
                f"Server '{server['name']}' (ID: {server['id']}) frozen by admin {message.chat.id} with reason: {reason}."
            )
            return

    bot.send_message(message.chat.id, f"⚠️ Сервер с именем '{server_name}' не найден.")
    logging.warning(
        f"Admin {message.chat.id} attempted to freeze non-existent server '{server_name}'."
    )


@bot.message_handler(commands=["unfreeze"])
def unfreeze_command(message):
    if not is_admin(message.chat.id):
        bot.send_message(message.chat.id, "🚫 У вас нет доступа к этой команде.")
        logging.warning(
            f"User {message.chat.id} attempted to use unfreeze command without permissions."
        )
        return

    try:
        _, server_name = message.text.split(maxsplit=1)
    except ValueError:
        bot.send_message(
            message.chat.id, "⚠️ Используйте команду в формате /unfreeze [SERVER_NAME]"
        )
        return

    for user in users_db["users"]:
        server = next((s for s in user["servers"] if s["name"] == server_name), None)
        if server:
            if server["status"] == "Заморожен":
                # Снимаем заморозку только в случае оплаты
                user_balance = user["balance"]
                plan_price = config["plans"][server["plan"]]["price"]
                if user_balance >= plan_price:
                    user["balance"] -= plan_price
                    server["status"] = "Запущен"
                    server["next_payment_due"] = (
                        datetime.now() + timedelta(days=30)
                    ).isoformat()
                    del server["freeze_reason"]
                    save_users()
                    bot.send_message(
                        user["id"],
                        f"🔓 Ваш сервер '{server['name']}' был разморожен и запущен.",
                    )
                    bot.send_message(
                        message.chat.id,
                        f"🔓 Сервер '{server['name']}' успешно разморожен и запущен.",
                    )
                    logging.info(
                        f"Server '{server['name']}' (ID: {server['id']}) unfrozen by admin {message.chat.id}."
                    )
                else:
                    bot.send_message(
                        message.chat.id,
                        f"⚠️ Недостаточно средств для разморозки сервера '{server['name']}'.",
                    )
            else:
                bot.send_message(
                    message.chat.id, "🚫 Сервер не находится в замороженном состоянии."
                )
                logging.info(
                    f"Attempted to unfreeze non-frozen server '{server['name']}' by admin {message.chat.id}."
                )
            return

    bot.send_message(message.chat.id, f"⚠️ Сервер с именем '{server_name}' не найден.")
    logging.warning(
        f"Admin {message.chat.id} attempted to unfreeze non-existent server '{server_name}'."
    )


# Обработчик текстовых команд из клавиатуры
@bot.message_handler(
    func=lambda message: message.text
    in ["📂 Мои сервера", "🛒 Товары", "📞 Поддержка", "👤 Профиль"]
)
def handle_text_commands(message):
    command = message.text
    if command == "📂 Мои сервера":
        menu_command(message)
    elif command == "🛒 Товары":
        shop_command(message)
    elif command == "📞 Поддержка":
        support_command(message)
    elif command == "👤 Профиль":
        profile_command(message)
    else:
        bot.send_message(
            message.chat.id,
            "❓ Неизвестная команда. Используйте кнопки ниже для взаимодействия с ботом.",
        )
        logging.warning(
            f"User {message.chat.id} sent an unknown command: {message.text}"
        )


@bot.message_handler(commands=["menu"])
def menu_command(message):
    user = next(
        (user for user in users_db["users"] if user["id"] == message.chat.id), None
    )
    if user:
        if user["servers"]:
            markup = telebot.types.InlineKeyboardMarkup(row_width=1)
            for server in user["servers"]:
                markup.add(
                    telebot.types.InlineKeyboardButton(
                        text=f"{server['name']} 🖥️",
                        callback_data=f"select_server:{server['id']}",
                    )
                )
            bot.send_message(
                message.chat.id,
                "📂 *Ваши серверы*: Выберите сервер для управления:",
                reply_markup=markup,
                parse_mode="Markdown",
            )
            logging.info(
                f"Displayed server list to user {message.chat.id} for selection."
            )
        else:
            bot.send_message(message.chat.id, "🚫 У вас нет серверов.")
            logging.info(f"User {message.chat.id} has no servers.")
    else:
        bot.send_message(message.chat.id, "🚫 У вас нет доступа.")
        logging.warning(
            f"Non-existent user {message.chat.id} attempted to access menu."
        )


# ---------------------- Обработчики Callback Query ----------------------
@bot.callback_query_handler(func=lambda call: call.data.startswith("plan:"))
def plan_callback(call):
    plan = call.data.split(":")[1]
    plan_info = config["plans"].get(plan)

    if not plan_info:
        bot.send_message(call.message.chat.id, "🚫 Некорректный план.")
        return

    # Путь к изображению тарифа
    image_path = f"tariffs/{plan}.jpg"

    # Проверяем, существует ли изображение для плана
    if not os.path.isfile(image_path):
        bot.send_message(call.message.chat.id, "🚫 Изображение тарифа не найдено.")
        logging.error(f"Image for plan '{plan}' not found at path '{image_path}'.")
        return

    # Показываем пользователю информацию о плане
    plan_details = (
        f"🛡️ *Тарифный план: {plan.capitalize()}*\n"
        f"——————————————\n"
        f"💾 *Диск:* `{plan_info['DISK']}`\n"
        f"⚙️ *ОЗУ:* `{plan_info['RAM']}`\n"
        f"⚡ *CPU:* `{plan_info['CPU']} ядер`\n"
        f"💵 *Цена:* `${plan_info['price']}`"
    )

    # Добавляем кнопки "Назад" и "Купить"
    markup = telebot.types.InlineKeyboardMarkup(row_width=2)
    markup.add(
        telebot.types.InlineKeyboardButton(text="⬅️ Назад", callback_data="shop"),
        telebot.types.InlineKeyboardButton(
            text="🛒 Купить", callback_data=f"confirm_purchase:{plan}"
        ),
    )

    # Отправляем изображение вместе с описанием тарифа
    with open(image_path, "rb") as photo:
        bot.send_photo(
            call.message.chat.id,
            photo,
            caption=plan_details,
            reply_markup=markup,
            parse_mode="Markdown",
        )

    logging.info(
        f"User {call.message.chat.id} checked details of plan '{plan}'. Image sent."
    )
    bot.answer_callback_query(call.id)


@bot.callback_query_handler(func=lambda call: call.data.startswith("confirm_purchase:"))
def confirm_purchase_callback(call):
    plan = call.data.split(":")[1]
    user = next(
        (user for user in users_db["users"] if user["id"] == call.message.chat.id), None
    )

    plan_price = config["plans"][plan]["price"]

    # Проверка, что цена плана положительная
    if plan_price <= 0:
        bot.send_message(
            call.message.chat.id,
            "⚠️ Некорректная цена выбранного тарифа. Пожалуйста, свяжитесь с поддержкой.",
        )
        logging.error(
            f"Plan '{plan}' has non-positive price: {plan_price}. Purchase aborted."
        )
        bot.answer_callback_query(call.id)
        return

    if user and user["balance"] >= plan_price:
        pending_server_creations[call.message.chat.id] = {"plan": plan}

        markup = telebot.types.InlineKeyboardMarkup(row_width=2)
        markup.add(
            telebot.types.InlineKeyboardButton(
                text="✅ Да", callback_data=f"autorenew:yes"
            ),
            telebot.types.InlineKeyboardButton(
                text="❌ Нет", callback_data=f"autorenew:no"
            ),
        )
        bot.send_message(
            call.message.chat.id, "🔄 Включить автопродление?", reply_markup=markup
        )
        logging.info(
            f"User {call.message.chat.id} selected plan '{plan}' to purchase. Prompting for auto-renew."
        )
    else:
        bot.send_message(
            call.message.chat.id,
            "🚫 У вас недостаточно средств для покупки этого тарифа.",
        )
    bot.answer_callback_query(call.id)


@bot.callback_query_handler(func=lambda call: call.data == "shop")
def return_to_shop(call):
    shop_command(call.message)
    bot.answer_callback_query(call.id)


@bot.callback_query_handler(func=lambda call: call.data.startswith("autorenew:"))
def autorenew_callback(call):
    response = call.data.split(":")[1]
    pending_server_creations[call.message.chat.id]["autorenew"] = response == "yes"
    bot.send_message(
        call.message.chat.id,
        (
            "ℹ️ Автопродление включено."
            if response == "yes"
            else "ℹ️ Автопродление отключено."
        ),
    )
    plan = pending_server_creations[call.message.chat.id]["plan"]

    versions = ["2022", "2025", "2019", "2016", "win10", "win11", "winxp"]
    version_markup = telebot.types.InlineKeyboardMarkup(row_width=1)
    for version in versions:
        version_display = (
            f"Windows Server {version}"
            if version.isdigit()
            else f"Windows {version[-2:].upper()}"
        )
        version_markup.add(
            telebot.types.InlineKeyboardButton(
                text=f"{version_display} 🖥️",
                callback_data=f"version:{version}:plan:{plan}",
            )
        )
    bot.send_message(
        call.message.chat.id,
        "📋 *Выберите версию Windows:*",
        reply_markup=version_markup,
        parse_mode="Markdown",
    )
    logging.info(
        f"User {call.message.chat.id} selected auto-renew option '{response}'. Prompting for OS version."
    )
    bot.answer_callback_query(call.id)


@bot.callback_query_handler(func=lambda call: call.data.startswith("select_server:"))
def select_server_callback(call):
    user_id = call.message.chat.id
    server_id = validate_integer(call.data.split(":")[1])

    if server_id is None:
        bot.send_message(user_id, "⚠️ Некорректный ID сервера.")
        logging.error(f"Invalid server ID in select_server callback: {call.data}")
        bot.answer_callback_query(call.id)
        return

    user = next((user for user in users_db["users"] if user["id"] == user_id), None)
    if not user:
        bot.send_message(user_id, "🚫 Пользователь не найден.")
        logging.error(f"User {user_id} not found in select_server_callback.")
        bot.answer_callback_query(call.id)
        return

    server = next((s for s in user["servers"] if s["id"] == server_id), None)
    if not server:
        bot.send_message(user_id, "🚫 Сервер не найден.")
        logging.error(
            f"Server ID {server_id} not found for user {user_id} in select_server_callback."
        )
        bot.answer_callback_query(call.id)
        return

    send_server_control_message(user_id, server, is_admin=False)
    bot.answer_callback_query(call.id)


@bot.callback_query_handler(func=lambda call: call.data.startswith("admin_server:"))
def admin_server_callback(call):
    server_id = validate_integer(call.data.split(":")[1])
    admin_id = call.message.chat.id

    if server_id is None:
        bot.send_message(admin_id, "⚠️ Некорректный ID сервера.")
        logging.error(f"Invalid server ID in admin_server callback: {call.data}")
        bot.answer_callback_query(call.id)
        return

    for user in users_db["users"]:
        server = next((s for s in user["servers"] if s["id"] == server_id), None)
        if server:
            send_server_control_message(admin_id, server, is_admin=True)
            bot.answer_callback_query(call.id)
            return

    bot.send_message(admin_id, "🚫 Сервер не найден.")
    logging.error(f"Server ID {server_id} not found.")
    bot.answer_callback_query(call.id)


@bot.callback_query_handler(func=lambda call: call.data.startswith("version:"))
def version_callback(call):
    data_parts = call.data.split(":")
    if len(data_parts) != 4:
        bot.send_message(
            call.message.chat.id, "⚠️ Некорректные данные. Попробуйте снова."
        )
        logging.error(
            f"Incorrect version callback data from user {call.message.chat.id}: {call.data}"
        )
        bot.answer_callback_query(call.id)
        return

    _, version, _, plan = data_parts

    supported_versions = [
        "2022",
        "2025",
        "2019",
        "win10",
        "win11",
        "2016",
        "winxp",
    ]

    if version not in supported_versions:
        bot.send_message(
            call.message.chat.id, "🚫 Выбранная версия Windows не поддерживается."
        )
        logging.warning(
            f"User {call.message.chat.id} selected unsupported version '{version}'."
        )
        bot.answer_callback_query(call.id)
        return

    pending_server_creations[call.message.chat.id]["version"] = version
    # Prompt for node selection here
    # Build InlineKeyboardMarkup with nodes
    node_markup = telebot.types.InlineKeyboardMarkup(row_width=1)
    for node in config["nodes"]:
        node_name = node["name"]
        node_description = node.get("description", "")
        node_markup.add(
            telebot.types.InlineKeyboardButton(
                text=f"{node_name} ({node_description})",
                callback_data=f"node:{node_name}",
            )
        )
    bot.send_message(
        call.message.chat.id,
        "📍 Пожалуйста, выберите ноду для размещения вашего сервера:",
        reply_markup=node_markup,
        parse_mode="Markdown",
    )
    logging.info(
        f"User {call.message.chat.id} selected version '{version}' for plan '{plan}'. Prompting for node selection."
    )
    bot.answer_callback_query(call.id)


@bot.callback_query_handler(func=lambda call: call.data.startswith("node:"))
def node_callback(call):
    node_name = call.data.split(":")[1]
    user_id = call.message.chat.id

    # Find the node in config
    node = next((n for n in config["nodes"] if n["name"] == node_name), None)
    if node:
        pending_server_creations[user_id]["node_name"] = node_name
        bot.send_message(
            user_id,
            "📝 Пожалуйста, введите имя для вашего сервера:",
            parse_mode="Markdown",
        )
        bot.register_next_step_handler(call.message, handle_server_name)
        logging.info(
            f"User {user_id} selected node '{node_name}'. Prompting for server name."
        )
    else:
        bot.send_message(user_id, "⚠️ Выбранная нода не найдена. Попробуйте снова.")
        logging.error(f"Node '{node_name}' not found in configuration.")
    bot.answer_callback_query(call.id)


@bot.callback_query_handler(
    func=lambda call: call.data.startswith("start:")
    or call.data.startswith("stop:")
    or call.data.startswith("restart:")
    or call.data.startswith("delete:")
    or call.data.startswith("reinstall:")
)
def server_control_callback(call):
    action, server_id = call.data.split(":")
    server_id = validate_integer(server_id)
    user_id = call.message.chat.id

    if server_id is None:
        bot.send_message(user_id, "⚠️ Некорректный ID сервера.")
        logging.error(f"Invalid server ID in callback: {call.data}")
        bot.answer_callback_query(call.id)
        return

    for user in users_db["users"]:
        server = next((s for s in user["servers"] if s["id"] == server_id), None)
        if server:
            # Restrict any server control if frozen
            if server["status"] == "Заморожен":
                bot.send_message(
                    user_id, "🚫 Сервер заморожен. Вы не можете управлять им."
                )
                logging.warning(
                    f"User {user_id} attempted to manage a frozen server '{server['name']}'."
                )
                bot.answer_callback_query(call.id)
                return

            node_name = server.get("node_name")
            if node_name is None:
                bot.send_message(
                    user_id,
                    "⚠️ Не удалось определить ноду сервера. Пожалуйста, свяжитесь с поддержкой.",
                )
                logging.error(
                    f"Server '{server['name']}' (ID: {server_id}) has no node_name specified."
                )
                bot.answer_callback_query(call.id)
                return

            if action == "reinstall":
                # Отправляем запрос на подтверждение переустановки
                confirm_markup = telebot.types.InlineKeyboardMarkup(row_width=2)
                confirm_markup.add(
                    telebot.types.InlineKeyboardButton(
                        text="✅ Подтвердить",
                        callback_data=f"confirm_reinstall:{server_id}",
                    ),
                    telebot.types.InlineKeyboardButton(
                        text="❌ Отмена", callback_data="cancel"
                    ),
                )
                bot.send_message(
                    user_id,
                    f"❗ Вы уверены, что хотите переустановить сервер ID {server_id}?",
                    reply_markup=confirm_markup,
                )
                return

            if action == "delete":
                confirm_markup = telebot.types.InlineKeyboardMarkup(row_width=2)
                confirm_markup.add(
                    telebot.types.InlineKeyboardButton(
                        text="✅ Подтвердить",
                        callback_data=f"confirm_delete:{server_id}",
                    ),
                    telebot.types.InlineKeyboardButton(
                        text="❌ Отмена", callback_data="cancel"
                    ),
                )
                bot.send_message(
                    user_id,
                    f"❗ Вы уверены, что хотите удалить сервер ID {server_id}?",
                    reply_markup=confirm_markup,
                )
                return

            success, response = execute_docker_command(server_id, action, node_name)
            if success:
                if action == "start":
                    server["status"] = "Запущен"
                elif action == "stop":
                    server["status"] = "Остановлен"
                save_users()

                status_text = server["status"]
                bot.send_message(
                    user_id,
                    f"✅ Сервер '{server['name']}' успешно {status_text.lower()}.",
                )
                logging.info(
                    f"User {user_id} {action}ed server '{server['name']}' (ID: {server_id})."
                )

            else:
                bot.send_message(
                    user_id,
                    f"⚠️ Не удалось выполнить действие '{action}' для сервера '{server['name']}'.\nОшибка: {response}",
                )
                logging.error(
                    f"Failed to {action} server '{server['name']}' (ID: {server_id}) for user {user_id}: {response}"
                )

    bot.answer_callback_query(call.id, f"✅ Действие '{action}' выполнено.")


@bot.callback_query_handler(
    func=lambda call: call.data.startswith("confirm_reinstall:")
)
def confirm_reinstall_callback(call):
    server_id = validate_integer(call.data.split(":")[1])
    user_id = call.message.chat.id

    if server_id is None:
        bot.send_message(user_id, "⚠️ Некорректный ID сервера.")
        logging.error(f"Invalid server ID in confirm_reinstall callback: {call.data}")
        bot.answer_callback_query(call.id)
        return

    restart_server_with_new_version(user_id, server_id)
    bot.answer_callback_query(call.id, "☑️ Переустановка сервера...")


def restart_server_with_new_version(user_id, server_id):
    version_markup = telebot.types.InlineKeyboardMarkup(row_width=1)
    versions = ["2022", "2025", "2019", "2016", "win10", "win11", "winxp"]

    for version in versions:
        version_display = (
            f"Windows Server {version}"
            if version.isdigit()
            else f"Windows {version[-2:].upper()}"
        )
        version_markup.add(
            telebot.types.InlineKeyboardButton(
                text=f"{version_display} 🖥️",
                callback_data=f"reinstall_version:{server_id}:{version}",
            )
        )

    bot.send_message(
        user_id,
        "📋 *Выберите новую версию Windows для переустановки:*",
        reply_markup=version_markup,
        parse_mode="Markdown",
    )


@bot.callback_query_handler(
    func=lambda call: call.data.startswith("reinstall_version:")
)
def handle_reinstall_version_selection(call):
    user_id = call.message.chat.id
    _, server_id_str, new_version = call.data.split(":")
    server_id = validate_integer(server_id_str)

    if server_id is None:
        bot.send_message(user_id, "⚠️ Некорректный ID сервера.")
        logging.error(f"Invalid server ID in reinstall version selection: {call.data}")
        bot.answer_callback_query(call.id)
        return

    supported_versions = [
        "2022",
        "2025",
        "2019",
        "2016",
        "win10",
        "win11",
        "winxp",
    ]

    if new_version not in supported_versions:
        bot.send_message(user_id, "🚫 Выбранная версия Windows не поддерживается.")
        logging.warning(f"User {user_id} selected unsupported version '{new_version}'.")
        bot.answer_callback_query(call.id)
        return

    # Найти сервер
    user = next((user for user in users_db["users"] if user["id"] == user_id), None)
    if not user:
        bot.send_message(user_id, "🚫 Пользователь не найден.")
        logging.error(f"User {user_id} not found in reinstall_version_selection.")
        bot.answer_callback_query(call.id)
        return

    server = next((s for s in user["servers"] if s["id"] == server_id), None)
    if not server:
        bot.send_message(user_id, "🚫 Сервер не найден.")
        logging.error(
            f"Server ID {server_id} not found for user {user_id} in reinstall_version_selection."
        )
        bot.answer_callback_query(call.id)
        return

    node_name = server.get("node_name")
    if node_name is None:
        bot.send_message(
            user_id,
            "⚠️ Не удалось определить ноду сервера. Пожалуйста, свяжитесь с поддержкой.",
        )
        logging.error(
            f"Server '{server['name']}' (ID: {server_id}) has no node_name specified."
        )
        bot.answer_callback_query(call.id)
        return
    if server["status"] != "Остановлен":
        # Останавливаем и удаляем контейнер
        success_kill, kill_response = execute_docker_command(server_id, "kill", node_name)
        if not success_kill:
            logging.error(
                f"Failed to kill container for server {server_id}. Response: {kill_response}"
            )
            bot.send_message(
                user_id,
                f"⚠️ Не удалось остановить сервер '{server_id}' перед переустановкой.",
            )
            return

    success_rm, rm_response = execute_docker_command(server_id, "rm", node_name)
    if not success_rm:
        logging.error(
            f"Failed to remove container for server {server_id}. Response: {rm_response}"
        )
        bot.send_message(user_id, f"⚠️ Не удалось удалить сервер '{server_id}'.")
        return

    # Модифицируем версию в docker-compose
    if not modify_docker_compose(user_id, server_id, new_version, node_name):
        logging.error(
            f"Failed to modify docker-compose.yml for server ID: {server_id}."
        )
        bot.send_message(
            user_id,
            "⚠️ Ошибка при модификации docker-compose.yml. Невозможно переустановить.",
        )
        return

    # Переустанавливаем сервер
    reinstall_user_server(user_id, server_id, node_name)
    bot.answer_callback_query(call.id)


def modify_docker_compose(user_id, server_id, new_version, node_name):
    node = next((n for n in config["nodes"] if n["name"] == node_name), None)
    if not node:
        logging.error(f"Node '{node_name}' not found in configuration.")
        return False

    ssh_client = connect_via_ssh(node)
    if not ssh_client:
        return False

    try:
        path = f"/root/windows{server_id}/docker-compose.yml"
        sftp = ssh_client.open_sftp()

        # Чтение текущего содержимого docker-compose.yml
        with sftp.open(path, "r") as file:
            content = file.read().decode()

        # Логика замены: находим строку с VERSION и заменяем значение
        lines = content.splitlines()
        for i, line in enumerate(lines):
            if "VERSION" in line:
                # Замена значения версии
                lines[i] = f'      VERSION: "{new_version}"'

        # Объединяем строки обратно в контент
        new_content = "\n".join(lines)

        # Запись обратно в файл
        with sftp.open(path, "w") as file:
            file.write(new_content)

        logging.info(
            f"Modified docker-compose.yml for server ID: {server_id} to version: {new_version}"
        )
        return True

    except Exception as error:
        logging.error(
            f"Failed to modify docker-compose.yml for server ID: {server_id}: {error}"
        )
        return False

    finally:
        ssh_client.close()


def reinstall_user_server(user_id, server_id, node_name):
    node = next((n for n in config["nodes"] if n["name"] == node_name), None)
    if not node:
        logging.error(f"Node '{node_name}' not found in configuration.")
        return

    ssh_client = connect_via_ssh(node)
    if not ssh_client:
        return

    try:
        command = f"cd /root/windows{server_id} && docker-compose up -d"
        logging.info(f"Reinstalling server ID {server_id}")
        stdin, stdout, stderr = ssh_client.exec_command(command)
        exit_status = stdout.channel.recv_exit_status()
        if exit_status != 0:
            error = stderr.read()
            logging.error(f"Failed to reinstall the server (ID: {server_id}): {error}")
            bot.send_message(
                user_id, f"⚠️ Не удалось переустановить сервер '{server_id}'."
            )
        else:
            bot.send_message(
                user_id, f"✅ Сервер '{server_id}' успешно переустановлен."
            )

    except Exception as error:
        logging.error(
            f"Error reinstalling Docker container for server {server_id}: {error}"
        )

    finally:
        ssh_client.close()


#
# ------------- Обработчики для удаления сервера, функция удаления файлов, и создание сервера ----------
#
@bot.callback_query_handler(func=lambda call: call.data.startswith("confirm_delete:"))
def confirm_delete_callback(call):
    server_id = validate_integer(call.data.split(":")[1])
    user_id = call.message.chat.id

    if server_id is None:
        bot.send_message(user_id, "⚠️ Некорректный ID сервера.")
        logging.error(f"Invalid server ID in confirm_delete callback: {call.data}")
        bot.answer_callback_query(call.id)
        return

    for user in users_db["users"]:
        server = next((s for s in user["servers"] if s["id"] == server_id), None)
        if server:
            # Проверяем статус сервера перед удалением
            if server["status"] == "Запущен":
                if server["status"] != "Остановлен":
                    success_kill, kill_response = execute_docker_command(server_id, "kill", server["node_name"])
                    if not success_kill:
                        bot.send_message(
                            user_id, f"⚠️ Не удалось остановить сервер '{server['name']}'."
                        )
                        logging.error(
                            f"Failed to kill server '{server['name']}' (ID: {server_id}) for user {user_id}: {kill_response}"
                        )
                        return

            success_rm, rm_response = execute_docker_command(server_id, "rm", server["node_name"])
            if not success_rm:
                bot.send_message(
                    user_id,
                    f"⚠️ Не удалось удалить сервер '{server['name']}'.\nОшибка: {rm_response}",
                )
                logging.error(
                    f"Failed to remove server '{server['name']}' (ID: {server_id}) for user {user_id}: {rm_response}"
                )
            else:
                remove_server_files(server_id, server["node_name"])
                user["servers"] = [s for s in user["servers"] if s["id"] != server_id]
                save_users()

                try:
                    if server.get("control_message_id"):
                        bot.delete_message(
                            chat_id=user_id, message_id=server["control_message_id"]
                        )
                        logging.info(
                            f"Deleted control message for server '{server['name']}' (ID: {server_id}) for user {user_id}."
                        )
                except Exception as e:
                    logging.error(
                        f"Failed to delete message for server '{server['name']}' (ID: {server_id}): {e}"
                    )

                bot.send_message(
                    user_id, f"✅ Сервер '{server['name']}' успешно удален."
                )
                logging.info(
                    f"Server '{server['name']}' (ID: {server_id}) deleted for user {user_id}."
                )
            break
    else:
        bot.send_message(user_id, "⚠️ Сервер не найден.")

    bot.answer_callback_query(call.id, "✅ Сервер удален.")


def remove_server_files(server_id, node_name):
    node = next((n for n in config["nodes"] if n["name"] == node_name), None)
    if not node:
        logging.error(f"Node '{node_name}' not found in configuration.")
        return

    ssh_client = connect_via_ssh(node)
    if ssh_client:
        try:
            # Команда для удаления файлов сервера
            remove_command = f"rm -rf /root/windows{server_id}"
            logging.info(
                f"Removing server files for server ID: {server_id}: {remove_command}"
            )
            stdin, stdout, stderr = ssh_client.exec_command(remove_command)
            exit_status = stdout.channel.recv_exit_status()

            if exit_status == 0:
                logging.info(f"Removed server files for server ID: {server_id}")
            else:
                error = stderr.read().decode().strip()
                logging.error(
                    f"Failed to remove server files for server ID: {server_id}: {error}"
                )

            # Добавление команды для очистки неиспользуемых сетей Docker
            prune_command = "docker network prune -f"
            logging.info(
                f"Executing command to remove unused Docker networks: {prune_command}"
            )
            stdin, stdout, stderr = ssh_client.exec_command(prune_command)
            exit_status = stdout.channel.recv_exit_status()

            if exit_status == 0:
                logging.info(
                    f"Successfully pruned unused Docker networks for server ID: {server_id}"
                )
            else:
                error = stderr.read().decode().strip()
                logging.error(
                    f"Failed to prune Docker networks for server ID: {server_id}: {error}"
                )

            # Добавление команды для очистки неиспользуемых volume Docker
            volume_prune_command = "docker volume prune -f"
            logging.info(
                f"Executing command to remove unused Docker volumes: {volume_prune_command}"
            )
            stdin, stdout, stderr = ssh_client.exec_command(volume_prune_command)
            exit_status = stdout.channel.recv_exit_status()

            if exit_status == 0:
                logging.info(
                    f"Successfully pruned unused Docker volumes for server ID: {server_id}"
                )
            else:
                error = stderr.read().decode().strip()
                logging.error(
                    f"Failed to prune Docker volumes for server ID: {server_id}: {error}"
                )

        except Exception as e:
            logging.error(
                f"Failed to execute server cleanup commands for server ID: {server_id}: {e}"
            )
        finally:
            ssh_client.close()
    else:
        logging.error(
            f"SSH connection failed while deleting server files for server ID: {server_id}."
        )


def handle_server_name(message):
    user_id = message.chat.id

    if user_id not in pending_server_creations:
        bot.send_message(
            user_id,
            "⚠️ В процессе создания сервера произошла ошибка. Пожалуйста, попробуйте снова.",
        )
        logging.error(
            f"User {user_id} tried to set server name without pending creation."
        )
        return

    plan = pending_server_creations[user_id]["plan"]
    version = pending_server_creations[user_id]["version"]
    node_name = pending_server_creations[user_id]["node_name"]
    server_name = message.text.strip()

    if not server_name:
        bot.send_message(
            user_id, "⚠️ Имя сервера не может быть пустым. Пожалуйста, попробуйте снова."
        )
        logging.warning(f"User {user_id} provided empty server name.")
        return

    # Check uniqueness
    user = next((user for user in users_db["users"] if user["id"] == user_id), None)
    if user:
        if any(s["name"] == server_name for s in user["servers"]):
            bot.send_message(
                user_id,
                "⚠️ Сервер с таким именем уже существует. Пожалуйста, выберите другое имя.",
            )
            logging.warning(
                f"User {user_id} attempted to use duplicate server name '{server_name}'."
            )
            return
    else:
        bot.send_message(user_id, "🚫 Пользователь не найден.")
        logging.error(f"User {user_id} not found while setting server name.")
        return

    # Find the selected node
    node = next((n for n in config["nodes"] if n["name"] == node_name), None)
    if not node:
        bot.send_message(user_id, "⚠️ Выбранная нода не найдена.")
        # Optionally, restart the process
        return

    # Now, check resource availability
    plan_config = config["plans"][plan]
    required_CPU = float(plan_config["CPU"])
    required_RAM = parse_size(plan_config["RAM"])
    required_DISK = parse_size(plan_config["DISK"])

    # Function to calculate node available resources
    available_resources = calculate_node_available_resources(node)
    if (available_resources["CPU"] < required_CPU or
        available_resources["RAM"] < required_RAM or
        available_resources["DISK"] < required_DISK):
        bot.send_message(
            user_id,
            "⚠️ На выбранной ноде недостаточно ресурсов для создания сервера. Пожалуйста, выберите другую ноду.",
        )
        # Optionally, re-prompt for node selection
        return

    rdp_port = get_unique_port()
    vnc_port = get_unique_port()
    password = generate_random_password()
    del pending_server_creations[user_id]

    server_id = create_server_id()
    try:
        docker_compose_content = create_docker_compose(
            plan, server_id, version, rdp_port, vnc_port, password
        )
    except Exception as e:
        bot.send_message(user_id, "⚠️ Ошибка при создании конфигурации сервера.")
        logging.error(
            f"Failed to create docker compose for server {server_id} and user {user_id}: {e}"
        )
        logging.error(traceback.format_exc())
        return

    ssh_client = connect_via_ssh(node)
    if not ssh_client:
        bot.send_message(
            user_id,
            "🚫 Не удалось подключиться к серверу. Пожалуйста, попробуйте позже.",
        )
        logging.error(
            f"SSH connection failed while creating server {server_id} for user {user_id}."
        )
        return

    try:
        save_docker_compose(ssh_client, docker_compose_content, server_id)
    except Exception as e:
        bot.send_message(user_id, "⚠️ Не удалось сохранить конфигурацию сервера.")
        logging.error(f"Failed to save docker-compose.yml for server {server_id}: {e}")
        logging.error(traceback.format_exc())
        ssh_client.close()
        return

    if not validate_docker_compose(ssh_client, server_id):
        bot.send_message(
            user_id,
            "⚠️ Некорректный docker-compose.yml. Пожалуйста, свяжитесь с поддержкой.",
        )
        ssh_client.close()
        return

    try:
        logging.info(f"Starting Docker container for server {server_id}.")
        stdin, stdout, stderr = ssh_client.exec_command(
            f"cd /root/windows{server_id} && docker-compose up -d"
        )
        exit_status = stdout.channel.recv_exit_status()
        if exit_status != 0:
            logging.error(f"Failed to start Docker container for server {server_id}.")
            bot.send_message(
                user_id,
                "⚠️ Не удалось запустить контейнер. Пожалуйста, попробуйте позже.",
            )
            ssh_client.close()
            return
    except Exception as e:
        logging.error(f"Error starting Docker container for server {server_id}: {e}")
        bot.send_message(
            user_id, "⚠️ Не удалось запустить контейнер. Пожалуйста, попробуйте позже."
        )
        ssh_client.close()
        return

    ssh_client.close()

    server_entry = {
        "id": server_id,
        "name": server_name,
        "plan": plan,
        "status": "Запущен",
        "rdp_port": rdp_port,
        "vnc_port": vnc_port,
        "username": "admin",
        "password": password,
        "next_payment_due": (datetime.now() + timedelta(days=30)).isoformat(),
        "control_message_id": None,
        "node_name": node_name,
    }
    if user:
        user["servers"].append(server_entry)
    else:
        bot.send_message(user_id, "🚫 Пользователь не найден.")
        logging.error(f"User {user_id} not found while updating server {server_id}.")
        return

    user["balance"] -= config["plans"][plan]["price"]
    save_users()

    server_details = (
        f"✅ **Сервер создан! (Ждите установки 10-30 минут.)**\n\n"
        f"**🖥️ Имя сервера:** `{server_name}`\n"
        f"👤 **Пользователь:** `admin`\n"
        f"🌐 **RDP IP:** `{node['host']}:{rdp_port}`\n"
        f"🌐 **VNC:** [http://{node['host']}:{vnc_port}](http://{node['host']}:{vnc_port})\n"
        f"🔑 **Пароль:** `{password}`\n\n"
        f"**🛡️ Характеристики:**\n"
        f"- 💾 **Диск:** `{config['plans'][plan]['DISK']}`\n"
        f"- ⚙️ **ОЗУ:** `{config['plans'][plan]['RAM']}`\n"
        f"- ⚡ **CPU:** `{config['plans'][plan]['CPU']} ядра`\n"
        f"- 💵 **Цена:** `${config['plans'][plan]['price']}`\n"
        f"🌐 **Нода:** `{node_name}` ({node.get('description', '')})"
    )

    control_markup = telebot.types.InlineKeyboardMarkup(row_width=1)
    control_markup.add(
        telebot.types.InlineKeyboardButton(
            text="В панель", callback_data=f"select_server:{server_id}"
        )
    )

    sent_message = bot.send_message(
        user_id, server_details, reply_markup=control_markup, parse_mode="Markdown"
    )
    logging.info(
        f"Server {server_id} created for user {user_id} with name '{server_name}'."
    )

    server_entry["control_message_id"] = sent_message.message_id
    save_users()


def send_server_control_message(chat_id, server, is_admin):
    markup = telebot.types.InlineKeyboardMarkup(row_width=3)

    if server["status"] != "Заморожен":
        markup.add(
            telebot.types.InlineKeyboardButton(
                text="🟢 Старт", callback_data=f"start:{server['id']}"
            ),
            telebot.types.InlineKeyboardButton(
                text="🛑 Стоп", callback_data=f"stop:{server['id']}"
            ),
            telebot.types.InlineKeyboardButton(
                text="🔄 Рестарт", callback_data=f"restart:{server['id']}"
            ),
        )
        markup.add(
            telebot.types.InlineKeyboardButton(
                text="🔧 Переустановить", callback_data=f"reinstall:{server['id']}"
            ),
            telebot.types.InlineKeyboardButton(
                text="❌ Удалить", callback_data=f"delete:{server['id']}"
            ),
        )

    bot.send_message(
        chat_id,
        compose_server_details(server),
        reply_markup=markup,
        parse_mode="Markdown",
    )


def compose_server_details(server):
    node = next((n for n in config["nodes"] if n["name"] == server["node_name"]), None)
    if not node:
        node = {"host": "Unknown", "name": "Unknown", "description": ""}
    plan_info = config["plans"][server["plan"]]
    status_text = server["status"]

    details = (
        f"🔧 **Управление сервером '{server['name']}':**\n"
        f"——————————————\n"
        f"**🛡️ Тарифный план:** {server['plan'].capitalize()}\n\n"
        f"**🛠️ Характеристики:**\n"
        f"- 💾 **Диск:** `{plan_info['DISK']}`\n"
        f"- ⚙️ **ОЗУ:** `{plan_info['RAM']}`\n"
        f"- ⚡ **CPU:** `{plan_info['CPU']} ядра`\n"
        f"- 💵 **Цена:** `${plan_info['price']}`\n\n"
        f"**📌 Статус:** {status_text}\n"
    )

    if server["status"] == "Заморожен":
        details += f"**❄️ Причина заморозки:** {server.get('freeze_reason', 'Нет причины указано.')}\n"

    if server["status"] != "Заморожен":
        details += (
            f"**🌐 RDP IP:** `{node['host']}:{server['rdp_port']}`\n"
            f"**🌐 VNC:** [http://{node['host']}:{server['vnc_port']}](http://{node['host']}:{server['vnc_port']})\n"
            f"👤 **Пользователь:** `admin`\n"
            f"🔑 **Пароль:** `{server['password']}`\n"
            f"🌐 **Нода:** `{server['node_name']}` ({node.get('description', '')})"
        )

    return details


# ---------------------- Функция Поддержки ----------------------
@bot.message_handler(commands=["support"])
def support_command(message):
    markup = telebot.types.InlineKeyboardMarkup(row_width=1)
    markup.add(
        telebot.types.InlineKeyboardButton(
            text="Создать тикет 📝", callback_data="start_ticket_creation"
        ),
        telebot.types.InlineKeyboardButton(
            text="Мои тикеты 📋", callback_data="view_user_tickets"
        ),
    )
    bot.send_message(
        message.chat.id,
        "📞 *Поддержка*: выберите действие ниже.",
        reply_markup=markup,
        parse_mode="Markdown",
    )


@bot.callback_query_handler(func=lambda call: call.data == "start_ticket_creation")
def start_ticket_creation(call):
    bot.send_message(call.message.chat.id, "📝 Введите тему вашего тикета:")
    bot.register_next_step_handler(call.message, handle_ticket_subject)
    bot.answer_callback_query(call.id)


def handle_ticket_subject(message):
    subject = message.text.strip()
    if not subject:
        bot.send_message(
            message.chat.id, "⚠️ Тема не может быть пустой. Попробуйте снова."
        )
        return support_command(message)

    bot.send_message(message.chat.id, "💬 Теперь введите ваш вопрос или проблему:")
    bot.register_next_step_handler(message, handle_ticket_message, subject)


def handle_ticket_message(message, subject):
    ticket_id = len(users_db["support_tickets"]) + 1
    ticket = {
        "id": ticket_id,
        "user_id": message.chat.id,
        "username": message.from_user.username,
        "date": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
        "subject": subject,
        "message": message.text,
        "replies": [],
    }
    users_db["support_tickets"].append(ticket)
    save_users()

    bot.send_message(
        message.chat.id, "✅ Ваш тикет создан. Ожидайте ответ от поддержки."
    )

    notify_admins_about_new_ticket(ticket)
    create_ticket_chat_interface(message.chat.id, ticket_id)


def notify_admins_about_new_ticket(ticket):
    admin_users = [user for user in users_db["users"] if user["is_admin"]]
    for admin in admin_users:
        bot.send_message(
            admin["id"], f"🔔 Новый тикет ID {ticket['id']} от @{ticket['username']}"
        )


@bot.callback_query_handler(func=lambda call: call.data == "view_user_tickets")
def view_user_tickets(call):
    user_id = call.message.chat.id
    user_tickets = [t for t in users_db["support_tickets"] if t["user_id"] == user_id]

    if not user_tickets:
        bot.send_message(user_id, "ℹ️ У вас нет активных тикетов.")
        bot.answer_callback_query(call.id)
        return

    for ticket in user_tickets:
        ticket_interface(user_id, ticket)

    bot.answer_callback_query(call.id)


def ticket_interface(chat_id, ticket):
    markup = telebot.types.InlineKeyboardMarkup(row_width=1)
    markup.add(
        telebot.types.InlineKeyboardButton(
            text="Ответить", callback_data=f"reply_user_ticket:{ticket['id']}"
        )
    )
    bot.send_message(
        chat_id,
        f"📝 *Тикет ID:* `{ticket['id']}`\n"
        f"📅 *Дата:* `{ticket['date']}`\n"
        f"——————————————\n"
        f"**Тема:** {ticket['subject']}\n"
        f"💬 **Сообщение:** {ticket['message']}\n\n"
        f"*Ответы:*\n"
        + "\n".join(
            [
                f'🗨️ {reply["from"]}: {reply["text"]} ({reply["date"]})'
                for reply in ticket["replies"]
            ]
        ),
        reply_markup=markup,
        parse_mode="Markdown",
    )


@bot.callback_query_handler(
    func=lambda call: call.data.startswith("reply_user_ticket:")
)
def reply_user_ticket(call):
    ticket_id = validate_integer(call.data.split(":")[1])
    user_id = call.message.chat.id

    ticket = next(
        (
            t
            for t in users_db["support_tickets"]
            if t["id"] == ticket_id and t["user_id"] == user_id
        ),
        None,
    )

    if ticket:
        bot.send_message(user_id, f"✍️ Введите ваш ответ для тикета ID {ticket_id}:")
        bot.register_next_step_handler(
            call.message, handle_user_reply_to_ticket, ticket_id
        )
    else:
        bot.send_message(user_id, "⚠️ Тикет не найден.")

    bot.answer_callback_query(call.id)


def handle_user_reply_to_ticket(message, ticket_id):
    ticket = next(
        (t for t in users_db["support_tickets"] if t["id"] == ticket_id), None
    )

    if ticket:
        reply = {
            "from": message.from_user.username,
            "text": message.text,
            "date": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()),
        }
        ticket["replies"].append(reply)
        save_users()

        notify_admins_about_reply(ticket_id)
        bot.send_message(
            message.chat.id, f"✅ Ваш ответ добавлен в тикет ID {ticket_id}."
        )
    else:
        bot.send_message(message.chat.id, "⚠️ Тикет не найден.")


def notify_admins_about_reply(ticket_id):
    admin_users = [user for user in users_db["users"] if user["is_admin"]]
    for admin in admin_users:
        bot.send_message(admin["id"], f"🔔 Новый ответ на тикет ID {ticket_id}.")


def create_ticket_chat_interface(chat_id, ticket_id):
    markup = telebot.types.InlineKeyboardMarkup(row_width=1)
    markup.add(
        telebot.types.InlineKeyboardButton(
            text="Ответить", callback_data=f"reply_user_ticket:{ticket_id}"
        ),
        telebot.types.InlineKeyboardButton(
            text="Выйти из чата", callback_data="exit_ticket_chat"
        ),
    )
    bot.send_message(
        chat_id, f"💬 Вы вошли в чат тикета ID {ticket_id}.", reply_markup=markup
    )


@bot.callback_query_handler(
    func=lambda call: call.data.startswith("reply_admin_ticket:")
)
def reply_admin_ticket(call):
    ticket_id = validate_integer(call.data.split(":")[1])
    admin_id = call.message.chat.id

    ticket = next(
        (t for t in users_db["support_tickets"] if t["id"] == ticket_id), None
    )

    if ticket:
        bot.send_message(admin_id, f"✍️ Введите ваш ответ для тикета ID {ticket_id}:")
        bot.register_next_step_handler(
            call.message, handle_admin_reply_to_ticket, ticket_id
        )
    else:
        bot.send_message(admin_id, "⚠️ Тикет не найден.")

    bot.answer_callback_query(call.id)


@bot.callback_query_handler(func=lambda call: call.data == "exit_ticket_chat")
def exit_ticket_chat(call):
    bot.send_message(call.message.chat.id, "📴 Вы вышли из чата тикета.")
    send_main_keyboard(call.message.chat.id)
    bot.answer_callback_query(call.id)


# ---------------------- Обработчик Неизвестных Команд ----------------------
@bot.message_handler(func=lambda message: True)
def unknown_command(message):
    bot.send_message(
        message.chat.id,
        "❓ Неизвестная команда. Используйте кнопки ниже для взаимодействия с ботом или введите /help для получения списка доступных команд.",
    )
    logging.warning(f"User {message.chat.id} sent an unknown command: {message.text}")


def periodic_checks():
    while True:
        try:
            check_frozen_servers()
            monthly_billing_check()
        except Exception as e:
            logging.error(f"Periodic check failed: {e}")
            logging.error(traceback.format_exc())
        time.sleep(60) 

# ---------------------- Функция Главного Запуска Бота ----------------------
def main():
    load_users()
    logging.info("Bot started")
    threading.Thread(target=periodic_checks, daemon=True).start()
    logging.info("Bot is polling...")
    while True:
        try:
            bot.polling(none_stop=True)
        except Exception as e:
            logging.error(f"Bot polling failed: {e}")
            logging.error(traceback.format_exc())
            time.sleep(15)  # Wait before restarting polling


if __name__ == "__main__":
    main()
```