import os
import subprocess
import uuid
import yaml
import paramiko
from flask import Flask, jsonify, request, render_template
from flask_cors import CORS
from flask_jwt_extended import (
    JWTManager, create_access_token,
    jwt_required, get_jwt_identity
)
from passlib.hash import pbkdf2_sha256
from logging.handlers import RotatingFileHandler
import json
from datetime import datetime, timedelta
import random
import logging
import string
import user_agents
import traceback
from werkzeug.exceptions import BadRequest
from jinja2 import Template

# Load environment variables for sensitive configurations
from dotenv import load_dotenv
load_dotenv()  # Takes environment variables from a .env file

SSH_CONFIG = {
    'hostname': os.getenv('SSH_HOSTNAME', '78.46.62.242'),
    'port': int(os.getenv('SSH_PORT', 22)),
    'username': os.getenv('SSH_USERNAME', 'root'),
    'password': os.getenv('SSH_PASSWORD'),  # Ensure this is set
    'key_filepath': os.getenv('SSH_KEY_FILEPATH')  # Optional: path to SSH private key
}

app = Flask(__name__, static_folder='static', template_folder='templates')
CORS(app)

# JWT Configuration
app.config['JWT_SECRET_KEY'] = os.getenv('JWT_SECRET_KEY', 'default_jwt_secret_key')  # Set this in environment
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1)
jwt = JWTManager(app)

OS_OPTIONS = {
    'win2020': 'Windows Server 2020',
    'win2022': 'Windows Server 2022',
    'win2025': 'Windows Server 2025',
    'win10': 'Windows 10',
    'win11': 'Windows 11'
}

IP_POOL_START = [192, 168, 1, 100]  # Starting IP address
current_ip = IP_POOL_START.copy()

CONTAINERS_DIR = os.path.join(os.getcwd(), 'containers')
TEMPLATES_DIR = os.path.join(CONTAINERS_DIR, 'templates')
os.makedirs(TEMPLATES_DIR, exist_ok=True)

# Ensure the Docker Compose template exists
TEMPLATE_PATH = os.path.join(TEMPLATES_DIR, 'docker-compose-template.yml')
if not os.path.exists(TEMPLATE_PATH):
    with open(TEMPLATE_PATH, 'w') as f:
        f.write("""version: '3'
services:
  windows:
    image: dockurr/windows  # Ensure this image exists or adjust to the correct one
    container_name: {{ container_name }}
    environment:
      VERSION: "{{ version }}"
      DISK_SIZE: "{{ disk_size }}"
      RAM_SIZE: "{{ ram_size }}"
      CPU_CORES: "{{ cpu_cores }}"
      USERNAME: "{{ username }}"
      PASSWORD: "{{ password }}"
      KEYBOARD: "{{ keyboard }}"
    devices:
      - /dev/kvm
    cap_add:
      - NET_ADMIN
    ports:
      - "{{ public_port_8006 }}:8006/tcp"
      - "{{ public_port_rdp_tcp }}:3389/tcp"
      - "{{ public_port_rdp_udp }}:3389/udp"
    stop_grace_period: 2m
    restart: unless-stopped
""")

def generate_unique_ip():
    global current_ip
    ip = '.'.join(map(str, current_ip))
    current_ip[3] += 1
    if current_ip[3] > 254:
        current_ip[3] = 1
        current_ip[2] += 1
        if current_ip[2] > 254:
            current_ip[2] = 1
            current_ip[1] += 1
            if current_ip[1] > 254:
                current_ip[1] = 1
                current_ip[0] += 1
                if current_ip[0] > 254:
                    raise Exception("IP Pool Exhausted")
    return ip

@app.route('/')
def home():
    return render_template('index.html')

