import json
import os
import random
import paramiko
import telebot
import string
import textwrap
import logging
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.FileHandler("bot.log"),
        logging.StreamHandler()
    ]
)

# Configuration and Sensitive Data
TOKEN = os.getenv("BOT_TOKEN")  # Ensure you have BOT_TOKEN in your .env file
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)

# Load configuration from 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())

# Initialize server_counter from config or start at 0
server_counter = config.get("server_counter", 0)

# User Database
users_db = {"users": [], "server_counter": server_counter}

# ---------------------- Utility Functions ----------------------


def load_users():
    global users_db
    try:
        with open("db.json", "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.json", "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})")


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():
    port = random.randint(4000, 10000)
    while port in config["rdp_ports"]:
        port = random.randint(4000, 10000)
    config["rdp_ports"].append(port)
    logging.debug(f"Assigned unique port: {port}")
    return port


def generate_random_password(length=12):
    characters = string.ascii_uppercase + '123456789'  # Allowed: A-Z and 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}")
            else:
                logging.info(f"Command '{full_command}' executed successfully: {output}")
        except Exception as e:
            logging.error(f"Error executing docker command '{command}' on server {server_id}: {e}")
        finally:
            ssh_client.close()
            logging.info(f"SSH connection closed for server {server['host']}")


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

# ---------------------- Bot Command Handlers ----------------------


@bot.message_handler(commands=["start"])
def start_command(message):
    add_user(message.chat.id, message.from_user.username)
    bot.send_message(message.chat.id, "👋 Привет! Используйте /help для списка доступных команд.")
    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 - Проверить баланс"
    )
    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"])
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"])
def shop_command(message):
    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] - Выдать баланс пользователю"
        )
        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"])
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['id']}",
                    callback_data=f"control:{server['id']}"
                ))
            bot.send_message(message.chat.id, "🖥️ Ваши серверы:", reply_markup=markup)
            logging.info(f"Displayed server menu to user {message.chat.id}.")
        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 Handlers ----------------------


@bot.callback_query_handler(func=lambda call: call.data.startswith("plan:"))
def plan_callback(call):
    global server_counter
    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"]:
        server_counter += 1
        server_id = server_counter  # Assign current server_id

        # Prepare server characteristics message without VERSION
        plan_info = config["plans"][plan]
        characteristics = (
            f"**Тарифный план:** {plan.capitalize()}\n"
            f"**Диск:** {plan_info['DISK']}\n"
            f"**ОЗУ:** {plan_info['RAM']}\n"
            f"**CPU:** {plan_info['CPU']} ядра\n"
            f"**Цена:** ${plan_info['price']}"
        )

        # Create inline keyboard with "Back" and "Buy" buttons
        markup = telebot.types.InlineKeyboardMarkup(row_width=2)
        markup.add(
            telebot.types.InlineKeyboardButton(text="🔙 Назад", callback_data=f"back_plan:{plan}"),
            telebot.types.InlineKeyboardButton(text="🛒 Купить", callback_data=f"confirm_buy:{plan}")
        )

        # Send characteristics message
        bot.send_message(call.message.chat.id, characteristics, reply_markup=markup, parse_mode="Markdown")
        logging.info(f"User {call.message.chat.id} selected plan '{plan}'. Sent characteristics with Buy and Back options.")
    else:
        bot.send_message(call.message.chat.id, "🚫 У вас недостаточно средств для покупки этого тарифа.")
        logging.warning(f"User {call.message.chat.id} has insufficient balance for plan '{plan}'.")


# New callback to handle "Back" button from plan characteristics
@bot.callback_query_handler(func=lambda call: call.data.startswith("back_plan:"))
def back_plan_callback(call):
    plan = call.data.split(":")[1]

    # Redisplay the plan selection menu
    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.")


