import os
import json
import traceback
import string
import random
from datetime import datetime, timedelta
from functools import wraps

import paramiko
from flask import Flask, jsonify, request, render_template, send_from_directory
from flask_cors import CORS
from flask_jwt_extended import (
    JWTManager, create_access_token,
    jwt_required, get_jwt_identity,
    verify_jwt_in_request
)
from passlib.hash import pbkdf2_sha256
from logging.handlers import RotatingFileHandler
from werkzeug.exceptions import HTTPException, NotFound
from apscheduler.schedulers.background import BackgroundScheduler
from jinja2 import Template
import posixpath
import logging
import user_agents

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

# SSH Configuration
SSH_CONFIG = {
    'hostname': os.getenv('SSH_HOSTNAME', 'your_server_ip'),  # Replace with your server IP or hostname
    'port': int(os.getenv('SSH_PORT', 22)),
    'username': os.getenv('SSH_USERNAME', 'your_ssh_username'),
    'password': os.getenv('SSH_PASSWORD'),  # Ensure this is set
    'key_filepath': os.getenv('SSH_KEY_FILEPATH')  # Optional: path to SSH private key
}

# Load the server IP from the environment variable
SERVER_IP = os.getenv('SERVER_IP', 'your_server_ip')  # Replace with your server IP

app = Flask(
    __name__,
    static_folder='static',
    static_url_path='/static',  # Ensure static files are served from /static
    template_folder='templates'
)
CORS(app)

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

# Server Plans
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
    }
]

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

# Define Remote Containers Directory
REMOTE_CONTAINERS_DIR = "/root"

# Define Remote Containers Directory
REMOTE_CONTAINERS_DIR = "/root"  # Base directory on the remote server

# Add the missing get_remote_server_dir function
def get_remote_server_dir(container_name):
    """
    Returns the remote directory path for the given container_name.
    """
    return posixpath.join(REMOTE_CONTAINERS_DIR, container_name)

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

# Log directories (local)
LOGS_DIR = 'logs'
if not os.path.exists(LOGS_DIR):
    os.makedirs(LOGS_DIR)

# Configure Logging
def setup_logging():
    formatter = logging.Formatter(
        '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
    )
    file_handler = RotatingFileHandler(
        os.path.join(LOGS_DIR, 'app.log'),
        maxBytes=1024 * 1024,
        backupCount=10
    )
    file_handler.setFormatter(formatter)
    file_handler.setLevel(logging.INFO)

    error_file_handler = RotatingFileHandler(
        os.path.join(LOGS_DIR, 'error.log'),
        maxBytes=1024 * 1024,
        backupCount=10
    )
    error_file_handler.setFormatter(formatter)
    error_file_handler.setLevel(logging.ERROR)

    app.logger.addHandler(file_handler)
    app.logger.addHandler(error_file_handler)
    app.logger.setLevel(logging.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)}')

setup_logging()

# Error Handlers
@app.errorhandler(NotFound)
def handle_404(e):
    app.logger.error(f'404 Error: {request.url} not found.')
    return render_template('404.html'), 404

@app.errorhandler(Exception)
def handle_exception(e):
    if isinstance(e, HTTPException):
        return e
    app.logger.error(f'Unhandled Exception: {str(e)}')
    app.logger.error(traceback.format_exc())
    return jsonify({'error': 'An internal server error occurred'}), 500

# Database Functions
def load_db():
    try:
        db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'db.json')
        if not os.path.exists(db_path):
            with open(db_path, 'w', encoding='utf-8') as f:
                json.dump({'users': {}, 'logs': []}, f, indent=2)
            app.logger.info('Initialized new db.json with default structure.')

        with open(db_path, 'r', encoding='utf-8') as f:
            content = f.read().strip()
            app.logger.debug(f'Content of db.json: {content}')  # Debug log
            if not content:
                app.logger.warning('db.json is empty. Initializing with default structure.')
                db = {'users': {}, 'logs': []}
            else:
                db = json.loads(content)

        db.setdefault('logs', [])
        db.setdefault('users', {})

        # Clean and validate user data
        cleaned_users = {}
        for username, user in db['users'].items():
            if isinstance(user, dict):
                user.setdefault('role', 'user')
                cleaned_users[username] = user
            else:
                app.logger.warning(f"Skipping invalid user entry: {username} is not a dictionary.")

        db['users'] = cleaned_users

        save_db(db)
        return db
    except json.JSONDecodeError as e:
        app.logger.error(f'JSON Decode Error in load_db: {e}')
        raise
    except Exception as e:
        app.logger.error(f'Database load error: {str(e)}')
        app.logger.error(traceback.format_exc())
        raise

