!!! ВЫШЛИ ПОЛНЫЙ КОД !!! Вот сюда интегрируй пополнение баланса в личном кабинете используй платежную систему от crypto bot, чтобы всё работало без моего участия.
```
import json
import os
import random
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:
        # Заморозка сервера, если недостаточно средств
        server["status"] = "Заморожен"
        server["freeze_reason"] = "Недостаточно средств."
        server["freeze_date"] = datetime.now().isoformat()
        save_users()
        bot.send_message(
            user["id"],
            f"❄️ Ваш сервер '{server['name']}' заморожен из-за отсутствия достаточного баланса. Пополните баланс в течение 3 дней.",
        )
        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=3):
                    # Удаляем сервер, так как он оставался замороженным более трех дней
                    logging.info(
                        f"Deleting server '{server['name']}' for user {user['id']} after 3 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']}' удален после более чем 3 дней заморозки.",
                    )
                    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"] = "Остановлен"
                elif action == "restart":
                    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

    # Останавливаем и удаляем контейнер
    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"] == "Запущен":
                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 main():
    load_users()
    logging.info("Bot started")
    logging.info("Bot is polling...")
    while True:
        try:
            monthly_billing_check()  # Проверка ежемесячного биллинга
            check_frozen_servers()  # Проверка замороженных серверов
            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()
```