# New callback to handle "Buy" button and proceed to OS selection
@bot.callback_query_handler(func=lambda call: call.data.startswith("confirm_buy:"))
def confirm_buy_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"]:
        # Proceed to OS selection step
        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} confirmed purchase of plan '{plan}'. Prompting for OS version.")
    else:
        bot.send_message(call.message.chat.id, "🚫 У вас недостаточно средств для покупки этого тарифа.")
        logging.warning(f"User {call.message.chat.id} attempted to buy plan '{plan}' without sufficient balance.")


@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

    server_id = server_counter  # Use the server_id assigned during plan selection

    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

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

    # Save docker-compose.yml to the server
    server = config["servers"][0]
    ssh_client = connect_via_ssh(server)
    if not ssh_client:
        bot.send_message(call.message.chat.id, "🚫 Не удалось подключиться к серверу. Пожалуйста, попробуйте позже.")
        logging.error(f"SSH connection failed while creating server {server_id} for user {call.message.chat.id}.")
        return

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

    # Optional: Validate the Docker Compose file
    if not validate_docker_compose(ssh_client, server_id):
        bot.send_message(call.message.chat.id, "⚠️ Некорректный docker-compose.yml. Пожалуйста, свяжитесь с поддержкой.")
        ssh_client.close()
        return

    # Start the Docker container
    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(call.message.chat.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}")
        bot.send_message(call.message.chat.id, "⚠️ Не удалось запустить контейнер. Пожалуйста, попробуйте позже.")
        ssh_client.close()
        return

    ssh_client.close()

    # Update user data
    user = next((user for user in users_db["users"] if user["id"] == call.message.chat.id), None)
    if user:
        user["servers"].append({
            "id": server_id,
            "plan": plan,
            "status": "Запущен",
            "rdp_port": rdp_port,
            "vnc_port": vnc_port,
            "password": password
        })

        # Deduct the price from the user's balance
        user["balance"] -= config["plans"][plan]["price"]
        save_users()

        # Send server details to the user
        server_info = config["servers"][0]
        bot.send_message(
            call.message.chat.id,
            f"✅ **Сервер создан!**\n\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}`",
            parse_mode="Markdown"
        )
        logging.info(f"Server {server_id} created for user {call.message.chat.id}.")
    else:
        bot.send_message(call.message.chat.id, "🚫 Пользователь не найден.")
        logging.error(f"User {call.message.chat.id} not found after server creation.")

@bot.callback_query_handler(func=lambda call: call.data.startswith("control:"))
def control_callback(call):
    server_id_str = call.data.split(":")[1]
    server_id = validate_integer(server_id_str)
    if server_id is None:
        bot.send_message(call.message.chat.id, "⚠️ Некорректный ID сервера.")
        logging.error(f"Invalid server ID in control callback from user {call.message.chat.id}: {server_id_str}")
        return

    user = next((user for user in users_db["users"] if user["id"] == call.message.chat.id), None)
    if user:
        server = next((s for s in user["servers"] if s["id"] == server_id), None)
        if server:
            server_status = f"**Статус:** {server['status']}\n"
            control_markup = telebot.types.InlineKeyboardMarkup(row_width=3)
            control_markup.row(
                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"start:{server_id}")
            )
            control_markup.add(
                telebot.types.InlineKeyboardButton(text="❌ Удалить", callback_data=f"delete:{server_id}")
            )
            bot.send_message(
                call.message.chat.id,
                f"🔧 **Управление сервером {server_id}:**\n{server_status}",
                reply_markup=control_markup,
                parse_mode="Markdown"
            )
            logging.info(f"Displayed control menu for server {server_id} to user {call.message.chat.id}.")
        else:
            bot.send_message(call.message.chat.id, "🚫 Сервер не найден.")
            logging.error(f"User {call.message.chat.id} attempted to control non-existent server {server_id}.")
    else:
        bot.send_message(call.message.chat.id, "🚫 У вас нет доступа к этому серверу.")
        logging.warning(f"Non-existent user {call.message.chat.id} attempted to access server controls.")