def save_db(db):
    db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'db.json')
    with open(db_path, 'w', encoding='utf-8') as f:
        json.dump(db, f, indent=2, ensure_ascii=False)

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(length=12):
    return ''.join(random.choices(string.ascii_letters + string.digits, k=length))

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, ssh_client, remote_path=None):
    try:
        if remote_path:
            command = f"cd {remote_path} && {command}"

        app.logger.info(f"Executing SSH Command: {command}")
        stdin, stdout, stderr = ssh_client.exec_command(command)
        exit_status = stdout.channel.recv_exit_status()
        stdout_str = stdout.read().decode().strip()
        stderr_str = stderr.read().decode().strip()
        return stdout_str, stderr_str, exit_status
    except Exception as e:
        app.logger.error(f'SSH command execution error: {e}')
        return None, str(e), -1

def create_docker_compose_content(container_name, version, plan_details, rdp_username, rdp_password, vnc_password, public_port_8006, public_port_rdp_tcp, public_port_rdp_udp, public_port_vnc):
    docker_compose_template = """
version: '3'
services:
  windows:
    image: dockurr/windows
    container_name: {{ container_name }}
    environment:
      VERSION: "{{ version }}"
      DISK_SIZE: "{{ disk_size }}"
      RAM_SIZE: "{{ ram_size }}"
      CPU_CORES: "{{ cpu_cores }}"
      USERNAME: "{{ username }}"
      PASSWORD: "{{ password }}"
      VNC_PASSWORD: "{{ vnc_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"
      - "{{ public_port_vnc }}:5900/tcp"
    stop_grace_period: 2m
    restart: unless-stopped
    """
    template = Template(docker_compose_template)
    return template.render(
        container_name=container_name,
        version=version,
        disk_size=plan_details['disk'],
        ram_size=plan_details['ram'],
        cpu_cores=plan_details['cpu'],
        username=rdp_username,
        password=rdp_password,
        vnc_password=vnc_password,
        public_port_8006=public_port_8006,
        public_port_rdp_tcp=public_port_rdp_tcp,
        public_port_rdp_udp=public_port_rdp_udp,
        public_port_vnc=public_port_vnc
    )

def is_valid_username(username):
    import re
    pattern = re.compile(r'^[A-Za-z0-9_]{3,30}$')
    return bool(pattern.match(username))