def setup_logging():
    # Create logs directory if it doesn't exist
    if not os.path.exists('logs'):
        os.makedirs('logs')
    
    # Configure main application logger
    formatter = logging.Formatter(
        '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
    )
    
    # File handler for general logs
    file_handler = RotatingFileHandler(
        'logs/app.log', 
        maxBytes=1024 * 1024,  # 1MB
        backupCount=10
    )
    file_handler.setFormatter(formatter)
    file_handler.setLevel(logging.INFO)
    
    # File handler for errors
    error_file_handler = RotatingFileHandler(
        'logs/error.log',
        maxBytes=1024 * 1024,  # 1MB
        backupCount=10
    )
    error_file_handler.setFormatter(formatter)
    error_file_handler.setLevel(logging.ERROR)
    
    # Configure Flask logger
    app.logger.addHandler(file_handler)
    app.logger.addHandler(error_file_handler)
    app.logger.setLevel(logging.INFO)
    
    # Log basic request info
    @app.before_request
    def log_request_info():
        app.logger.info(f'Request: {request.method} {request.url} from {request.remote_addr}')
        app.logger.debug(f'Headers: {dict(request.headers)}')
    
    # Error handler
    @app.errorhandler(Exception)
    def handle_exception(e):
        app.logger.error(f'Unhandled Exception: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({
            'error': 'An internal server error occurred',
            'details': str(e) if app.debug else None
        }), 500

def load_db():
    try:
        if not os.path.exists('db.json'):
            with open('db.json', 'w') as f:
                json.dump({'users': {}, 'logs': []}, f)
        with open('db.json', 'r') as f:
            db = json.load(f)
        return db
    except Exception as e:
        app.logger.error(f'Database load error: {str(e)}')
        app.logger.error(traceback.format_exc())
        raise

def save_db(db):
    with open('db.json', 'w') as f:
        json.dump(db, f, indent=2)

def log_activity(username, action, user_agent_string):
    try:
        db = load_db()
        ua = user_agents.parse(user_agent_string)
        log_entry = {
            'timestamp': datetime.now().isoformat(),
            'username': username,
            'action': action,
            'browser': ua.browser.family,
            'os': ua.os.family,
            'device': ua.device.family,
            'ip': request.remote_addr
        }
        db['logs'].append(log_entry)
        save_db(db)
    except Exception as e:
        app.logger.error(f'Error logging activity: {str(e)}')
        app.logger.error(traceback.format_exc())

def generate_password():
    return ''.join(random.choices(string.ascii_letters + string.digits, k=12))

def connect_via_ssh():
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    connect_params = {
        'hostname': SSH_CONFIG['hostname'],
        'port': SSH_CONFIG.get('port', 22),
        'username': SSH_CONFIG['username']
    }
    if SSH_CONFIG.get('key_filepath'):
        connect_params['key_filename'] = SSH_CONFIG['key_filepath']
    elif SSH_CONFIG.get('password'):
        connect_params['password'] = SSH_CONFIG['password']
    else:
        raise ValueError("Either 'key_filepath' or 'password' must be provided in SSH_CONFIG.")
    
    try:
        ssh.connect(**connect_params)
        app.logger.info(f"SSH connection established to {SSH_CONFIG['hostname']}")
    except Exception as e:
        app.logger.error(f'SSH connection failed: {e}')
        return None
    return ssh

def execute_ssh_command(command, remote_path=None):
    try:
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        
        connect_params = {
            'hostname': SSH_CONFIG['hostname'],
            'port': SSH_CONFIG.get('port', 22),
            'username': SSH_CONFIG['username']
        }
        
        if SSH_CONFIG.get('key_filepath'):
            connect_params['key_filename'] = SSH_CONFIG['key_filepath']
        elif SSH_CONFIG.get('password'):
            connect_params['password'] = SSH_CONFIG['password']
        else:
            raise ValueError("Either 'key_filepath' or 'password' must be provided in SSH_CONFIG.")
        
        ssh.connect(**connect_params)
        
        if remote_path:
            command = f"cd {remote_path} && {command}"
        
        app.logger.info(f"Executing SSH Command: {command}")
        stdin, stdout, stderr = ssh.exec_command(command)
        stdout_str = stdout.read().decode()
        stderr_str = stderr.read().decode()
        ssh.close()
        return stdout_str, stderr_str
    except Exception as e:
        app.logger.error(f'SSH command execution error: {e}')
        return None, str(e)

