import json
import os
import random
import paramiko
import telebot
import string
import textwrap
import logging
import time
import traceback
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")  # Убедитесь, что у вас есть BOT_TOKEN в вашем .env файле
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") as file:
        config = json.load(file)
    logging.info("Configuration loaded successfully.")
except FileNotFoundError:
    logging.error("config.json file not found.")
    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 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}")


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, "👋 Добро пожаловать! Используйте /help для списка доступных команд.")


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'  # Разрешены: A-Z и 1-9
    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):
    plan_config = config["plans"][plan]
    rdp_port = get_unique_port()
    vnc_port = get_unique_port()
    password = generate_random_password()

    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}"
        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, rdp_port, vnc_port, password


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):
    server = config["servers"][0]
    ssh_client = connect_via_ssh(server)
    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 {server['host']}")
    else:
        logging.error(
            f"Cannot execute docker command '{command}' on server {server['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"]


# ---------------------- Ограничение по Частоте Запросов (Rate Limiting) ----------------------

user_command_times = defaultdict(list)
RATE_LIMIT = 5  # Количество команд
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


# ---------------------- Обработчики Команд Бота ----------------------

@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.")


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


@bot.message_handler(commands=["balance"])
@rate_limit_decorator
def balance_command(message):
    user = next((user for user in users_db["users"] if user["id"] == message.chat.id), None)
    if user:
        bot.send_message(message.chat.id, f"💵 Ваш баланс: ${user['balance']}")
        logging.info(f"Displayed balance for user {message.chat.id}.")
    else:
        bot.send_message(message.chat.id, "🚫 У вас нет баланса.")
        logging.warning(f"Balance check for non-existent user {message.chat.id}.")


@bot.message_handler(commands=["shop"])
@rate_limit_decorator
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=plan.capitalize(), callback_data=f"plan:{plan}"))
    bot.send_message(message.chat.id, "🛒 Выберите тарифный план:", reply_markup=markup)
    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"
            "/setadmin [ID] - Назначить пользователя админом\n"
            "/removeadmin [ID] - Удалить пользователя из админов\n"
            "/listusers - Показать всех пользователей\n"
            "/givebalance [ID] [AMOUNT] - Выдать баланс пользователю\n"
            "/viewtickets - Просмотреть все запросы в поддержку\n"
            "/backup - Создать резервную копию данных\n"
            "/restore [backup_filename] - Восстановить данные из резервной копии"
        )
        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", "removeadmin"])
def set_admin_command(message):
    if is_admin(message.chat.id):
        parts = message.text.split()
        if len(parts) != 2:
            bot.send_message(message.chat.id, "⚠️ Использование: /setadmin [ID] или /removeadmin [ID]")
            logging.warning(f"Incorrect admin command usage by {message.chat.id}: {message.text}")
            return
        command, user_id_str = parts
        user_id = validate_integer(user_id_str)
        if user_id is None:
            bot.send_message(message.chat.id, "⚠️ ID пользователя должно быть числом.")
            logging.warning(f"Non-integer user ID provided by {message.chat.id}: {user_id_str}")
            return
        if command == "/setadmin":
            success = set_admin(user_id, True)
            if success:
                bot.send_message(message.chat.id, f"✅ Пользователь {user_id} назначен администратором.")
                logging.info(f"User {user_id} set as admin by {message.chat.id}.")
            else:
                bot.send_message(message.chat.id, "🚫 Пользователь не найден.")
                logging.error(f"Failed to set admin for non-existent user {user_id} by {message.chat.id}.")
        elif command == "/removeadmin":
            success = set_admin(user_id, False)
            if success:
                bot.send_message(message.chat.id, f"✅ Пользователь {user_id} удален из администраторов.")
                logging.info(f"User {user_id} removed from admin by {message.chat.id}.")
            else:
                bot.send_message(message.chat.id, "🚫 Пользователь не найден.")
                logging.error(f"Failed to remove admin for non-existent user {user_id} by {message.chat.id}.")
    else:
        bot.send_message(message.chat.id, "🚫 У вас нет прав.")
        logging.warning(f"User {message.chat.id} attempted to use admin commands without permissions.")