# Admin Decorator
def admin_required(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        verify_jwt_in_request()
        current_user = get_jwt_identity()
        db = load_db()
        user_data = db['users'].get(current_user)
        if not user_data or user_data.get('role') != 'admin':
            app.logger.warning(f'Unauthorized admin access attempt by user: {current_user}')
            return jsonify({'error': 'Admin access required'}), 403
        return fn(*args, **kwargs)
    return wrapper

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

@app.route('/admin')
def admin_page():
    return render_template('admin.html')

# Serve favicon
@app.route('/favicon.ico')
def favicon():
    return send_from_directory(os.path.join(app.root_path, 'static'),
                               'favicon.ico', mimetype='image/vnd.microsoft.icon')

@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

        # Only use the password verification method
        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)}')
            return jsonify({
                'token': access_token,
                'username': username,
                'balance': user_data.get('balance', 0),
                'role': user_data.get('role', 'user')  # Include role in response
            }), 200

        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 not is_valid_username(username):
            app.logger.warning(f'Invalid username attempted: {username}')
            return jsonify({'error': 'Invalid username. Please use only letters, numbers, and underscores. Length between 3-30 characters.'}), 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': [],
            'logs': [],
            'created_at': datetime.now().isoformat(),
            'role': 'user'  # Assign 'user' role by default
        }

        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()

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

        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

        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

        # Check if a server with the same name exists
        existing_server = next((s for s in user_data['servers'] if s['name'] == data['name']), None)
        if existing_server:
            return jsonify({'error': 'Server with this name already exists'}), 400

        # Generate a unique server ID
        new_server_id = max([s['id'] for s in user_data['servers']], default=0) + 1
        container_name = f"server_{new_server_id}"

        rdp_password = generate_password()
        vnc_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
        public_port_vnc = random.randint(5900, 6000)

        docker_compose_content = create_docker_compose_content(
            container_name, os_selected, selected_plan,
            data.get('rdp_username', 'admin'), rdp_password,
            vnc_password,  # Pass VNC password
            public_port_8006, public_port_rdp_tcp, public_port_rdp_udp,
            public_port_vnc  # Pass VNC port
        )

        ssh_client = connect_via_ssh()
        if not ssh_client:
            return jsonify({'error': 'Failed to establish SSH connection'}), 500

        remote_path = get_remote_server_dir(container_name)

        try:
            mkdir_command = f"mkdir -p {remote_path}"
            app.logger.info(f"Creating remote directory: {remote_path}")
            stdout_str, stderr_str, exit_status = execute_ssh_command(mkdir_command, ssh_client)
            if exit_status != 0:
                app.logger.error(f"Failed to create remote directory {remote_path}: {stderr_str}")
                raise Exception(f"Failed to create remote directory: {stderr_str}")
            app.logger.info(f"Remote directory created: {remote_path}")

            docker_compose_command = f"""cat <<EOF > {remote_path}/docker-compose.yml
{docker_compose_content}
EOF
"""
            app.logger.info(f"Creating docker-compose.yml on remote server for {container_name}")
            stdout_str, stderr_str, exit_status = execute_ssh_command(docker_compose_command, ssh_client)
            if exit_status != 0:
                app.logger.error(f"Error creating docker-compose.yml on remote server: {stderr_str}")
                raise Exception(f"Failed to create docker-compose.yml: {stderr_str}")
            app.logger.info(f"docker-compose.yml created successfully at {remote_path}")

            validate_command = f"docker-compose -f {remote_path}/docker-compose.yml config"
            validate_stdout, validate_stderr, validate_exit_status = execute_ssh_command(validate_command, ssh_client)
            if validate_exit_status != 0:
                app.logger.error(f'Docker Compose Validation Error: {validate_stderr}')
                raise Exception(f'Docker Compose validation failed: {validate_stderr}')
            else:
                app.logger.info("Docker Compose configuration is valid.")

            deploy_command = f"docker-compose -f {remote_path}/docker-compose.yml up -d"
            app.logger.info(f"Deploying server {container_name} on remote server")
            stdout_str, stderr_str, deploy_exit_status = execute_ssh_command(deploy_command, ssh_client)
            if deploy_exit_status != 0:
                app.logger.error(f'Docker Compose Deployment Error: {stderr_str}')
                raise Exception(f'Docker Compose deployment failed: {stderr_str}')
            else:
                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()

        new_server = {
    'id': new_server_id,
    'name': data['name'],
    'plan': {
        'key': selected_plan['key'],
        'name': selected_plan['name'],
        'cpu': selected_plan['cpu'],
        'ram': selected_plan['ram'],
        'disk': selected_plan['disk'],
        'price': selected_plan['price']
    },
    'os': OS_OPTIONS[os_selected],
    'status': 'active',
    '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,
        '5900/tcp': public_port_vnc
    },
    'vnc_password': vnc_password,
    'vnc_address': f"http://{SERVER_IP}:{public_port_vnc}",  # Assuming SERVER_IP is the public IP
    'ip': SERVER_IP  # Add this line
}

        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

def monthly_billing_check():
    app.logger.info("Checking for monthly billing...")
    try:
        db = load_db()
        for username, user in db['users'].items():
            for server in user.get('servers', []):
                if server.get("next_payment_due"):
                    due_date = datetime.fromisoformat(server["next_payment_due"])
                    if datetime.now() >= due_date:
                        handle_billing_cycle(user, server)
        save_db(db)
    except Exception as e:
        app.logger.error(f"Error during monthly billing check: {e}")
        app.logger.error(traceback.format_exc())

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

    if user.get("balance", 0) >= plan_price:
        user["balance"] -= plan_price
        server["next_payment_due"] = (datetime.now() + timedelta(days=30)).isoformat()
        app.logger.info(f"Successfully billed user for server '{server['name']}'. Next payment due on {server['next_payment_due']}.")
    else:
        server["status"] = "Заморожен"
        server["freeze_reason"] = "Недостаточно средств."
        server["freeze_date"] = datetime.now().isoformat()
        app.logger.info(f"Server '{server['name']}' frozen due to insufficient funds.")