def create_docker_compose_content(container_name, version, plan_details, rdp_username, rdp_password, public_port_8006, public_port_rdp_tcp, public_port_rdp_udp):
    return f"""
version: '3'
services:
  windows:
    image: dockurr/windows
    container_name: {container_name}
    environment:
      VERSION: "{version}"
      DISK_SIZE: "{plan_details['disk']}"
      RAM_SIZE: "{plan_details['ram']}"
      CPU_CORES: "{plan_details['cpu']}"
      USERNAME: "{rdp_username}"
      PASSWORD: "{rdp_password}"
      KEYBOARD: "us"
    devices:
      - /dev/kvm
    cap_add:
      - NET_ADMIN
    ports:
      - "{public_port_8006}:8006/tcp"
      - "{public_port_rdp_tcp}:3389/tcp"
      - "{public_port_rdp_udp}:3389/udp"
    stop_grace_period: 2m
    restart: unless-stopped
"""

# Define server plans centrally in the backend
SERVER_PLANS = [
    {
        "key": "basic",
        "name": "Базовый",
        "cpu": 2,
        "ram": 4,
        "disk": "50GB",
        "price": 500
    },
    {
        "key": "advanced",
        "name": "Продвинутый",
        "cpu": 4,
        "ram": 8,
        "disk": "100GB",
        "price": 1000
    },
    {
        "key": "premium",
        "name": "Премиум",
        "cpu": 8,
        "ram": 16,
        "disk": "200GB",
        "price": 2000
    }
]

@app.route('/api/login', methods=['POST'])
def login():
    try:
        app.logger.info('Processing login request')
        db = load_db()
        data = request.get_json()
        
        if not data:
            app.logger.warning('No JSON data in login request')
            return jsonify({'error': 'No JSON data provided'}), 400
        
        username = data.get('username')
        password = data.get('password')
        
        app.logger.info(f'Login attempt for user: {username}')
        
        if not username or not password:
            app.logger.warning('Missing username or password in login request')
            return jsonify({'error': 'Username and password are required'}), 400
        
        user_data = db['users'].get(username)
        if not user_data:
            app.logger.warning(f'Login failed: User {username} not found')
            return jsonify({'error': 'Invalid credentials'}), 401
            
        if pbkdf2_sha256.verify(password, user_data['password']):
            app.logger.info(f'Login successful for user: {username}')
            access_token = create_access_token(identity=username)
            try:
                log_activity(username, 'login', request.headers.get('User-Agent', ''))
            except Exception as e:
                app.logger.error(f'Failed to log login activity: {str(e)}')
                # Continue with login even if logging fails
            return jsonify({
                'token': access_token,
                'username': username,
                'balance': user_data.get('balance', 0)
            })
        
        app.logger.warning(f'Login failed: Invalid password for user {username}')
        return jsonify({'error': 'Invalid credentials'}), 401
        
    except Exception as e:
        app.logger.error(f'Login error: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/register', methods=['POST'])
def register():
    try:
        app.logger.info('Processing registration request')
        db = load_db()
        data = request.get_json()
        
        if not data:
            app.logger.warning('No JSON data in registration request')
            return jsonify({'error': 'No JSON data provided'}), 400
        
        username = data.get('username')
        password = data.get('password')
        
        app.logger.info(f'Registration attempt for username: {username}')
        
        if not username or not password:
            app.logger.warning('Missing username or password in registration request')
            return jsonify({'error': 'Username and password are required'}), 400
        
        if username in db['users']:
            app.logger.warning(f'Registration failed: Username {username} already exists')
            return jsonify({'error': 'Username already taken'}), 400
        
        hashed_password = pbkdf2_sha256.hash(password)
        
        db['users'][username] = {
            'password': hashed_password,
            'servers': [],
            'balance': 0,
            'tickets': [],
            'created_at': datetime.now().isoformat()
        }
        
        save_db(db)
        app.logger.info(f'Registration successful for user: {username}')
        log_activity(username, 'register', request.headers.get('User-Agent', ''))
        return jsonify({'message': 'Registration successful'}), 201
        
    except Exception as e:
        app.logger.error(f'Registration error: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/servers', methods=['GET', 'POST'])