@bot.message_handler(commands=["givebalance"])
def give_balance_command(message):
    if is_admin(message.chat.id):
        parts = message.text.split()
        if len(parts) != 3:
            bot.send_message(message.chat.id, "⚠️ Использование: /givebalance [ID] [AMOUNT]")
            logging.warning(f"Incorrect givebalance command usage by {message.chat.id}: {message.text}")
            return
        _, user_id_str, amount_str = parts
        user_id = validate_integer(user_id_str)
        amount = validate_float(amount_str)
        if user_id is None or amount is None:
            bot.send_message(message.chat.id, "⚠️ ID должно быть числом и сумма должна быть числом.")
            logging.warning(
                f"Invalid parameters for givebalance by {message.chat.id}: ID={user_id_str}, AMOUNT={amount_str}")
            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"✅ Выдано ${amount} пользователю {user_id}.")
            logging.info(f"User {user_id} given ${amount} by admin {message.chat.id}.")
        else:
            bot.send_message(message.chat.id, "🚫 Пользователь не найден.")
            logging.error(f"Attempted to give balance to non-existent user {user_id} by {message.chat.id}.")
    else:
        bot.send_message(message.chat.id, "🚫 У вас нет прав.")
        logging.warning(f"User {message.chat.id} attempted to use givebalance without permissions.")


@bot.message_handler(commands=["menu"])
@rate_limit_decorator
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()
            markup.add(
                telebot.types.InlineKeyboardButton(text="▶️ Запустить все", callback_data="start_all"),
                telebot.types.InlineKeyboardButton(text="🛑 Остановить все", callback_data="stop_all"),
                telebot.types.InlineKeyboardButton(text="🔄 Перезапустить все", callback_data="restart_all")
            )
            bot.send_message(message.chat.id, "🛠️ **Управление всеми серверами:**", reply_markup=markup)
            logging.info(f"Displayed global management buttons to user {message.chat.id}.")

            # Отправить отдельные сообщения для каждого сервера с контролем
            for server in user["servers"]:
                send_server_control_message(message.chat.id, server)
        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]

    user = next((user for user in users_db["users"] if user["id"] == call.message.chat.id), None)
    if user and user["balance"] >= config["plans"][plan]["price"]:
        # Перейти к выбору версии ОС
        versions = ["2022", "2025", "2019", "win10", "win11"]
        version_markup = telebot.types.InlineKeyboardMarkup(row_width=1)

        for version in versions:
            if version.startswith("win"):
                version_display = f"Windows {version[-2:].upper()}"
            else:
                version_display = f"Windows Server {version}"
            version_markup.add(
                telebot.types.InlineKeyboardButton(
                    text=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 plan '{plan}'. Prompting for OS version.")
    else:
        bot.send_message(call.message.chat.id, "🚫 У вас недостаточно средств для покупки этого тарифа.")
        logging.warning(f"User {call.message.chat.id} has insufficient balance for plan '{plan}'.")


@bot.callback_query_handler(func=lambda call: call.data.startswith("back_plan:"))
def back_plan_callback(call):
    plan = call.data.split(":")[1]

    # Повторно отобразить меню выбора тарифных планов
    markup = telebot.types.InlineKeyboardMarkup(row_width=1)
    for plan in plan_names:
        markup.add(telebot.types.InlineKeyboardButton(text=plan.capitalize(), callback_data=f"plan:{plan}"))
    bot.send_message(call.message.chat.id, "🛒 Выберите тарифный план:", reply_markup=markup)
    logging.info(f"User {call.message.chat.id} clicked Back. Redisplaying plan selection.")


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

    _, version, _, plan = data_parts

    supported_versions = ["2022", "2025", "2019", "win10", "win11"]  # Ensure '2025' is included

    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}'.")
        return

    # Сохранить данные о создаваемом сервере в временном хранилище
    pending_server_creations[call.message.chat.id] = {'plan': plan, 'version': version}

    # Запросить у пользователя имя сервера
    bot.send_message(call.message.chat.id, "📝 **Введите имя для вашего сервера:**", parse_mode="Markdown")
    bot.register_next_step_handler(call.message, handle_server_name)

    logging.info(
        f"User {call.message.chat.id} selected version '{version}' for plan '{plan}'. Prompting for server name.")


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']
    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

    # Проверка уникальности имени сервера для данного пользователя
    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

    # Удалить запись из временного хранилища
    del pending_server_creations[user_id]

    # Продолжить процесс создания сервера с использованием заданного имени
    try:
        server_id = create_server_id()
        docker_compose_content, rdp_port, vnc_port, password = create_docker_compose(plan, server_id, version)
    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

    server_info = config["servers"][0]
    ssh_client = connect_via_ssh(server_info)
    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

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

    # Запуск Docker контейнера
    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()
        output = stdout.read().decode().strip()
        error = stderr.read().decode().strip()
        if exit_status != 0:
            logging.error(f"Failed to start Docker container for server {server_id}: {error}")
            bot.send_message(user_id, "⚠️ Не удалось запустить контейнер. Пожалуйста, попробуйте позже.")
            ssh_client.close()
            return
        logging.info(f"Docker container for server {server_id} started successfully: {output}")
    except Exception as e:
        logging.error(f"Error starting Docker container for server {server_id}: {e}")
        logging.error(traceback.format_exc())
        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,
        "control_message_id": None  # Будет обновлено после отправки сообщения
    }
    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"✅ **Сервер создан!**\n\n"
        f"**Имя сервера:** `{server_name}`\n"
        f"👤 **Пользователь:** `admin`\n"
        f"🌐 **RDP IP:** {server_info['host']}:{rdp_port}\n"
        f"🌐 **VNC:** [http://{server_info['host']}:{vnc_port}](http://{server_info['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']}"
    )

    # Создать inline клавиатуру для управления сервером
    control_markup = telebot.types.InlineKeyboardMarkup(row_width=4)
    control_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}"),
        telebot.types.InlineKeyboardButton(text="❌ Удалить", callback_data=f"delete:{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}'.")

    # Сохранить message_id в сервере для дальнейшего обновления
    server_entry["control_message_id"] = sent_message.message_id
    save_users()