def check_frozen_servers():
    app.logger.info("Checking for frozen servers...")
    try:
        db = load_db()
        for username, user in db['users'].items():
            servers_to_remove = []
            for server in user.get('servers', []):
                if server["status"] == "Заморожен":
                    freeze_date = datetime.fromisoformat(server["freeze_date"])
                    if datetime.now() > freeze_date + timedelta(days=3):
                        ssh_client = connect_via_ssh()
                        if not ssh_client:
                            app.logger.error(f"Failed to establish SSH connection to delete frozen server '{server['name']}'.")
                            continue
                        try:
                            execute_command = f"docker-compose -f {get_remote_server_dir(server['container_name'])}/docker-compose.yml kill && docker-compose -f {get_remote_server_dir(server['container_name'])}/docker-compose.yml rm -f"
                            app.logger.info(f"Deleting frozen server '{server['name']}' from remote server.")
                            stdout_str, stderr_str, exit_status = execute_ssh_command(execute_command, ssh_client)
                            if exit_status != 0:
                                app.logger.error(f"Failed to delete server '{server['name']}': {stderr_str}")
                                continue
                            servers_to_remove.append(server)
                            app.logger.info(f"Server '{server['name']}' deleted after being frozen for over 3 days.")
                        finally:
                            ssh_client.close()
            for server in servers_to_remove:
                user['servers'].remove(server)
        save_db(db)
    except Exception as e:
        app.logger.error(f"Error during frozen server check: {e}")
        app.logger.error(traceback.format_exc())

def initialize_scheduler():
    scheduler = BackgroundScheduler()
    scheduler.add_job(func=monthly_billing_check, trigger="interval", days=30)
    scheduler.add_job(func=check_frozen_servers, trigger="interval", hours=24)
    scheduler.start()
    app.logger.info("Scheduler started for billing and frozen server checks.")

initialize_scheduler()

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

        if not user_data:
            app.logger.warning(f'User {current_user} not found when accessing /api/servers/{server_id}')
            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:
            app.logger.warning(f'Server with ID {server_id} not found for user {current_user}')
            return jsonify({'error': 'Server not found'}), 404

        plan = server.get('plan', {})
        plan_key = plan.get('key')
        plan_name = plan.get('name', 'Unknown')

        if not plan_key:
            app.logger.error(f"Server {server_id} has a plan without a 'key'. Cannot process request.")
            return jsonify({'error': "Server's plan information is incomplete. Please contact support."}), 500

        formatted_server = {
            'id': server['id'],
            'name': server['name'],
            'plan': {
                'key': plan_key,
                'name': plan_name,
                'cpu': plan.get('cpu'),
                'ram': plan.get('ram'),
                'disk': plan.get('disk'),
                'price': plan.get('price')
            },
            'os': server['os'],
            'status': server['status'],
            'ip': server['ip'],
            'created_at': server['created_at'],
            'container_name': server['container_name'],
            'ports': server['ports'],
            'vnc_address': server.get('vnc_address'),
            'vnc_password': server.get('vnc_password')
        }

        return jsonify(formatted_server), 200

    except Exception as e:
        app.logger.error(f'Error in get_server endpoint: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/servers/<int:server_id>/toggle', methods=['POST'])