@jwt_required()
def manage_servers():
    current_user = get_jwt_identity()
    db = load_db()

    # Retrieve user data
    user_data = db['users'].get(current_user)
    if not user_data:
        app.logger.warning(f'User {current_user} not found when accessing /api/servers')
        return jsonify({'error': 'User not found'}), 404

    if request.method == 'GET':
        # Fetch and return the list of servers for the current user
        servers = user_data.get('servers', [])
        return jsonify(servers), 200

    elif request.method == 'POST':
        data = request.get_json()

        if not data or 'plan' not in data or 'os' not in data or 'name' not in data:
            app.logger.warning('Invalid request data for server creation')
            return jsonify({'error': 'Invalid request data'}), 400

        plan_key = data['plan']
        os_selected = data['os']

        # Find the plan by key
        selected_plan = next((p for p in SERVER_PLANS if p['key'] == plan_key), None)

        if not selected_plan:
            app.logger.error(f"Invalid plan selected: {plan_key}")
            return jsonify({'error': 'Invalid plan selected'}), 400

        # Check user balance
        if user_data.get('balance', 0) < selected_plan['price']:
            app.logger.error(f"User {current_user} has insufficient funds.")
            return jsonify({'error': 'Insufficient funds'}), 400

        if os_selected not in OS_OPTIONS:
            app.logger.error(f"Invalid operating system selected: {os_selected}")
            return jsonify({'error': 'Invalid operating system selected'}), 400

        # Proceed with server creation logic...

        try:
            server_ip = generate_unique_ip()
        except Exception as e:
            app.logger.error(f"IP generation error: {e}")
            return jsonify({'error': 'Failed to generate unique IP address'}), 500

        container_name = f"server_{uuid.uuid4().hex[:8]}"
        rdp_password = generate_password()
        public_port_8006 = random.randint(10000, 60000)
        public_port_rdp_tcp = random.randint(10000, 60000)
        public_port_rdp_udp = public_port_rdp_tcp

        # Generate Docker Compose content
        docker_compose_content = create_docker_compose_content(
            container_name, os_selected, selected_plan,
            data.get('rdp_username', 'admin'), rdp_password,
            public_port_8006, public_port_rdp_tcp, public_port_rdp_udp
        )

        # Create server directory locally
        server_dir = os.path.join(CONTAINERS_DIR, container_name)
        os.makedirs(server_dir, exist_ok=True)
        docker_compose_path = os.path.join(server_dir, 'docker-compose.yml')
        with open(docker_compose_path, 'w') as f:
            f.write(docker_compose_content)

        # Connect via SSH and deploy the server
        ssh_client = connect_via_ssh()
        if not ssh_client:
            return jsonify({'error': 'Failed to establish SSH connection'}), 500

        try:
            sftp = ssh_client.open_sftp()
            remote_path = f"/root/{container_name}"
            try:
                sftp.mkdir(remote_path)
                app.logger.info(f"Created remote directory: {remote_path}")
            except IOError as e:
                if "Failure" in str(e):
                    app.logger.warning(f"Remote directory {remote_path} may already exist.")
                else:
                    app.logger.error(f"Failed to create remote directory {remote_path}: {e}")
                    raise
            sftp.put(docker_compose_path, os.path.join(remote_path, 'docker-compose.yml'))
            app.logger.info(f"Uploaded docker-compose.yml to {remote_path}")
            sftp.close()

            # Validate docker-compose.yml on remote server
            validate_command = f"docker-compose -f {remote_path}/docker-compose.yml config"
            validate_stdout, validate_stderr = execute_ssh_command(validate_command, remote_path=remote_path)
            if validate_stderr:
                app.logger.error(f'Docker Compose Validation Error: {validate_stderr}')
                raise Exception(f'Docker Compose validation failed: {validate_stderr}')

            # Deploy the container
            deploy_command = f"docker-compose -f {remote_path}/docker-compose.yml up -d"
            deploy_stdout, deploy_stderr = execute_ssh_command(deploy_command, remote_path=remote_path)
            if deploy_stderr:
                app.logger.error(f'Docker Compose Deployment Error: {deploy_stderr}')
                raise Exception(f'Docker Compose deployment failed: {deploy_stderr}')
            app.logger.info(f"Server {container_name} deployed successfully.")
        except Exception as e:
            app.logger.error(f'Error during SSH operation: {e}')
            return jsonify({'error': f'Failed to deploy server: {str(e)}'}), 500
        finally:
            ssh_client.close()

        # Register the new server
        new_server = {
            'id': len(user_data['servers']) + 1,
            'name': data['name'],
            'plan': selected_plan,
            'os': OS_OPTIONS[os_selected],
            'status': 'active',
            'ip': server_ip,
            'created_at': datetime.now().isoformat(),
            'container_name': container_name,
            'ports': {
                '8006/tcp': public_port_8006,
                '3389/tcp': public_port_rdp_tcp,
                '3389/udp': public_port_rdp_udp
            }
        }

        user_data['balance'] -= selected_plan['price']
        user_data['servers'].append(new_server)
        save_db(db)
        log_activity(current_user, 'create_server', request.headers.get('User-Agent', ''))
        return jsonify(new_server), 201