@bot.callback_query_handler(func=lambda call: call.data in ["start_all", "stop_all", "restart_all"])
def manage_all_callback(call):
    user_id = call.message.chat.id
    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 while managing all servers.")
        return

    action = call.data.split("_")[0]  # 'start', 'stop', 'restart'
    for server in user["servers"]:
        success, response = execute_docker_command(server["id"], action)
        if success:
            server["status"] = "Запущен" if action == "start" else "Остановлен" if action == "stop" else "Перезапущен"
            save_users()
            bot.send_message(user_id, f"✅ Сервер '{server['name']}' успешно {server['status'].lower()}.")
            # Обновить сообщение с деталями сервера
            update_server_message(user_id, server)
        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}.")


def send_server_control_message(chat_id, server):
    # Проверить, есть ли уже сообщение для сервера
    if server.get("control_message_id"):
        try:
            bot.edit_message_text(
                chat_id=chat_id,
                message_id=server["control_message_id"],
                text=compose_server_details(server),
                reply_markup=compose_server_markup(server),
                parse_mode="Markdown"
            )
            logging.info(
                f"Updated control message for server '{server['name']}' (ID: {server['id']}) for user {chat_id}.")
        except Exception as e:
            logging.error(f"Failed to edit message for server {server['id']}: {e}")
    else:
        # Отправить новое сообщение для сервера
        sent_message = bot.send_message(
            chat_id,
            compose_server_details(server),
            reply_markup=compose_server_markup(server),
            parse_mode="Markdown"
        )
        logging.info(f"Sent control message for server '{server['name']}' (ID: {server['id']}) to user {chat_id}.")

        # Сохранить message_id
        server["control_message_id"] = sent_message.message_id
        save_users()


def compose_server_details(server):
    server_info = config["servers"][0]
    plan_info = config["plans"][server['plan']]
    status_text = server["status"]

    return (
        f"🔧 **Управление сервером '{server['name']}':**\n\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"
        f"**RDP IP:** {server_info['host']}:{server['rdp_port']}\n"
        f"**VNC:** [http://{server_info['host']}:{server['vnc_port']}](http://{server_info['host']}:{server['vnc_port']})\n"
        f"👤 **Пользователь:** `admin`\n"
        f"🔑 **Пароль:** `{server['password']}`"
    )


def compose_server_markup(server):
    return telebot.types.InlineKeyboardMarkup(row_width=4).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']}"),
        telebot.types.InlineKeyboardButton(text="❌ Удалить", callback_data=f"delete:{server['id']}")
    )


def update_server_message(chat_id, server):
    if server.get("control_message_id"):
        try:
            bot.edit_message_text(
                chat_id=chat_id,
                message_id=server["control_message_id"],
                text=compose_server_details(server),
                reply_markup=compose_server_markup(server),
                parse_mode="Markdown"
            )
            logging.info(f"Updated server details for '{server['name']}' (ID: {server['id']}) for user {chat_id}.")
        except Exception as e:
            logging.error(f"Failed to edit message for server {server['id']}: {e}")
    else:
        send_server_control_message(chat_id, server)