@jwt_required()
def toggle_server(server_id):
    try:
        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

        server_dir = get_remote_server_dir(container_name)
        if server['status'] == 'active':
            cmd = f"docker-compose -f {server_dir}/docker-compose.yml stop"
            new_status = 'frozen'
        else:
            cmd = f"docker-compose -f {server_dir}/docker-compose.yml start"
            new_status = 'active'

        ssh_client = connect_via_ssh()
        if not ssh_client:
            return jsonify({'error': 'Failed to establish SSH connection'}), 500

        try:
            stdout_str, stderr_str, exit_status = execute_ssh_command(cmd, ssh_client)
            if exit_status != 0:
                app.logger.error(f'SSH command error: {stderr_str}')
                return jsonify({'error': f'Failed to toggle server status: {stderr_str}'}), 500

            server['status'] = new_status
            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

        except Exception as e:
            app.logger.error(f'Error during SSH operation: {e}')
            return jsonify({'error': f'Failed to toggle server status: {str(e)}'}), 500
        finally:
            ssh_client.close()

    except Exception as e:
        app.logger.error(f'Unhandled exception in toggle_server: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/servers/<int:server_id>', methods=['DELETE'])
@jwt_required()
def delete_server(server_id):
    try:
        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

        server_dir = get_remote_server_dir(container_name)
        # Добавляем команды docker volume prune -f и docker system prune -f
        commands = [
            f"docker-compose -f {server_dir}/docker-compose.yml down",
            f"rm -rf {server_dir}",
            "docker volume prune -f",  # Удаление неиспользуемых томов
            "docker system prune -f"    # Дополнительная очистка Docker
        ]
        full_command = ' && '.join(commands)

        ssh_client = connect_via_ssh()
        if not ssh_client:
            app.logger.error('Failed to establish SSH connection for deleting server.')
            return jsonify({'error': 'Failed to establish SSH connection'}), 500

        try:
            stdout_str, stderr_str, exit_status = execute_ssh_command(full_command, ssh_client)

            if exit_status != 0:
                app.logger.error(f'SSH command error: {stderr_str}')
                return jsonify({'error': f'Failed to delete server: {stderr_str}'}), 500

            user_data['servers'].remove(server)
            save_db(db)
            log_activity(current_user, f'delete_server_{server_id}', request.headers.get('User-Agent', ''))
            app.logger.info(f"Server {server_id} deleted successfully for user {current_user}.")
            return jsonify({'message': 'Server deleted successfully'}), 200

        except Exception as e:
            app.logger.error(f'Error during SSH operation: {e}')
            return jsonify({'error': f'Failed to delete server: {str(e)}'}), 500
        finally:
            ssh_client.close()

    except Exception as e:
        app.logger.error(f'Unhandled exception in delete_server: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/servers/<int:server_id>', methods=['PUT'])
@jwt_required()
def update_server(server_id):
    try:
        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

        new_name = data.get('name')
        new_os = data.get('os')

        if new_name:
            server['name'] = new_name
            app.logger.info(f"Server ID {server_id} name updated to {new_name} by user {current_user}")

        if new_os:
            if new_os not in OS_OPTIONS:
                app.logger.error(f"Invalid operating system selected: {new_os}")
                return jsonify({'error': 'Invalid operating system selected'}), 400
                
            container_name = server.get('container_name')
            server_dir = get_remote_server_dir(container_name)
            docker_compose_path = posixpath.join(server_dir, 'docker-compose.yml')

            docker_compose_content = create_docker_compose_content(
                container_name, new_os, server['plan'],
                server.get('username', 'admin'), server.get('password', generate_password()),
                server.get('vnc_password', generate_password()),
                server['ports']['8006/tcp'], server['ports']['3389/tcp'],
                server['ports']['3389/udp'], server['ports']['5900/tcp']
            )

            ssh_client = connect_via_ssh()
            if not ssh_client:
                app.logger.error('Failed to establish SSH connection for updating server.')
                return jsonify({'error': 'Failed to establish SSH connection'}), 500

            try:
                docker_compose_command = f"""cat <<EOF > {docker_compose_path}
{docker_compose_content}
EOF
"""
                app.logger.info(f"Updating docker-compose.yml on remote server for {container_name}")
                stdout_str, stderr_str, exit_status = execute_ssh_command(docker_compose_command, ssh_client)
                if exit_status != 0:
                    app.logger.error(f"Error updating docker-compose.yml on remote server: {stderr_str}")
                    raise Exception(f"Failed to update docker-compose.yml: {stderr_str}")
                app.logger.info(f"docker-compose.yml updated successfully at {docker_compose_path}")

                restart_command = f"docker-compose -f {docker_compose_path} up -d"
                app.logger.info(f"Restarting server {container_name} to apply OS changes")
                stdout_str, stderr_str, exit_status = execute_ssh_command(restart_command, ssh_client)
                if exit_status != 0:
                    app.logger.error(f"Error restarting server on remote: {stderr_str}")
                    raise Exception(f"Failed to restart server: {stderr_str}")
                app.logger.info(f"Server {container_name} restarted successfully with new OS")

                server['os'] = OS_OPTIONS[new_os]

            except Exception as e:
                app.logger.error(f'Error during SSH operation: {e}')
                return jsonify({'error': f'Failed to update server: {str(e)}'}), 500
            finally:
                ssh_client.close()

        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
    except Exception as e:
        app.logger.error(f'Error in update_server endpoint: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/userinfo', methods=['GET', 'PUT'])