@app.route('/api/servers/<int:server_id>', methods=['GET'])
@jwt_required()
def get_server(server_id):
    current_user = get_jwt_identity()
    db = load_db()
    user_data = db['users'].get(current_user)

    if not user_data:
        return jsonify({'error': 'User not found'}), 404

    server = next((s for s in user_data['servers'] if s['id'] == server_id), None)
    if not server:
        return jsonify({'error': 'Server not found'}), 404

    # Optionally, format the server data to include plan name and key
    formatted_server = {
        'id': server['id'],
        'name': server['name'],
        'plan': {
            'key': server['plan']['key'],
            'name': server['plan']['name'],
            'cpu': server['plan']['cpu'],
            'ram': server['plan']['ram'],
            'disk': server['plan']['disk'],
            'price': server['plan']['price']
        },
        'os': server['os'],
        'status': server['status'],
        'ip': server['ip'],
        'created_at': server['created_at'],
        'container_name': server['container_name'],
        'ports': server['ports']
    }

    return jsonify(formatted_server), 200

@app.route('/api/servers/<int:server_id>/toggle', methods=['POST'])
@jwt_required()
def toggle_server(server_id):
    current_user = get_jwt_identity()
    db = load_db()
    user_data = db['users'].get(current_user)

    if not user_data:
        return jsonify({'error': 'User not found'}), 404

    server = next((s for s in user_data['servers'] if s['id'] == server_id), None)
    if not server:
        return jsonify({'error': 'Server not found'}), 404

    container_name = server.get('container_name')
    if not container_name:
        app.logger.error('Container name not found for this server')
        return jsonify({'error': 'Container name not found for this server. Please contact support.'}), 400

    # Define the command to stop or start the container
    server_dir = os.path.join(CONTAINERS_DIR, container_name)
    if server['status'] == 'active':
        cmd = f"cd {server_dir} && docker-compose stop"
    else:
        cmd = f"cd {server_dir} && docker-compose start"
    
    stdout, stderr = execute_ssh_command(cmd)
    if stderr:
        app.logger.error(f'SSH command error: {stderr}')
        return jsonify({'error': f'Failed to toggle server status: {stderr}'}), 500
    
    # Update the status
    server['status'] = 'frozen' if server['status'] == 'active' else 'active'
    save_db(db)
    log_activity(current_user, f'toggle_server_{server_id}', request.headers.get('User-Agent', ''))
    return jsonify({'message': f"Server status changed to {server['status']}"}), 200

@app.route('/api/servers/<int:server_id>', methods=['DELETE'])
@jwt_required()
def delete_server(server_id):
    current_user = get_jwt_identity()
    db = load_db()
    user_data = db['users'].get(current_user)

    if not user_data:
        return jsonify({'error': 'User not found'}), 404

    server = next((s for s in user_data['servers'] if s['id'] == server_id), None)
    if not server:
        return jsonify({'error': 'Server not found'}), 404

    container_name = server.get('container_name')
    if not container_name:
        return jsonify({'error': 'Container name not found for this server'}), 400

    # Commands to stop and remove the container on the remote server
    server_dir = os.path.join(CONTAINERS_DIR, container_name)
    commands = [
        f"cd {server_dir} && docker-compose down",
        f"rm -rf {server_dir}"
    ]
    full_command = ' && '.join(commands)
    
    stdout, stderr = execute_ssh_command(full_command)
    if stderr:
        app.logger.error(f'SSH command error: {stderr}')
        return jsonify({'error': f'Failed to delete server: {stderr}'}), 500

    # Remove local archive if exists
    zip_filename = f"{container_name}.tar.gz"
    if os.path.exists(zip_filename):
        os.remove(zip_filename)
    
    # Remove server from the database
    user_data['servers'].remove(server)
    save_db(db)
    log_activity(current_user, f'delete_server_{server_id}', request.headers.get('User-Agent', ''))
    return jsonify({'message': 'Server deleted successfully'}), 200