@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:"))
def server_control_callback(call):
    user_id = call.message.chat.id
    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 while controlling server.")
        return

    parts = call.data.split(":")
    action = parts[0]
    server_id = validate_integer(parts[1])

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

    if action == "delete":
        # Подтвердить удаление
        markup = telebot.types.InlineKeyboardMarkup(row_width=2)
        markup.add(
            telebot.types.InlineKeyboardButton(text="✅ Да", callback_data=f"confirm_delete:{server_id}"),
            telebot.types.InlineKeyboardButton(text="❌ Нет", callback_data="cancel_delete")
        )
        bot.send_message(
            user_id,
            f"❗ Вы уверены, что хотите удалить сервер '{server['name']}'?",
            reply_markup=markup
        )
        logging.info(f"User {user_id} initiated delete for server '{server['name']}' (ID: {server_id}).")
        bot.answer_callback_query(call.id, "⚠️ Подтвердите удаление сервера.")
        return

    # Выполнить действие (start, stop, restart)
    success, response = execute_docker_command(server_id, action)
    if success:
        # Обновить статус сервера
        if action == "start":
            server["status"] = "Запущен"
        elif action == "stop":
            server["status"] = "Остановлен"
        elif action == "restart":
            server["status"] = "Перезапущен"
        save_users()

        # Обновить сообщение
        update_server_message(user_id, server)

        # Отправить подтверждение
        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"✅ Сервер '{server['name']}' {action}ирован.")


@bot.callback_query_handler(func=lambda call: call.data.startswith("confirm_delete:") or
                                              call.data == "cancel_delete")
def delete_server_confirm_callback(call):
    user_id = call.message.chat.id
    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 while confirming delete.")
        return

    if call.data.startswith("confirm_delete:"):
        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 confirm_delete callback: {call.data}")
            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}.")
            return

        # Остановить и удалить Docker контейнер
        success_stop, response_stop = execute_docker_command(server_id, "stop")
        success_rm, response_rm = execute_docker_command(server_id, "rm")

        if not success_stop or not success_rm:
            bot.send_message(user_id,
                             f"⚠️ Не удалось удалить сервер '{server['name']}'.\nОшибка: {response_stop or response_rm}")
            logging.error(f"Failed to delete server '{server['name']}' (ID: {server_id}) for user {user_id}.")
            return

        # Удалить файлы docker-compose.yml с сервера
        server_info = config["servers"][0]
        ssh_client = connect_via_ssh(server_info)
        if ssh_client:
            try:
                command = f"rm -rf /root/windows{server_id}"
                logging.info(f"Removing server files for server '{server['name']}' (ID: {server_id}): {command}")
                stdin, stdout, stderr = ssh_client.exec_command(command)
                exit_status = stdout.channel.recv_exit_status()
                if exit_status == 0:
                    logging.info(f"Removed server files for server '{server['name']}' (ID: {server_id}).")
                else:
                    error = stderr.read().decode().strip()
                    logging.error(
                        f"Failed to remove server files for server '{server['name']}' (ID: {server_id}): {error}")
            except Exception as e:
                logging.error(f"Failed to remove server files for server '{server['name']}' (ID: {server_id}): {e}")
            finally:
                ssh_client.close()
        else:
            logging.error(
                f"SSH connection failed while deleting server files for server '{server['name']}' (ID: {server_id}).")

        # Удалить сервер из списка пользователя
        user["servers"] = [s for s in user["servers"] if s["id"] != server_id]
        save_users()

        # Удалить сообщение с сервера
        try:
            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}.")

    elif call.data == "cancel_delete":
        bot.send_message(user_id, "❌ Удаление сервера отменено.")
        logging.info(f"User {user_id} canceled server deletion.")

    bot.answer_callback_query(call.id)


# ---------------------- Обработчики Callback Query Для Глобального Управления ----------------------

@bot.callback_query_handler(func=lambda call: call.data.startswith("start_all") or
                                              call.data.startswith("stop_all") or
                                              call.data.startswith("restart_all"))
def global_control_callback(call):
    user_id = call.message.chat.id
    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 while performing global control.")
        return

    action = call.data.split("_")[0]  # 'start', 'stop', 'restart'
    for server in user["servers"]:
        success, response = execute_docker_command(server["id"], action)
        if success:
            # Обновить статус сервера
            if action == "start":
                server["status"] = "Запущен"
            elif action == "stop":
                server["status"] = "Остановлен"
            elif action == "restart":
                server["status"] = "Перезапущен"
            save_users()
            bot.send_message(user_id, f"✅ Сервер '{server['name']}' успешно {server['status'].lower()}.")
            # Обновить сообщение с деталями сервера
            update_server_message(user_id, server)
        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}ены.")


# ---------------------- Функция Главного Запуска Бота ----------------------

def main():
    load_users()
    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()

вот мой код телеграм бота, думаю сам поймешь что он делает. короче какие исправления вносим:

добавляем Обычные кнопки клавиатуры для бота и команд
в /menu делаем управление не сразу всеми серверами а по отдельности выбор сервера и уже его управление