@jwt_required()
def userinfo():
    try:
        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

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

            if new_username:
                if not is_valid_username(new_username):
                    return jsonify({'error': 'Invalid new username. Please use only letters, numbers, and underscores. Length between 3-30 characters.'}), 400
                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

            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

    except Exception as e:
        app.logger.error(f'Error in userinfo endpoint: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/tickets', methods=['GET', 'POST'])
@jwt_required()
def tickets():
    try:
        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

    except Exception as e:
        app.logger.error(f'Error in tickets endpoint: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/tickets/<int:ticket_id>', methods=['GET'])
@jwt_required()
def get_ticket(ticket_id):
    try:
        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

    except Exception as e:
        app.logger.error(f'Error in get_ticket endpoint: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/tickets/<int:ticket_id>/reply', methods=['POST'])
@jwt_required()
def reply_ticket(ticket_id):
    try:
        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

    except Exception as e:
        app.logger.error(f'Error in reply_ticket endpoint: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

# Admin Routes
@app.route('/api/admin/users', methods=['GET'])
@admin_required
def get_all_users():
    try:
        db = load_db()
        users = [
            {
                'username': username,
                'balance': user.get('balance', 0),
                'servers_count': len(user.get('servers', [])),
                'tickets_count': len(user.get('tickets', [])),
                'created_at': user.get('created_at'),
                'role': user.get('role', 'user')
            }
            for username, user in db['users'].items()
        ]
        app.logger.info(f'Admin accessed all users list.')
        return jsonify(users), 200
    except Exception as e:
        app.logger.error(f'Error in get_all_users: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/admin/users/<string:username>', methods=['DELETE'])
@admin_required
def delete_user(username):
    try:
        current_user = get_jwt_identity()  # Retrieve the current admin's username
        db = load_db()
        user = db['users'].get(username)
        if not user:
            app.logger.warning(f'Admin tried to delete non-existent user: {username}')
            return jsonify({'error': 'User not found'}), 404

        # Delete all servers associated with the user
        for server in user.get('servers', []):
            container_name = server.get('container_name')
            server_dir = get_remote_server_dir(container_name)
            commands = [
                f"docker-compose -f {server_dir}/docker-compose.yml down",
                f"rm -rf {server_dir}",
                "docker volume prune -f",
                "docker system prune -f"
            ]
            full_command = ' && '.join(commands)

            ssh_client = connect_via_ssh()
            if ssh_client:
                try:
                    stdout_str, stderr_str, exit_status = execute_ssh_command(full_command, ssh_client)
                    if exit_status != 0:
                        app.logger.error(f'Failed to delete server {server["name"]} for user {username}: {stderr_str}')
                except Exception as e:
                    app.logger.error(f'Error deleting server {server["name"]} for user {username}: {e}')
                finally:
                    ssh_client.close()

        del db['users'][username]
        save_db(db)
        log_activity(current_user, f'delete_user_{username}', request.headers.get('User-Agent', ''))
        app.logger.info(f'Admin {current_user} deleted user: {username}')
        return jsonify({'message': f'User {username} deleted successfully'}), 200
    except Exception as e:
        app.logger.error(f'Error in delete_user: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/admin/logs', methods=['GET'])
@admin_required
def get_logs():
    try:
        log_file_path = os.path.join(LOGS_DIR, 'app.log')
        if not os.path.exists(log_file_path):
            return jsonify({'logs': []}), 200

        with open(log_file_path, 'r') as log_file:
            logs = log_file.readlines()
        return jsonify({'logs': logs}), 200
    except Exception as e:
        app.logger.error(f'Error in get_logs: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'Unable to retrieve logs'}), 500

@app.route('/api/admin/tickets', methods=['GET'])
@admin_required
def admin_get_all_tickets():
    try:
        db = load_db()
        all_tickets = []
        for username, user in db['users'].items():
            for ticket in user.get('tickets', []):
                ticket_info = ticket.copy()
                ticket_info['username'] = username
                all_tickets.append(ticket_info)
        return jsonify(all_tickets), 200
    except Exception as e:
        app.logger.error(f'Error in admin_get_all_tickets: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/admin/tickets/<string:username>/<int:ticket_id>/reply', methods=['POST'])
@admin_required
def admin_reply_ticket(username, ticket_id):
    try:
        db = load_db()
        user_data = db['users'].get(username)
        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': 'admin',  # Indicate that the admin is replying
            'timestamp': datetime.now().isoformat()
        }

        ticket['messages'].append(message)
        save_db(db)
        return jsonify(message), 201

    except Exception as e:
        app.logger.error(f'Error in admin_reply_ticket: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/admin/users/<string:username>/tickets/<int:ticket_id>', methods=['GET'])
@admin_required
def admin_get_ticket(username, ticket_id):
    try:
        db = load_db()
        user_data = db['users'].get(username)
        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
    except Exception as e:
        app.logger.error(f'Error in admin_get_ticket: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/admin/servers', methods=['GET'])
@admin_required
def get_all_servers():
    try:
        db = load_db()
        servers = []
        for username, user in db['users'].items():
            for server in user.get('servers', []):
                server_info = server.copy()
                server_info['owner'] = username
                servers.append(server_info)
        app.logger.info('Admin accessed all servers list.')
        return jsonify(servers), 200
    except Exception as e:
        app.logger.error(f'Error in get_all_servers: {e}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

@app.route('/api/admin/servers/<int:server_id>/toggle', methods=['POST'])
@admin_required
def admin_toggle_server(server_id):
    try:
        db = load_db()
        target_server = None
        target_user = None

        # Find the server and its owner
        for username, user in db['users'].items():
            server = next((s for s in user.get('servers', []) if s['id'] == server_id), None)
            if server:
                target_server = server
                target_user = user
                break

        if not target_server:
            app.logger.warning(f'Admin tried to toggle non-existent server ID: {server_id}')
            return jsonify({'error': 'Server not found'}), 404

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

        server_dir = get_remote_server_dir(container_name)
        if target_server['status'] == 'active':
            cmd = f"docker-compose -f {server_dir}/docker-compose.yml stop"
            new_status = 'frozen'
        else:
            cmd = f"docker-compose -f {server_dir}/docker-compose.yml start"
            new_status = 'active'

        ssh_client = connect_via_ssh()
        if not ssh_client:
            return jsonify({'error': 'Failed to establish SSH connection'}), 500

        try:
            stdout_str, stderr_str, exit_status = execute_ssh_command(cmd, ssh_client)
            if exit_status != 0:
                app.logger.error(f'SSH command error: {stderr_str}')
                return jsonify({'error': f'Failed to toggle server status: {stderr_str}'}), 500

            target_server['status'] = new_status
            save_db(db)
            log_activity(target_user['username'], f'admin_toggle_server_{server_id}', request.headers.get('User-Agent', ''))
            app.logger.info(f'Admin toggled server ID {server_id} to {new_status}')
            return jsonify({'message': f"Server status changed to {new_status}"}), 200

        except Exception as e:
            app.logger.error(f'Error during SSH operation: {e}')
            return jsonify({'error': f'Failed to toggle server status: {str(e)}'}), 500
        finally:
            ssh_client.close()
    except Exception as e:
        app.logger.error(f'Unhandled exception in admin_toggle_server: {str(e)}')
        app.logger.error(traceback.format_exc())
        return jsonify({'error': 'An internal server error occurred'}), 500

# Scheduler Initialization
initialize_scheduler()

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


rewrite this backend use react, next and other web framework if you need this