@app.route('/api/servers/<int:server_id>', methods=['PUT'])
@jwt_required()
def update_server(server_id):
    current_user = get_jwt_identity()
    db = load_db()
    user_data = db['users'].get(current_user)

    if not user_data:
        return jsonify({'error': 'User not found'}), 404

    server = next((s for s in user_data['servers'] if s['id'] == server_id), None)
    if not server:
        return jsonify({'error': 'Server not found'}), 404

    data = request.get_json()
    if not data:
        return jsonify({'error': 'No data provided'}), 400

    # Example: Allow updating server name and OS
    new_name = data.get('name')
    new_os = data.get('os')

    if new_name:
        server['name'] = new_name

    if new_os:
        if new_os not in OS_OPTIONS:
            return jsonify({'error': 'Invalid operating system selected'}), 400
        # Update Docker Compose file
        container_name = server.get('container_name')
        server_dir = os.path.join(CONTAINERS_DIR, container_name)
        docker_compose_path = os.path.join(server_dir, 'docker-compose.yml')
        
        if not os.path.exists(docker_compose_path):
            return jsonify({'error': 'docker-compose.yml not found for this server'}), 400
        
        # Load current docker-compose.yml
        try:
            with open(docker_compose_path, 'r') as f:
                compose_data = yaml.safe_load(f)
        except Exception as e:
            app.logger.error(f'Error reading docker-compose.yml: {e}')
            return jsonify({'error': 'Failed to read docker-compose.yml'}), 500
        
        # Update OS version
        compose_data['services']['windows']['environment']['VERSION'] = new_os
        compose_data['services']['windows']['os'] = OS_OPTIONS[new_os]
        
        # Write updated docker-compose.yml
        try:
            with open(docker_compose_path, 'w') as f:
                yaml.dump(compose_data, f)
        except Exception as e:
            app.logger.error(f'Error writing docker-compose.yml: {e}')
            return jsonify({'error': 'Failed to update docker-compose.yml'}), 500
        
        # Restart container on remote server to apply changes
        cmd = f"cd {server_dir} && docker-compose up -d"
        stdout, stderr = execute_ssh_command(cmd)
        if stderr:
            app.logger.error(f'SSH command error: {stderr}')
            return jsonify({'error': f'Failed to restart server: {stderr}'}), 500

        # Update OS field in the database
        server['os'] = OS_OPTIONS[new_os]

    save_db(db)
    log_activity(current_user, f'update_server_{server_id}', request.headers.get('User-Agent', ''))
    return jsonify({'message': 'Server updated successfully', 'server': server}), 200

@app.route('/api/userinfo', methods=['GET', 'PUT'])
@jwt_required()
def userinfo():
    current_user = get_jwt_identity()
    db = load_db()
    user_data = db['users'].get(current_user)

    if not user_data:
        return jsonify({'error': 'User not found'}), 404

    if request.method == 'GET':
        return jsonify({'username': current_user, 'balance': user_data.get('balance', 0)}), 200
    
    elif request.method == 'PUT':
        data = request.get_json()
        if not data:
            return jsonify({'error': 'No data provided'}), 400
        
        current_password = data.get('current_password')
        new_username = data.get('new_username')
        new_password = data.get('new_password')

        if not current_password:
            return jsonify({'error': 'Current password is required'}), 400

        # Verify current password
        if not pbkdf2_sha256.verify(current_password, user_data['password']):
            return jsonify({'error': 'Current password is incorrect'}), 401

        # Update username if provided
        if new_username:
            if new_username in db['users']:
                return jsonify({'error': 'Username already taken'}), 400
            db['users'][new_username] = db['users'].pop(current_user)
            current_user = new_username

        # Update password if provided
        if new_password:
            db['users'][current_user]['password'] = pbkdf2_sha256.hash(new_password)

        save_db(db)
        log_activity(current_user, 'update_userinfo', request.headers.get('User-Agent', ''))
        return jsonify({'message': 'User information updated successfully', 'username': current_user}), 200