@bot.callback_query_handler(func=lambda call: call.data.startswith("start:"))
def start_server_callback(call):
    server_id_str = call.data.split(":")[1]
    server_id = validate_integer(server_id_str)
    if server_id is None:
        bot.send_message(call.message.chat.id, "⚠️ Некорректный ID сервера.")
        logging.error(f"Invalid server ID in start callback from user {call.message.chat.id}: {server_id_str}")
        return

    execute_docker_command(server_id, "start")

    user = next((user for user in users_db["users"] if user["id"] == call.message.chat.id), None)
    if user:
        server = next((s for s in user["servers"] if s["id"] == server_id), None)
        if server:
            server["status"] = "Запущен"
            save_users()
            bot.send_message(call.message.chat.id, f"✅ Сервер {server_id} запущен.")
            logging.info(f"User {call.message.chat.id} started server {server_id}.")
        else:
            bot.send_message(call.message.chat.id, "🚫 Сервер не найден.")
            logging.error(f"User {call.message.chat.id} attempted to start non-existent server {server_id}.")
    else:
        bot.send_message(call.message.chat.id, "🚫 Пользователь не найден.")
        logging.error(f"User {call.message.chat.id} not found while starting server {server_id}.")


@bot.callback_query_handler(func=lambda call: call.data.startswith("stop:"))
def stop_server_callback(call):
    server_id_str = call.data.split(":")[1]
    server_id = validate_integer(server_id_str)
    if server_id is None:
        bot.send_message(call.message.chat.id, "⚠️ Некорректный ID сервера.")
        logging.error(f"Invalid server ID in stop callback from user {call.message.chat.id}: {server_id_str}")
        return

    execute_docker_command(server_id, "stop")

    user = next((user for user in users_db["users"] if user["id"] == call.message.chat.id), None)
    if user:
        server = next((s for s in user["servers"] if s["id"] == server_id), None)
        if server:
            server["status"] = "Остановлен"
            save_users()
            bot.send_message(call.message.chat.id, f"🛑 Сервер {server_id} остановлен.")
            logging.info(f"User {call.message.chat.id} stopped server {server_id}.")
        else:
            bot.send_message(call.message.chat.id, "🚫 Сервер не найден.")
            logging.error(f"User {call.message.chat.id} attempted to stop non-existent server {server_id}.")
    else:
        bot.send_message(call.message.chat.id, "🚫 Пользователь не найден.")
        logging.error(f"User {call.message.chat.id} not found while stopping server {server_id}.")


@bot.callback_query_handler(func=lambda call: call.data.startswith("restart:"))
def restart_server_callback(call):
    server_id_str = call.data.split(":")[1]
    server_id = validate_integer(server_id_str)
    if server_id is None:
        bot.send_message(call.message.chat.id, "⚠️ Некорректный ID сервера.")
        logging.error(f"Invalid server ID in restart callback from user {call.message.chat.id}: {server_id_str}")
        return

    execute_docker_command(server_id, "restart")

    user = next((user for user in users_db["users"] if user["id"] == call.message.chat.id), None)
    if user:
        server = next((s for s in user["servers"] if s["id"] == server_id), None)
        if server:
            server["status"] = "Запущен"
            save_users()
            bot.send_message(call.message.chat.id, f"🔄 Сервер {server_id} перезапущен.")
            logging.info(f"User {call.message.chat.id} restarted server {server_id}.")
        else:
            bot.send_message(call.message.chat.id, "🚫 Сервер не найден.")
            logging.error(f"User {call.message.chat.id} attempted to restart non-existent server {server_id}.")
    else:
        bot.send_message(call.message.chat.id, "🚫 Пользователь не найден.")
        logging.error(f"User {call.message.chat.id} not found while restarting server {server_id}.")