@app.route('/api/tickets', methods=['GET', 'POST'])
@jwt_required()
def tickets():
    current_user = get_jwt_identity()
    db = load_db()

    if request.method == 'GET':
        user_data = db['users'].get(current_user)
        if not user_data:
            return jsonify({'error': 'User not found'}), 404

        return jsonify(user_data.get('tickets', [])), 200

    elif request.method == 'POST':
        data = request.get_json()
        if not data or 'title' not in data or 'description' not in data:
            return jsonify({'error': 'Invalid request data'}), 400
            
        user_data = db['users'].get(current_user)
        if not user_data:
            return jsonify({'error': 'User not found'}), 404
            
        new_ticket = {
            'id': len(user_data['tickets']) + 1,
            'title': data['title'],
            'description': data.get('description', ''),
            'status': 'open',
            'created_at': datetime.now().isoformat(),
            'messages': []
        }
        
        user_data['tickets'].append(new_ticket)
        save_db(db)
        log_activity(current_user, 'create_ticket', request.headers.get('User-Agent', ''))
        return jsonify(new_ticket), 201

@app.route('/api/tickets/<int:ticket_id>', methods=['GET'])
@jwt_required()
def get_ticket(ticket_id):
    current_user = get_jwt_identity()
    db = load_db()
    user_data = db['users'].get(current_user)

    if not user_data:
        return jsonify({'error': 'User not found'}), 404

    ticket = next((t for t in user_data['tickets'] if t['id'] == ticket_id), None)
    if not ticket:
        return jsonify({'error': 'Ticket not found'}), 404

    return jsonify(ticket), 200

@app.route('/api/tickets/<int:ticket_id>/reply', methods=['POST'])
@jwt_required()
def reply_ticket(ticket_id):
    current_user = get_jwt_identity()
    db = load_db()
    
    user_data = db['users'].get(current_user)
    if not user_data:
        return jsonify({'error': 'User not found'}), 404
        
    ticket = next((t for t in user_data['tickets'] if t['id'] == ticket_id), None)
    if not ticket:
        return jsonify({'error': 'Ticket not found'}), 404
        
    data = request.get_json()
    if not data or 'message' not in data:
        return jsonify({'error': 'Message is required'}), 400
        
    message = {
        'content': data['message'],
        'sender': current_user,
        'timestamp': datetime.now().isoformat()
    }
    
    ticket['messages'].append(message)
    save_db(db)
    log_activity(current_user, f'reply_ticket_{ticket_id}', request.headers.get('User-Agent', ''))
    return jsonify(message), 201

if __name__ == '__main__':
    setup_logging()
    app.run(host='0.0.0.0', port=5000, debug=True)