@bot.callback_query_handler(func=lambda call: call.data.startswith("delete:"))
def delete_server_callback(call):
    server_id_str = call.data.split(":")[1]
    server_id = validate_integer(server_id_str)
    if server_id is None:
        bot.send_message(call.message.chat.id, "⚠️ Некорректный ID сервера.")
        logging.error(f"Invalid server ID in delete callback from user {call.message.chat.id}: {server_id_str}")
        return

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

    if user:
        server = next((s for s in user["servers"] if s["id"] == server_id), None)
        if server:
            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(
                call.message.chat.id,
                f"❗ Вы уверены, что хотите удалить сервер {server_id}?",
                reply_markup=markup
            )
            logging.info(f"User {call.message.chat.id} initiated delete for server {server_id}.")
        else:
            bot.send_message(call.message.chat.id, "🚫 Сервер не найден.")
            logging.error(f"User {call.message.chat.id} attempted to delete non-existent server {server_id}.")
    else:
        bot.send_message(call.message.chat.id, "🚫 У вас нет доступа к этому серверу.")
        logging.warning(f"Non-existent user {call.message.chat.id} attempted to delete server {server_id}.")


@bot.callback_query_handler(func=lambda call: call.data.startswith("confirm_delete:"))
def confirm_delete_callback(call):
    server_id_str = call.data.split(":")[1]
    server_id = validate_integer(server_id_str)
    if server_id is None:
        bot.send_message(call.message.chat.id, "⚠️ Некорректный ID сервера.")
        logging.error(f"Invalid server ID in confirm_delete callback from user {call.message.chat.id}: {server_id_str}")
        return

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

    if user:
        server = next((s for s in user["servers"] if s["id"] == server_id), None)
        if server:
            # Stop and remove the Docker container
            execute_docker_command(server_id, "stop")
            execute_docker_command(server_id, "rm")

            # Remove docker-compose files from the server
            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_id}: {command}")
                    ssh_client.exec_command(command)
                    logging.info(f"Removed server files for server {server_id}.")
                except Exception as e:
                    logging.error(f"Failed to remove server files for server {server_id}: {e}")
                finally:
                    ssh_client.close()
            else:
                logging.error(f"SSH connection failed while deleting server files for server {server_id}.")

            # Remove server from user's list
            user["servers"] = [s for s in user["servers"] if s["id"] != server_id]
            save_users()
            bot.send_message(call.message.chat.id, f"✅ Сервер {server_id} успешно удален.")
            logging.info(f"Server {server_id} deleted for user {call.message.chat.id}.")
        else:
            bot.send_message(call.message.chat.id, "🚫 Сервер не найден.")
            logging.error(f"User {call.message.chat.id} attempted to confirm delete for non-existent server {server_id}.")
    else:
        bot.send_message(call.message.chat.id, "🚫 Пользователь не найден.")
        logging.error(f"User {call.message.chat.id} not found while confirming delete for server {server_id}.")


@bot.callback_query_handler(func=lambda call: call.data == "cancel_delete")
def cancel_delete_callback(call):
    bot.send_message(call.message.chat.id, "❌ Удаление сервера отменено.")
    logging.info(f"User {call.message.chat.id} canceled server deletion.")

# ---------------------- Start Bot ----------------------


def main():
    load_users()
    logging.info("Bot is polling...")
    try:
        bot.polling(none_stop=True)
    except Exception as e:
        logging.error(f"Bot polling failed: {e}")


if __name__ == "__main__":
    main()

here is my billing bot for creating servers through docker
here is its config:
{
    "plans": {
        "basic": {
            "DISK": "50G",
            "RAM": "2G",
            "CPU": "1",
            "price": 10.0
        },
        "standard": {
            "DISK": "100G",
            "RAM": "4G",
            "CPU": "2",
            "price": 20.0
        },
        "premium": {
            "DISK": "200G",
            "RAM": "8G",
            "CPU": "4",
            "price": 30.0
        }
    },
    "servers": [
        {
            "host": "78.46.62.242",
            "username": "root",
            "password": "imp6Vjj8s4z3Ui"
        }
    ],
    "rdp_ports": []
}

can you add some more design, functions, commands, features to bot?