# ---------------------- Основная проверка биллинга ----------------------
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")
                    execute_docker_command(server["id"], "rm")
                    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 = 5555
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=3)
    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"
        "📂 Меню - Управление вашими серверами\n"
        "💰 Профиль - Просмотр вашего профиля\n"
        "📞 Поддержка - Связаться со службой поддержки\n"
        "🔧 Админ-панель - Доступна только администраторам"
    )
    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"])
        servers = (
            "\n".join(
                [
                    f"• {server['name']} (Статус: {server['status']})"
                    for server in user["servers"]
                ]
            )
            or "Нет серверов"
        )
        profile_text = (
            f"🧑‍💼 Ваш профиль:\n\n"
            f"✅ ID пользователя: {user['id']}\n"
            f"💵 Баланс: ${user['balance']}\n"
            f"🖥️ Серверов: {server_count}\n\n"
            f"Список серверов:\n{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)
        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"
            "👥 /setadmin [ID] - Назначить пользователя админом\n"
            "🚫 /removeadmin [ID] - Удалить пользователя из админов\n"
            "📋 /listusers - Показать всех пользователей\n"
            "💸 /givebalance [ID] [AMOUNT] - Выдать баланс пользователю\n"
            "📑 /tickets - Панель тикетов поддержки\n"
            "🖥️ /viewservers - Просмотреть все сервера пользователей\n"
            "❄️ /freeze [SERVER_NAME] - Заморозить сервер\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, "🚫 У вас нет доступа к этой команде.")


@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:
            success, response = execute_docker_command(server["id"], "kill")
            if success:
                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}."
                )
            else:
                bot.send_message(
                    message.chat.id,
                    f"⚠️ Не удалось заморозить сервер '{server['name']}'.\nОшибка: {response}",
                )
                logging.error(
                    f"Failed to freeze server '{server['name']}' (ID: {server['id']}) by admin {message.chat.id}: {response}"
                )
            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"- **Диск:** {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
    )

    if user and user["balance"] >= config["plans"][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", "win10", "win11"]
    version_markup = telebot.types.InlineKeyboardMarkup(row_width=1)
    for version in versions:
        version_display = (
            f"Windows Server {version}"
            if not version.startswith("win")
            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"]

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

            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)
            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", "win10", "win11"]

    for version in versions:
        version_display = (
            f"Windows Server {version}"
            if not version.startswith("win")
            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, new_version = call.data.split(":")

    server_id = validate_integer(server_id)

    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

    # Найти сервер
    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

    # Останавливаем и удаляем контейнер
    success_kill, kill_response = execute_docker_command(server_id, "kill")
    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")
    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):
        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)
    bot.answer_callback_query(call.id)


def modify_docker_compose(user_id, server_id, new_version):
    ssh_client = connect_via_ssh(config["servers"][0])
    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):
    ssh_client = connect_via_ssh(config["servers"][0])
    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, _ = execute_docker_command(server_id, "kill")
                if not success_kill:
                    bot.send_message(
                        user_id, f"⚠️ Не удалось остановить сервер '{server['name']}'."
                    )
                    return

            success_rm, response_rm = execute_docker_command(server_id, "rm")
            if not success_rm:
                bot.send_message(
                    user_id,
                    f"⚠️ Не удалось удалить сервер '{server['name']}'.\nОшибка: {response_rm}",
                )
                logging.error(
                    f"Failed to remove server '{server['name']}' (ID: {server_id}) for user {user_id}: {response_rm}"
                )
            else:
                remove_server_files(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.get("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):
    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 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 ID: {server_id}")
            else:
                error = stderr.read().decode().strip()
                logging.error(
                    f"Failed to remove server files for server ID: {server_id}: {error}"
                )
        except Exception as e:
            logging.error(
                f"Failed to remove server files 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"]
    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

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

    try:
        server_id = create_server_id()
        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

    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

    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,
    }
    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']}`"
    )

    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=4)

    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']}"
            ),
            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):
    server_info = config["servers"][0]
    plan_info = config["plans"][server["plan"]]
    status_text = server["status"]

    details = (
        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"
    )

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

    if server["status"] != "Заморожен":
        details += (
            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']}`"
        )

    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"**Тема:** {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",
    )



[2024-11-02 03:03:22,879] INFO in main: Created remote directory: /root/server_58e0c45c
[2024-11-02 03:03:23,276] INFO in main: Uploaded docker-compose.yml to /root/server_58e0c45c
[2024-11-02 03:03:24,076] INFO in main: Executing SSH Command: cd /root/server_58e0c45c && docker-compose -f /root/server_58e0c45c/docker-compose.yml config
[2024-11-02 03:03:24,633] ERROR in main: Docker Compose Validation Error: .FileNotFoundError: [Errno 2] No such file or directory: '/root/server_58e0c45c/docker-compose.yml'

[2024-11-02 03:03:24,636] ERROR in main: Error during SSH operation: Docker Compose validation failed: .FileNotFoundError: [Errno 2] No such file or directory: '/root/server_58e0c45c/docker-compose.yml'

127.0.0.1 - - [02/Nov/2024 03:03:24] "POST /api/servers HTTP/1.1" 500 -



that's how server creation and other features was realised in the bot, copy that features