<?php

declare(strict_types=1);

namespace ServerManager\Controllers;

use ServerManager\Core\App;
use ServerManager\Core\Session;
use ServerManager\Core\Validator;
use ServerManager\Models\Server;
use ServerManager\Models\User;
use ServerManager\Services\SSHService;
use ServerManager\Services\MonitoringService;
use ServerManager\Services\AuditService;
use ServerManager\Models\CommandHistory;

class ServerController
{
    private \ServerManager\Core\View $view;
    private Server $serverModel;
    private AuditService $auditService;

    public function __construct()
    {
        $this->view = App::getInstance()->getView();
        $this->serverModel = new Server();
        $this->auditService = new AuditService();
    }

    private function requireServerAccess(int $serverId, string $minRole = 'viewer'): ?array
    {
        $userId = (int) Session::get('user_id');
        $server = $this->serverModel->findById($serverId);

        if (!$server) {
            $this->view->error(404);
        }

        $role = $this->serverModel->getUserRole($serverId, $userId);

        if ($role === null) {
            $this->view->error(404);
        }

        if ($minRole === 'manager' && $role !== 'owner' && $role !== 'manager') {
            Session::setFlash('error', 'You do not have permission to perform this action.');
            $this->view->redirect("/servers/{$serverId}");
        }

        return $server;
    }

    public function index(): void
    {
        $page = (int) ($_GET['page'] ?? 1);
        $search = $_GET['search'] ?? '';
        $status = $_GET['status'] ?? '';
        $group = $_GET['group'] ?? '';
        $userId = (int) Session::get('user_id');

        $filters = [];
        if ($search) $filters['search'] = $search;
        if ($status) $filters['status'] = $status;
        if ($group) $filters['group_name'] = $group;

        $accessibleIds = $this->serverModel->getAccessibleServerIds($userId);
        $filters['accessible_ids'] = !empty($accessibleIds) ? $accessibleIds : [-1];

        $servers = $this->serverModel->getAll($page, 15, $filters);
        $groups = $this->serverModel->getGroups();

        $this->view->display('servers.index', [
            'title' => 'Servers - ServerManager',
            'servers' => $servers,
            'groups' => $groups,
            'search' => $search,
            'statusFilter' => $status,
            'groupFilter' => $group,
        ]);
    }

    public function create(): void
    {
        $groups = $this->serverModel->getGroups();

        $this->view->display('servers.create', [
            'title' => 'Add Server - ServerManager',
            'groups' => $groups,
        ]);
    }

    public function store(): void
    {
        $validator = new Validator();
        $rules = [
            'name' => 'required|min:2|max:100',
            'ip_address' => 'required',
            'ssh_port' => 'required|numeric|port',
            'ssh_user' => 'required|min:2|max:50',
            'group_name' => 'max:50',
        ];

        if (!$validator->validate($_POST, $rules)) {
            Session::setFlash('error', $validator->getFirstError());
            $this->view->redirect('/servers/create');
        }

        if (empty($_POST['ssh_password']) && empty($_POST['ssh_key'])) {
            Session::setFlash('error', 'You must provide either an SSH password or a private key.');
            $this->view->redirect('/servers/create');
        }

        $data = [
            'name' => $_POST['name'],
            'description' => $_POST['description'] ?? '',
            'ip_address' => $_POST['ip_address'],
            'ssh_port' => (int) $_POST['ssh_port'],
            'ssh_user' => $_POST['ssh_user'],
            'ssh_password' => $_POST['ssh_password'] ?? null,
            'ssh_key' => $_POST['ssh_key'] ?? null,
            'group_name' => $_POST['group_name'] ?? null,
            'is_active' => (int) ($_POST['is_active'] ?? 1),
        ];

        $id = $this->serverModel->create($data);

        $this->auditService->log('server_created', 'server', $id, [
            'name' => $data['name'],
            'ip_address' => $data['ip_address'],
        ]);

        Session::setFlash('success', 'Server added successfully.');
        $this->view->redirect('/servers');
    }

    public function show(int $id): void
    {
        $server = $this->requireServerAccess($id, 'viewer');

        $role = $this->serverModel->getUserRole($id, (int) Session::get('user_id'));

        $monitoringService = new MonitoringService();
        $latestMetrics = $monitoringService->getLatestMetrics($id);
        $historicalMetrics = $monitoringService->getHistoricalMetrics($id, 24);

        $this->view->display('servers.show', [
            'title' => $server['name'] . ' - ServerManager',
            'server' => $server,
            'metrics' => $latestMetrics,
            'historicalMetrics' => $historicalMetrics,
            'server_role' => $role,
        ]);
    }

    public function edit(int $id): void
    {
        $server = $this->requireServerAccess($id, 'manager');

        $groups = $this->serverModel->getGroups();

        $this->view->display('servers.edit', [
            'title' => 'Edit ' . $server['name'] . ' - ServerManager',
            'server' => $server,
            'groups' => $groups,
        ]);
    }

    public function update(int $id): void
    {
        $server = $this->requireServerAccess($id, 'manager');

        $validator = new Validator();
        $rules = [
            'name' => 'required|min:2|max:100',
            'ip_address' => 'required',
            'ssh_port' => 'required|numeric|port',
            'ssh_user' => 'required|min:2|max:50',
            'group_name' => 'max:50',
        ];

        if (!$validator->validate($_POST, $rules)) {
            Session::setFlash('error', $validator->getFirstError());
            $this->view->redirect("/servers/{$id}/edit");
        }

        $data = [
            'name' => $_POST['name'],
            'description' => $_POST['description'] ?? '',
            'ip_address' => $_POST['ip_address'],
            'ssh_port' => (int) $_POST['ssh_port'],
            'ssh_user' => $_POST['ssh_user'],
            'group_name' => $_POST['group_name'] ?? null,
            'is_active' => (int) ($_POST['is_active'] ?? 1),
        ];

        if (!empty($_POST['ssh_password'])) {
            $data['ssh_password'] = $_POST['ssh_password'];
        }

        if (!empty($_POST['ssh_key'])) {
            $data['ssh_key'] = $_POST['ssh_key'];
        }

        $this->serverModel->update($id, $data);

        $this->auditService->log('server_updated', 'server', $id, [
            'name' => $data['name'],
        ]);

        Session::setFlash('success', 'Server updated successfully.');
        $this->view->redirect("/servers/{$id}");
    }

    public function delete(int $id): void
    {
        $server = $this->requireServerAccess($id, 'manager');

        $this->serverModel->delete($id);

        $this->auditService->log('server_deleted', 'server', $id, [
            'name' => $server['name'],
        ]);

        Session::setFlash('success', 'Server deleted successfully.');
        $this->view->redirect('/servers');
    }

    public function toggleActive(int $id): void
    {
        $server = $this->serverModel->findById($id);

        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        $newState = $this->serverModel->toggleActive($id);

        $this->auditService->log(
            $newState ? 'server_activated' : 'server_deactivated',
            'server',
            $id,
            ['name' => $server['name']]
        );

        $this->view->json([
            'success' => true,
            'is_active' => $newState,
            'message' => $newState ? 'Server activated.' : 'Server deactivated.',
        ]);
    }

    public function agentInstall(int $id): void
    {
        $server = $this->requireServerAccess($id, 'manager');

        $agentService = new \ServerManager\Services\AgentService();
        $result = $agentService->install($id);

        $this->view->json($result);
    }

    public function agentUninstall(int $id): void
    {
        $server = $this->requireServerAccess($id, 'manager');

        $agentService = new \ServerManager\Services\AgentService();
        $result = $agentService->uninstall($id);

        $this->view->json($result);
    }

    public function agentRegenerateKey(int $id): void
    {
        $this->requireServerAccess($id, 'manager');

        $newKey = $this->serverModel->regenerateAgentKey($id);

        $this->auditService->log('agent_key_regenerated', 'server', $id);

        $this->view->json([
            'success' => true,
            'agent_key' => $newKey,
        ]);
    }

    public function testConnection(int $id): void
    {
        $server = $this->requireServerAccess($id, 'viewer');

        $ssh = new SSHService();
        $result = $ssh->testConnection($server);

        $this->auditService->logCredentialAccess($id, 'SSH connection test');

        $this->view->json([
            'success' => $result,
            'message' => $result ? 'Connection successful.' : 'Connection failed.',
        ]);
    }

    public function processes(int $id): void
    {
        $server = $this->requireServerAccess($id, 'viewer');

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }
            $this->auditService->logCredentialAccess($id, 'View processes');

            $result = $ssh->exec(
                "ps aux --sort=-%cpu | awk 'NR>1{pid=\$2; user=\$1; cpu=\$3; mem=\$4; vsz=\$5; rss=\$6; tty=\$7; stat=\$8; start=\$9; time=\$10; cmd=substr(\$0, index(\$0,\$11)); print user\"|\"pid\"|\"cpu\"|\"mem\"|\"vsz\"|\"rss\"|\"tty\"|\"stat\"|\"start\"|\"time\"|\"cmd}' | head -50"
            );
            $ssh->disconnect();

            $processList = [];
            foreach (explode("\n", trim($result['output'])) as $line) {
                $parts = explode('|', $line);
                if (count($parts) >= 11) {
                    $processList[] = [
                        'user' => $parts[0],
                        'pid' => (int) $parts[1],
                        'cpu' => (float) $parts[2],
                        'mem' => (float) $parts[3],
                        'vsz' => (int) $parts[4],
                        'rss' => (int) $parts[5],
                        'tty' => $parts[6],
                        'stat' => $parts[7],
                        'start' => $parts[8],
                        'time' => $parts[9],
                        'command' => $parts[10],
                    ];
                }
            }

            $this->view->display('servers.processes', [
                'title' => 'Processes - ' . $server['name'] . ' - ServerManager',
                'server' => $server,
                'processList' => $processList,
            ]);
        } catch (\Throwable $e) {
            Session::setFlash('error', 'Failed to get processes: ' . $e->getMessage());
            $this->view->redirect("/servers/{$id}");
        }
    }

    public function systemInfo(int $id): void
    {
        $server = $this->requireServerAccess($id, 'viewer');

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }
            $this->auditService->logCredentialAccess($id, 'View system info');

            $info = $ssh->getSystemInfo();
            $ssh->disconnect();

            $this->view->display('servers.system_info', [
                'title' => 'System Info - ' . $server['name'] . ' - ServerManager',
                'server' => $server,
                'systemInfo' => $info,
            ]);
        } catch (\Throwable $e) {
            Session::setFlash('error', 'Failed to get system info: ' . $e->getMessage());
            $this->view->redirect("/servers/{$id}");
        }
    }

    public function logs(int $id): void
    {
        $server = $this->requireServerAccess($id, 'viewer');

        $logType = $_GET['type'] ?? 'syslog';
        $lines = (int) ($_GET['lines'] ?? 100);

        $logFiles = [
            'syslog' => '/var/log/syslog',
            'auth' => '/var/log/auth.log',
            'messages' => '/var/log/messages',
            'nginx' => '/var/log/nginx/error.log',
            'apache' => '/var/log/apache2/error.log',
            'mysql' => '/var/log/mysql/error.log',
            'dmesg' => 'dmesg',
        ];

        $logOutput = '';

        try {
            $ssh = new SSHService();
            if ($ssh->connect($server)) {
                $this->auditService->logCredentialAccess($id, 'View system logs');

                if ($logType === 'dmesg') {
                    $result = $ssh->exec('dmesg | tail -n ' . $lines);
                } else {
                    $logFile = $logFiles[$logType] ?? $logFiles['syslog'];
                    $result = $ssh->exec("tail -n {$lines} {$logFile} 2>/dev/null || echo 'Log file not found or not accessible'");
                }

                $logOutput = $result['output'];
                $ssh->disconnect();
            }
        } catch (\Throwable $e) {
            $logOutput = 'Error: ' . $e->getMessage();
        }

        $this->view->display('servers.logs', [
            'title' => 'System Logs - ' . $server['name'] . ' - ServerManager',
            'server' => $server,
            'logOutput' => $logOutput,
            'logType' => $logType,
            'lines' => $lines,
            'logFiles' => array_keys($logFiles),
        ]);
    }

    public function reboot(int $id): void
    {
        $server = $this->serverModel->findById($id);

        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }
            $this->auditService->logServerAction($id, 'reboot', 'sudo reboot');

            $ssh->exec('sudo reboot 2>&1 || echo "Reboot command sent (may require sudo)"');
            $ssh->disconnect();

            $this->auditService->log('server_rebooted', 'server', $id, ['name' => $server['name']]);

            $this->view->json([
                'success' => true,
                'message' => 'Reboot command sent.',
            ]);
        } catch (\Throwable $e) {
            $this->view->json([
                'success' => false,
                'message' => 'Failed to send reboot command: ' . $e->getMessage(),
            ]);
        }
    }

    public function shutdown(int $id): void
    {
        $server = $this->serverModel->findById($id);

        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }
            $this->auditService->logServerAction($id, 'shutdown', 'sudo shutdown -h now');

            $ssh->exec('sudo shutdown -h now 2>&1 || echo "Shutdown command sent (may require sudo)"');
            $ssh->disconnect();

            $this->auditService->log('server_shutdown', 'server', $id, ['name' => $server['name']]);

            $this->view->json([
                'success' => true,
                'message' => 'Shutdown command sent.',
            ]);
        } catch (\Throwable $e) {
            $this->view->json([
                'success' => false,
                'message' => 'Failed to send shutdown command: ' . $e->getMessage(),
            ]);
        }
    }

    public function restartService(int $id): void
    {
        $server = $this->serverModel->findById($id);

        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        $service = $_POST['service'] ?? '';
        if (empty($service)) {
            $this->view->json(['success' => false, 'message' => 'Service name is required']);
        }

        $allowedServices = ['nginx', 'apache2', 'mysql', 'mariadb', 'postgresql', 'redis', 'docker', 'sshd', 'cron'];
        $service = preg_replace('/[^a-zA-Z0-9\-_]/', '', $service);

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }
            $this->auditService->logServerAction($id, 'service_restart', "sudo systemctl restart {$service}");

            $result = $ssh->exec("sudo systemctl restart {$service} 2>&1");
            $ssh->disconnect();

            $this->auditService->log('service_restarted', 'server', $id, [
                'name' => $server['name'],
                'service' => $service,
            ]);

            $this->view->json([
                'success' => true,
                'message' => "Service '{$service}' restarted.",
                'output' => $result['output'],
            ]);
        } catch (\Throwable $e) {
            $this->view->json([
                'success' => false,
                'message' => 'Failed to restart service: ' . $e->getMessage(),
            ]);
        }
    }

    public function killProcess(int $id): void
    {
        $server = $this->serverModel->findById($id);

        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        $pid = (int) ($_POST['pid'] ?? 0);
        $signal = (int) ($_POST['signal'] ?? 15);

        if ($pid <= 0) {
            $this->view->json(['success' => false, 'message' => 'Invalid PID']);
        }

        $allowedSignals = [9, 15];
        if (!in_array($signal, $allowedSignals, true)) {
            $signal = 15;
        }

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }

            $this->auditService->logServerAction($id, 'kill_process', "kill -{$signal} {$pid}");
            $result = $ssh->exec("kill -{$signal} {$pid} 2>&1");
            $ssh->disconnect();

            $exitStatus = $result['exit_status'] ?? 0;
            $success = $exitStatus === 0;

            $this->view->json([
                'success' => $success,
                'message' => $success
                    ? "Process {$pid} terminated with signal {$signal}."
                    : "Failed to kill process {$pid}: " . ($result['output'] ?: 'Unknown error'),
            ]);
        } catch (\Throwable $e) {
            $this->view->json([
                'success' => false,
                'message' => 'Failed to kill process: ' . $e->getMessage(),
            ]);
        }
    }

    public function nginx(int $id): void
    {
        $server = $this->requireServerAccess($id, 'viewer');

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }

            $this->auditService->logCredentialAccess($id, 'View nginx config');

            $installed = trim($ssh->exec('command -v nginx 2>/dev/null || which nginx 2>/dev/null || echo ""')['output'] ?? '');
            $nginxFound = !empty($installed);

            $data = [
                'installed' => $nginxFound,
            ];

            if ($nginxFound) {
                $data['version'] = trim($ssh->exec('nginx -v 2>&1')['output'] ?? '');

                $statusResult = $ssh->exec("systemctl is-active nginx 2>/dev/null || echo 'inactive'");
                $data['status'] = trim($statusResult['output'] ?? 'inactive');

                $enabledResult = $ssh->exec("systemctl is-enabled nginx 2>/dev/null || echo 'disabled'");
                $data['enabled'] = trim($enabledResult['output'] ?? 'disabled');

                $confResult = $ssh->exec('cat /etc/nginx/nginx.conf 2>/dev/null || echo "CONFIG_NOT_FOUND"');
                $data['config'] = $confResult['output'] === 'CONFIG_NOT_FOUND' ? '' : $confResult['output'];

                $sitesResult = $ssh->exec('ls -1 /etc/nginx/sites-enabled/ 2>/dev/null || echo ""');
                $data['sites_enabled'] = array_filter(explode("\n", trim($sitesResult['output'])));

                $availResult = $ssh->exec('ls -1 /etc/nginx/sites-available/ 2>/dev/null || echo ""');
                $data['sites_available'] = array_filter(explode("\n", trim($availResult['output'])));

                $testResult = $ssh->exec('sudo nginx -t 2>&1');
                $data['config_test'] = trim($testResult['output']);
                $data['config_valid'] = $testResult['exit_status'] === 0;
            }

            $ssh->disconnect();

            $this->view->display('servers.nginx', [
                'title' => 'Nginx - ' . $server['name'] . ' - ServerManager',
                'server' => $server,
                'nginx' => $data,
            ]);
        } catch (\Throwable $e) {
            Session::setFlash('error', 'Failed to get nginx info: ' . $e->getMessage());
            $this->view->redirect("/servers/{$id}");
        }
    }

    public function nginxGetFile(int $id): void
    {
        $server = $this->requireServerAccess($id, 'viewer');

        $file = $_GET['file'] ?? '';
        if (empty($file) || str_contains($file, '..')) {
            $this->view->json(['success' => false, 'message' => 'Invalid file path']);
        }

        $allowedPaths = ['/etc/nginx/nginx.conf', '/etc/nginx/sites-available/', '/etc/nginx/sites-enabled/'];
        $fullPath = str_starts_with($file, '/etc/nginx/') ? $file : '/etc/nginx/sites-available/' . basename($file);

        $valid = false;
        foreach ($allowedPaths as $path) {
            if (str_starts_with($fullPath, $path)) {
                $valid = true;
                break;
            }
        }

        if (!$valid) {
            $this->view->json(['success' => false, 'message' => 'Access denied']);
        }

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }

            $result = $ssh->exec("cat " . escapeshellarg($fullPath) . " 2>/dev/null || echo 'FILE_NOT_FOUND'");
            $ssh->disconnect();

            if (trim($result['output']) === 'FILE_NOT_FOUND') {
                $this->view->json(['success' => false, 'message' => 'File not found']);
            }

            $this->view->json([
                'success' => true,
                'path' => $fullPath,
                'content' => $result['output'],
            ]);
        } catch (\Throwable $e) {
            $this->view->json(['success' => false, 'message' => $e->getMessage()]);
        }
    }

    public function nginxSaveFile(int $id): void
    {
        $server = $this->serverModel->findById($id);

        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        $file = $_POST['file'] ?? '';
        $content = $_POST['content'] ?? '';

        if (empty($file) || str_contains($file, '..')) {
            $this->view->json(['success' => false, 'message' => 'Invalid file path']);
        }

        $allowedPaths = ['/etc/nginx/nginx.conf', '/etc/nginx/sites-available/'];
        $fullPath = str_starts_with($file, '/etc/nginx/') ? $file : '/etc/nginx/sites-available/' . basename($file);

        $valid = false;
        foreach ($allowedPaths as $path) {
            if (str_starts_with($fullPath, $path)) {
                $valid = true;
                break;
            }
        }

        if (!$valid) {
            $this->view->json(['success' => false, 'message' => 'Access denied']);
        }

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }

            $b64 = base64_encode($content);
            $result = $ssh->exec("echo {$b64} | base64 -d | sudo tee " . escapeshellarg($fullPath) . " > /dev/null 2>&1; echo EXIT:\$?");
            $ssh->disconnect();

            $this->auditService->logServerAction($id, 'nginx_save_config', $fullPath);

            $this->view->json([
                'success' => true,
                'message' => 'File saved successfully.',
            ]);
        } catch (\Throwable $e) {
            $this->view->json([
                'success' => false,
                'message' => 'Failed to save file: ' . $e->getMessage(),
            ]);
        }
    }

    public function nginxDeleteSite(int $id): void
    {
        $server = $this->serverModel->findById($id);

        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        $site = $_POST['site'] ?? '';
        if (empty($site) || str_contains($site, '..') || str_contains($site, '/')) {
            $this->view->json(['success' => false, 'message' => 'Invalid site name']);
        }

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }

            $this->auditService->logServerAction($id, 'nginx_delete_site', $site);

            $result = $ssh->exec("sudo /bin/rm /etc/nginx/sites-enabled/" . escapeshellarg($site) . " /etc/nginx/sites-available/" . escapeshellarg($site) . " 2>&1; echo EXIT:\$?");
            $exitCode = (int) trim(explode("\n", $result['output'] ?? 'EXIT:-1')[0] ?? '-1');

            $ssh->disconnect();

            $this->view->json([
                'success' => $exitCode === 0,
                'message' => $exitCode === 0 ? "Site '{$site}' deleted." : "Failed to delete site '{$site}'.",
            ]);
        } catch (\Throwable $e) {
            $this->view->json([
                'success' => false,
                'message' => 'Failed to delete site: ' . $e->getMessage(),
            ]);
        }
    }

    public function nginxCreateSite(int $id): void
    {
        $server = $this->serverModel->findById($id);

        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        $siteName = trim($_POST['site_name'] ?? '');
        $siteRoot = trim($_POST['site_root'] ?? '');
        $serverName = trim($_POST['server_name'] ?? '');
        $index = trim($_POST['index'] ?? 'index.html index.htm index.php');
        $enablePhp = !empty($_POST['enable_php']);
        $enableSite = !empty($_POST['enable_site']);

        if (empty($siteName) || empty($siteRoot)) {
            $this->view->json(['success' => false, 'message' => 'Site name and root directory are required']);
        }

        if (!preg_match('/^[a-zA-Z0-9._-]+$/', $siteName)) {
            $this->view->json(['success' => false, 'message' => 'Invalid site name. Use only letters, numbers, dots, hyphens.']);
        }

        if (str_starts_with($siteRoot, '/etc/nginx') || str_contains($siteRoot, '..')) {
            $this->view->json(['success' => false, 'message' => 'Invalid root directory']);
        }

        if (empty($serverName)) {
            $serverName = $siteName;
        }

        $config = "server {\n";
        $config .= "    listen 80;\n";
        $config .= "    listen [::]:80;\n\n";
        $config .= "    root {$siteRoot};\n";
        $config .= "    index {$index};\n\n";
        $config .= "    server_name {$serverName};\n\n";
        $config .= "    location / {\n";
        $config .= "        try_files \$uri \$uri/ =404;\n";
        $config .= "    }\n";

        if ($enablePhp) {
            $config .= "\n    location ~ \\.php\$ {\n";
            $config .= "        include snippets/fastcgi-php.conf;\n";
            $config .= "        fastcgi_pass unix:/var/run/php/php-fpm.sock;\n";
            $config .= "    }\n";
        }

        $config .= "}\n";

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }

            $file = "/etc/nginx/sites-available/{$siteName}";
            $b64 = base64_encode($config);

            $result = $ssh->exec("echo {$b64} | base64 -d | sudo tee " . escapeshellarg($file) . " > /dev/null 2>&1; echo EXIT:\$?");
            $exitCode = (int) trim($result['output'] ?? '-1');

            if ($exitCode !== 0) {
                throw new \RuntimeException('Failed to create site configuration file');
            }

            $this->auditService->logServerAction($id, 'nginx_create_site', $siteName);

            if ($enableSite) {
                $linkResult = $ssh->exec('sudo ln -s ' . escapeshellarg($file) . ' /etc/nginx/sites-enabled/ 2>&1; echo EXIT:$?');
            }

            $ssh->disconnect();

            $this->view->json([
                'success' => true,
                'message' => "Site '{$siteName}' created successfully." . ($enableSite ? ' Enabled.' : ''),
            ]);
        } catch (\Throwable $e) {
            $this->view->json([
                'success' => false,
                'message' => 'Failed to create site: ' . $e->getMessage(),
            ]);
        }
    }

    public function nginxToggleSite(int $id): void
    {
        $server = $this->serverModel->findById($id);

        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        $site = $_POST['site'] ?? '';
        $enable = (bool) ($_POST['enable'] ?? false);

        if (empty($site) || str_contains($site, '..') || str_contains($site, '/')) {
            $this->view->json(['success' => false, 'message' => 'Invalid site name']);
        }

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }

            if ($enable) {
                $result = $ssh->exec('sudo ln -s /etc/nginx/sites-available/' . escapeshellarg($site) . ' /etc/nginx/sites-enabled/ 2>&1; echo EXIT:$?');
            } else {
                $result = $ssh->exec('sudo rm /etc/nginx/sites-enabled/' . escapeshellarg($site) . ' 2>&1; echo EXIT:$?');
            }
            $ssh->disconnect();

            $action = $enable ? 'enabled' : 'disabled';
            $this->auditService->logServerAction($id, "nginx_{$action}_site", $site);

            $this->view->json([
                'success' => true,
                'message' => "Site '{$site}' {$action}.",
            ]);
        } catch (\Throwable $e) {
            $this->view->json([
                'success' => false,
                'message' => 'Failed to toggle site: ' . $e->getMessage(),
            ]);
        }
    }

    public function nginxAction(int $id): void
    {
        $server = $this->serverModel->findById($id);

        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        $action = $_POST['action'] ?? '';
        $allowed = ['restart', 'reload', 'test', 'start', 'stop'];
        if (!in_array($action, $allowed, true)) {
            $this->view->json(['success' => false, 'message' => 'Invalid action']);
        }

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }

            $this->auditService->logServerAction($id, "nginx_{$action}", "sudo systemctl {$action} nginx");

            if ($action === 'test') {
                $result = $ssh->exec('sudo nginx -t 2>&1');
                $valid = $result['exit_status'] === 0;
                $this->view->json([
                    'success' => $valid,
                    'message' => $valid ? 'Configuration is valid.' : 'Configuration test failed.',
                    'output' => trim($result['output']),
                ]);
            } else {
                $result = $ssh->exec("sudo systemctl {$action} nginx 2>&1");
                $success = $result['exit_status'] === 0;
                $msg = $success
                    ? "Nginx {$action}ed successfully."
                    : "Failed to {$action} nginx: " . ($result['output'] ?: 'Unknown error');
                $this->view->json(['success' => $success, 'message' => $msg, 'output' => trim($result['output'])]);
            }

            $ssh->disconnect();
        } catch (\Throwable $e) {
            $this->view->json([
                'success' => false,
                'message' => 'Failed to execute nginx action: ' . $e->getMessage(),
            ]);
        }
    }

    public function ssl(int $id): void
    {
        $server = $this->requireServerAccess($id, 'viewer');

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }

            $this->auditService->logCredentialAccess($id, 'View SSL certificates');

            $installed = trim($ssh->exec('command -v certbot 2>/dev/null || which certbot 2>/dev/null || echo ""')['output'] ?? '');
            $certbotFound = !empty($installed);

            $data = ['certbot_installed' => $certbotFound];

            if ($certbotFound) {
                $data['version'] = trim($ssh->exec('certbot --version 2>&1')['output'] ?? '');

                $certsResult = $ssh->exec('sudo certbot certificates 2>&1');
                $data['certificates_raw'] = trim($certsResult['output']);

                $certs = [];
                $lines = explode("\n", $certsResult['output']);
                $currentCert = null;
                foreach ($lines as $line) {
                    if (preg_match('/^  Certificate Name:\s+(.+)$/', $line, $m)) {
                        if ($currentCert) $certs[] = $currentCert;
                        $currentCert = ['name' => trim($m[1]), 'domains' => [], 'expiry' => ''];
                    } elseif ($currentCert && preg_match('/^\s+Domains:\s+(.+)$/', $line, $m)) {
                        $currentCert['domains'] = array_map('trim', explode(' ', trim($m[1])));
                    } elseif ($currentCert && preg_match('/^\s+Expiry Date:\s+(.+?)(?:\s+\(VALID|$)/', $line, $m)) {
                        $currentCert['expiry'] = trim($m[1]);
                    } elseif ($currentCert && preg_match('/^\s+Certificate Path:\s+(.+)$/', $line, $m)) {
                        $currentCert['path'] = trim($m[1]);
                    } elseif ($currentCert && preg_match('/^\s+Key Type:\s+(.+)$/', $line, $m)) {
                        $currentCert['key_type'] = trim($m[1]);
                    }
                }
                if ($currentCert) $certs[] = $currentCert;
                $data['certificates'] = $certs;

                $renewalResult = $ssh->exec('certbot renew --dry-run 2>&1 | tail -5');
                $data['renewal_status'] = trim($renewalResult['output']);
            } else {
                $osResult = $ssh->exec('(. /etc/os-release && echo $ID) 2>/dev/null || echo "unknown"');
                $data['os'] = trim($osResult['output'] ?? 'unknown');
            }

            $ssh->disconnect();

            $this->view->display('servers.ssl', [
                'title' => 'SSL Certificates - ' . $server['name'] . ' - ServerManager',
                'server' => $server,
                'ssl' => $data,
            ]);
        } catch (\Throwable $e) {
            Session::setFlash('error', 'Failed to get SSL info: ' . $e->getMessage());
            $this->view->redirect("/servers/{$id}");
        }
    }

    public function sslAction(int $id): void
    {
        $server = $this->serverModel->findById($id);

        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        $action = $_POST['action'] ?? '';
        $allowed = ['install', 'request', 'renew', 'delete'];
        if (!in_array($action, $allowed, true)) {
            $this->view->json(['success' => false, 'message' => 'Invalid action']);
        }

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }

            $this->auditService->logServerAction($id, "certbot_{$action}", $action);

            if ($action === 'install') {
                $osResult = $ssh->exec('(. /etc/os-release && echo $ID) 2>/dev/null || echo "unknown"');
                $os = trim($osResult['output'] ?? 'unknown');

                if ($os === 'ubuntu' || $os === 'debian') {
                    $result = $ssh->exec('sudo apt-get update -qq 2>&1 && sudo apt-get install -y -qq certbot python3-certbot-nginx 2>&1; echo EXIT:$?');
                } elseif ($os === 'centos' || $os === 'fedora' || $os === 'rhel' || str_contains($os, 'rocky') || str_contains($os, 'almalinux')) {
                    $result = $ssh->exec('sudo dnf install -y -q certbot python3-certbot-nginx 2>&1; echo EXIT:$?');
                } else {
                    $result = $ssh->exec('sudo apt-get install -y -qq certbot 2>&1 || sudo yum install -y -q certbot 2>&1; echo EXIT:$?');
                }

                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();

                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? 'Certbot installed successfully.' : 'Failed to install certbot. Try installing manually.',
                    'output' => trim($result['output']),
                ]);
            } elseif ($action === 'request') {
                $domains = trim($_POST['domains'] ?? '');
                $email = trim($_POST['email'] ?? '');

                if (empty($domains)) {
                    $this->view->json(['success' => false, 'message' => 'Domain is required']);
                }

                $domainList = preg_replace('/\s+/', ',', $domains);
                $emailArg = !empty($email) ? "--email {$email}" : '--register-unsafely-without-email';
                $cmd = "sudo certbot --nginx -d {$domainList} {$emailArg} --non-interactive --agree-tos 2>&1; echo EXIT:\$?";
                $result = $ssh->exec($cmd);
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();

                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? 'Certificate obtained successfully.' : 'Failed to obtain certificate.',
                    'output' => trim($result['output']),
                ]);
            } elseif ($action === 'renew') {
                $result = $ssh->exec('sudo certbot renew --non-interactive 2>&1; echo EXIT:$?');
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();

                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? 'Certificates renewed.' : 'Renewal attempted with some issues.',
                    'output' => trim($result['output']),
                ]);
            } elseif ($action === 'delete') {
                $certName = trim($_POST['cert_name'] ?? '');
                if (empty($certName)) {
                    $this->view->json(['success' => false, 'message' => 'Certificate name is required']);
                }
                $result = $ssh->exec("sudo certbot delete --non-interactive --cert-name " . escapeshellarg($certName) . " 2>&1; echo EXIT:\$?");
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();

                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? "Certificate '{$certName}' deleted." : 'Failed to delete certificate.',
                    'output' => trim($result['output']),
                ]);
            } else {
                $ssh->disconnect();
                $this->view->json(['success' => false, 'message' => 'Unknown action']);
            }
        } catch (\Throwable $e) {
            $this->view->json([
                'success' => false,
                'message' => 'Failed: ' . $e->getMessage(),
            ]);
        }
    }

    public function database(int $id): void
    {
        $server = $this->requireServerAccess($id, 'viewer');

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }

            $this->auditService->logCredentialAccess($id, 'View database manager');

            $mysqlFound = trim($ssh->exec('command -v mysql 2>/dev/null || which mysql 2>/dev/null || echo ""')['output'] ?? '');
            $mariadbFound = trim($ssh->exec('command -v mariadb 2>/dev/null || which mariadb 2>/dev/null || echo ""')['output'] ?? '');
            $hasMysql = !empty($mysqlFound);
            $hasMariadb = !empty($mariadbFound);

            $dbType = '';
            $version = '';
            $dbStatus = '';

            if ($hasMariadb) {
                $dbType = 'MariaDB';
                $version = trim($ssh->exec('mariadb --version 2>&1')['output'] ?? '');
                $dbStatus = trim($ssh->exec("systemctl is-active mariadb 2>/dev/null || echo 'inactive'")['output'] ?? 'inactive');
            } elseif ($hasMysql) {
                $versionResult = $ssh->exec('mysql --version 2>&1');
                $version = trim($versionResult['output'] ?? '');
                if (stripos($version, 'mariadb') !== false) {
                    $dbType = 'MariaDB';
                    $dbStatus = trim($ssh->exec("systemctl is-active mariadb 2>/dev/null || echo 'inactive'")['output'] ?? 'inactive');
                } else {
                    $dbType = 'MySQL';
                    $dbStatus = trim($ssh->exec("systemctl is-active mysql 2>/dev/null || echo 'inactive'")['output'] ?? 'inactive');
                }
                $hasMysql = true;
            }

            $data = [
                'installed' => $hasMysql || $hasMariadb,
                'db_type' => $dbType,
                'version' => $version,
                'status' => $dbStatus,
            ];

            if ($data['installed']) {
                $mysqlBin = $dbType === 'MariaDB' ? 'mariadb' : 'mysql';

                $dbsResult = $ssh->exec("sudo {$mysqlBin} --batch -e \"SELECT TABLE_SCHEMA AS name, ROUND(SUM(DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 1) AS size_mb FROM information_schema.TABLES GROUP BY TABLE_SCHEMA ORDER BY size_mb DESC\" 2>/dev/null | tail -n +2 || echo 'ACCESS_DENIED'");
                $dbsOutput = trim($dbsResult['output'] ?? '');

                $databases = [];
                if ($dbsOutput && $dbsOutput !== 'NO_ACCESS') {
                    foreach (explode("\n", $dbsOutput) as $line) {
                        $parts = preg_split('/\s+/', trim($line), 2);
                        if (count($parts) === 2) {
                            $databases[] = ['name' => $parts[0], 'size_mb' => (float) $parts[1]];
                        }
                    }
                }
                $data['databases'] = $databases;

                $mysqlBin = $dbType === 'MariaDB' ? 'mariadb' : 'mysql';
                $sizeResult = $ssh->exec("sudo {$mysqlBin} -e \"SELECT ROUND(SUM(DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 1) AS total_mb FROM information_schema.TABLES\" 2>/dev/null | tail -1");
                $data['total_size_mb'] = (float) trim($sizeResult['output'] ?? '0');

                $processResult = $ssh->exec("sudo {$mysqlBin} -e \"SHOW PROCESSLIST\" 2>/dev/null | head -20");
                $processes = [];
                $lines = explode("\n", trim($processResult['output'] ?? ''));
                $header = true;
                foreach ($lines as $line) {
                    if ($header) { $header = false; continue; }
                    $parts = preg_split('/\s+\|\s+/', trim($line, '| '));
                    if (count($parts) >= 5) {
                        $processes[] = [
                            'id' => $parts[0],
                            'user' => $parts[1],
                            'host' => $parts[2],
                            'db' => $parts[3] ?? '',
                            'command' => $parts[4] ?? '',
                            'time' => $parts[5] ?? '0',
                            'state' => $parts[6] ?? '',
                            'info' => $parts[7] ?? '',
                        ];
                    }
                }
                $data['processes'] = array_slice($processes, 0, 20);
            }

            $ssh->disconnect();

            $this->view->display('servers.database', [
                'title' => "{$dbType} Manager - {$server['name']} - ServerManager",
                'server' => $server,
                'db' => $data,
            ]);
        } catch (\Throwable $e) {
            Session::setFlash('error', 'Failed to get database info: ' . $e->getMessage());
            $this->view->redirect("/servers/{$id}");
        }
    }

    public function databaseAction(int $id): void
    {
        $server = $this->serverModel->findById($id);

        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        $action = $_POST['action'] ?? '';
        $allowed = ['query', 'tables', 'structure', 'browse', 'users', 'create_user', 'grant', 'revoke', 'drop_user', 'change_pass'];
        if (!in_array($action, $allowed, true)) {
            $this->view->json(['success' => false, 'message' => 'Invalid action']);
        }

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }

            $this->auditService->logServerAction($id, "db_{$action}", $action);

            $mysqlBinResult = $ssh->exec('command -v mariadb 2>/dev/null || echo mysql');
            $mysqlBin = trim($mysqlBinResult['output'] ?? 'mariadb');
            $db = trim($_POST['db'] ?? '');
            $table = trim($_POST['table'] ?? '');

            if ($action === 'tables') {
                if (empty($db)) {
                    $this->view->json(['success' => false, 'message' => 'Database is required']);
                }
                $escapedDb = str_replace("'", "'\\''", $db);
                $result = $ssh->exec("sudo {$mysqlBin} --batch -e 'USE `{$escapedDb}`; SHOW TABLE STATUS' 2>&1");
                $lines = explode("\n", trim($result['output'] ?? ''));
                $tables = [];
                $header = true;
                foreach ($lines as $line) {
                    if ($header) { $header = false; continue; }
                    $parts = explode("\t", $line);
                    if (count($parts) >= 5) {
                        $tables[] = [
                            'name' => $parts[0] ?? '',
                            'engine' => $parts[1] ?? '',
                            'rows' => (int) ($parts[4] ?? 0),
                            'size' => (int) (($parts[6] ?? 0) + ($parts[8] ?? 0)),
                            'collation' => $parts[14] ?? '',
                        ];
                    }
                }
                $ssh->disconnect();
                $this->view->json(['success' => true, 'tables' => $tables]);
            } elseif ($action === 'structure') {
                if (empty($db) || empty($table)) {
                    $this->view->json(['success' => false, 'message' => 'Database and table are required']);
                }
                $escapedDb = str_replace("'", "'\\''", $db);
                $escapedTable = str_replace("'", "'\\''", $table);
                $result = $ssh->exec("sudo {$mysqlBin} --batch -e 'SHOW FULL COLUMNS FROM `{$escapedDb}`.`{$escapedTable}`' 2>&1");
                $lines = explode("\n", trim($result['output'] ?? ''));
                $columns = [];
                $header = true;
                foreach ($lines as $line) {
                    if ($header) { $header = false; continue; }
                    if (str_starts_with($line, '---')) continue;
                    $parts = explode("\t", $line);
                    if (count($parts) >= 6) {
                        $columns[] = [
                            'field' => $parts[0] ?? '',
                            'type' => $parts[1] ?? '',
                            'collation' => $parts[2] ?? '',
                            'null' => $parts[3] ?? '',
                            'key' => $parts[4] ?? '',
                            'default' => $parts[5] ?? '',
                            'extra' => $parts[6] ?? '',
                        ];
                    }
                }
                $ssh->disconnect();
                $this->view->json(['success' => true, 'columns' => $columns]);
            } elseif ($action === 'browse') {
                if (empty($db) || empty($table)) {
                    $this->view->json(['success' => false, 'message' => 'Database and table are required']);
                }
                $page = max(1, (int) ($_POST['page'] ?? 1));
                $limit = 50;
                $offset = ($page - 1) * $limit;
                $escapedDb = str_replace("'", "'\\''", $db);
                $escapedTable = str_replace("'", "'\\''", $table);

                $colsResult = $ssh->exec("sudo {$mysqlBin} --batch -e 'SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=\"{$escapedDb}\" AND TABLE_NAME=\"{$escapedTable}\" ORDER BY ORDINAL_POSITION' 2>&1 | tail -n +2");
                $columns = array_map('trim', array_filter(explode("\n", trim($colsResult['output'] ?? ''))));

                $where = '';
                $conditions = [];
                foreach ($columns as $col) {
                    $val = trim($_POST[$col] ?? '');
                    if ($val !== '') {
                        $escapedVal = str_replace(["\\", "'"], ["\\\\", "'\\''"], $val);
                        $conditions[] = "`{$escapedTable}`.`{$col}` LIKE '%{$escapedVal}%'";
                    }
                }
                if (!empty($conditions)) {
                    $where = ' WHERE ' . implode(' AND ', $conditions);
                }

                $escapedWhere = str_replace("'", "'\\''", $where);
                $countResult = $ssh->exec("sudo {$mysqlBin} --batch -e 'SELECT COUNT(*) AS cnt FROM `{$escapedDb}`.`{$escapedTable}`{$escapedWhere}' 2>&1 | tail -1");
                $totalRows = (int) trim($countResult['output'] ?? '0');

                $data = [];
                if ($totalRows > 0) {
                    $result = $ssh->exec("sudo {$mysqlBin} --batch -e 'SELECT * FROM `{$escapedDb}`.`{$escapedTable}`{$escapedWhere} LIMIT {$limit} OFFSET {$offset}' 2>&1 | tail -n +2");
                    if (trim($result['output'] ?? '')) {
                        foreach (explode("\n", trim($result['output'])) as $line) {
                            $data[] = explode("\t", $line);
                        }
                    }
                }

                $ssh->disconnect();
                $this->view->json([
                    'success' => true,
                    'columns' => $columns,
                    'rows' => $data,
                    'total' => $totalRows,
                    'page' => $page,
                    'per_page' => $limit,
                ]);
            } elseif ($action === 'users') {
                $userResult = $ssh->exec("sudo {$mysqlBin} --batch -e \"SELECT User, Host, password_expired FROM mysql.user ORDER BY User\"");
                $users = [];
                $userLines = explode("\n", trim($userResult['output'] ?? ''));
                array_shift($userLines);
                foreach ($userLines as $line) {
                    $parts = explode("\t", $line);
                    if (count($parts) >= 2 && $parts[0] !== '' && $parts[0] !== 'User') {
                        $users[] = ['user' => $parts[0], 'host' => $parts[1], 'expired' => $parts[2] ?? '', 'locked' => ''];
                    }
                }
                $grantsResult = $ssh->exec("sudo {$mysqlBin} --batch -e \"SELECT User, Host, Db FROM mysql.db ORDER BY User, Db\"");
                $grants = [];
                $grantLines = explode("\n", trim($grantsResult['output'] ?? ''));
                array_shift($grantLines);
                foreach ($grantLines as $line) {
                    $parts = explode("\t", $line);
                    if (count($parts) >= 3 && $parts[0] !== '') {
                        $grants[] = ['user' => $parts[0], 'host' => $parts[1], 'db' => $parts[2], 'table_priv' => '', 'column_priv' => ''];
                    }
                }
                $ssh->disconnect();
                $this->view->json(['success' => true, 'users' => $users, 'grants' => $grants]);
            } elseif ($action === 'create_user') {
                $newUser = trim($_POST['new_user'] ?? '');
                $newPass = trim($_POST['new_pass'] ?? '');
                $newHost = trim($_POST['new_host'] ?? 'localhost');
                if (empty($newUser) || empty($newPass)) {
                    $this->view->json(['success' => false, 'message' => 'Username and password are required']);
                }
                if (!preg_match('/^[a-zA-Z0-9_.-]+$/', $newUser)) {
                    $this->view->json(['success' => false, 'message' => 'Invalid username']);
                }
                $sqlCreate = "CREATE USER '{$newUser}'@'{$newHost}' IDENTIFIED BY '{$newPass}'";
                $b64 = base64_encode($sqlCreate);
                $result = $ssh->exec("echo {$b64} | base64 -d | sudo {$mysqlBin} 2>&1; echo EXIT:\$?");
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();
                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? "User '{$newUser}' created." : 'Failed to create user.',
                    'output' => trim($result['output']),
                ]);
            } elseif ($action === 'grant') {
                $grantUser = trim($_POST['grant_user'] ?? '');
                $grantDb = trim($_POST['grant_db'] ?? '');
                $grantHost = trim($_POST['grant_host'] ?? 'localhost');
                $grantPrivs = trim($_POST['grant_privs'] ?? 'ALL PRIVILEGES');
                if (empty($grantUser) || empty($grantDb)) {
                    $this->view->json(['success' => false, 'message' => 'User and database are required']);
                }
                $sqlGrant = "GRANT {$grantPrivs} ON {$grantDb}.* TO '{$grantUser}'@'{$grantHost}'; FLUSH PRIVILEGES";
                $b64 = base64_encode($sqlGrant);
                $result = $ssh->exec("echo {$b64} | base64 -d | sudo {$mysqlBin} 2>&1; echo EXIT:\$?");
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();
                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? "Privileges granted to '{$grantUser}' on {$grantDb}." : 'Failed to grant privileges.',
                ]);
            } elseif ($action === 'revoke') {
                $revokeUser = trim($_POST['revoke_user'] ?? '');
                $revokeDb = trim($_POST['revoke_db'] ?? '');
                $revokeHost = trim($_POST['revoke_host'] ?? 'localhost');
                if (empty($revokeUser) || empty($revokeDb)) {
                    $this->view->json(['success' => false, 'message' => 'User and database are required']);
                }
                $sqlRevoke = "REVOKE ALL PRIVILEGES ON {$revokeDb}.* FROM '{$revokeUser}'@'{$revokeHost}'; FLUSH PRIVILEGES";
                $b64 = base64_encode($sqlRevoke);
                $result = $ssh->exec("echo {$b64} | base64 -d | sudo {$mysqlBin} 2>&1; echo EXIT:\$?");
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();
                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? "Privileges revoked from '{$revokeUser}' on {$revokeDb}." : 'Failed to revoke privileges.',
                ]);
            } elseif ($action === 'query') {
                $query = trim($_POST['query'] ?? '');
                if (empty($query)) {
                    $this->view->json(['success' => false, 'message' => 'Query is required']);
                }
                $upper = strtoupper(substr(trim($query), 0, 6));
                $allowedPrefixes = ['SELECT', 'SHOW', 'DESC', 'EXPLAI'];
                $blocked = true;
                foreach ($allowedPrefixes as $prefix) {
                    if (str_starts_with($upper, $prefix)) {
                        $blocked = false;
                        break;
                    }
                }
                if ($blocked) {
                    $this->view->json(['success' => false, 'message' => 'Only SELECT/SHOW/DESCRIBE/EXPLAIN queries are allowed']);
                }
                $escaped = str_replace("'", "'\\''", $query);
                $result = $ssh->exec("sudo {$mysqlBin} -e '{$escaped}' 2>&1");
                $output = trim($result['output'] ?? '');
                $ssh->disconnect();
                $this->view->json([
                    'success' => !empty($output),
                    'message' => 'Query executed.',
                    'output' => $output,
                ]);
            } elseif ($action === 'drop_user') {
                $targetUser = trim($_POST['target_user'] ?? '');
                $targetHost = trim($_POST['target_host'] ?? 'localhost');
                if (empty($targetUser)) {
                    $this->view->json(['success' => false, 'message' => 'Username required']);
                }
                $sql = "DROP USER '{$targetUser}'@'{$targetHost}'";
                $b64 = base64_encode($sql);
                $result = $ssh->exec("echo {$b64} | base64 -d | sudo {$mysqlBin} 2>&1; echo EXIT:\$?");
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();
                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? "User '{$targetUser}' deleted." : 'Failed to delete user.',
                ]);
            } elseif ($action === 'change_pass') {
                $targetUser = trim($_POST['target_user'] ?? '');
                $targetHost = trim($_POST['target_host'] ?? 'localhost');
                $newPass = trim($_POST['new_pass'] ?? '');
                if (empty($targetUser) || empty($newPass)) {
                    $this->view->json(['success' => false, 'message' => 'Username and password required']);
                }
                $sql = "ALTER USER '{$targetUser}'@'{$targetHost}' IDENTIFIED BY '{$newPass}'";
                $b64 = base64_encode($sql);
                $result = $ssh->exec("echo {$b64} | base64 -d | sudo {$mysqlBin} 2>&1; echo EXIT:\$?");
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();
                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? "Password changed for '{$targetUser}'." : 'Failed to change password.',
                ]);
            }
        } catch (\Throwable $e) {
            $this->view->json([
                'success' => false,
                'message' => 'Failed: ' . $e->getMessage(),
            ]);
        }
    }

    public function sidebarServers(): void
    {
        $userId = (int) Session::get('user_id');
        $accessibleIds = $this->serverModel->getAccessibleServerIds($userId);

        $servers = $this->serverModel->findAll();
        $result = [];
        foreach ($servers as $s) {
            if (!in_array((int) $s['id'], $accessibleIds, true)) {
                continue;
            }
            $result[] = [
                'id' => $s['id'],
                'name' => $s['name'],
                'current_status' => $s['current_status'] ?? 'unknown',
                'ip_address' => $s['ip_address'] ?? '',
            ];
        }
        $this->view->json(['success' => true, 'data' => $result]);
    }

    public function services(int $id): void
    {
        $server = $this->requireServerAccess($id, 'viewer');

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }
            $this->auditService->logCredentialAccess($id, 'View services');

            $listResult = $ssh->exec("systemctl list-units --type=service --all --no-pager --no-legend 2>/dev/null | awk '{print \$1, \$3}'");
            $serviceNames = [];
            foreach (explode("\n", trim($listResult['output'] ?? '')) as $line) {
                if (preg_match('/^(\S+)\s+(\S+)/', $line, $m)) {
                    $serviceNames[$m[1]] = $m[2] === 'active' ? 'active' : ($m[2] === 'inactive' ? 'inactive' : 'other');
                }
            }
            $enabledNames = [];
            $enabledResult = $ssh->exec("systemctl list-unit-files --type=service --no-pager --no-legend 2>/dev/null | awk '\$2==\"enabled\"{print \$1}'");
            foreach (explode("\n", trim($enabledResult['output'] ?? '')) as $line) {
                $enabledNames[trim($line)] = true;
            }
            $services = [];
            foreach ($serviceNames as $name => $status) {
                $services[] = [
                    'name' => $name,
                    'status' => $status,
                    'enabled' => isset($enabledNames[$name]),
                ];
            }
            $ssh->disconnect();

            $this->view->display('servers.services', [
                'title' => 'Services - ' . $server['name'] . ' - ServerManager',
                'server' => $server,
                'services' => $services,
            ]);
        } catch (\Throwable $e) {
            Session::setFlash('error', 'Failed to get services: ' . $e->getMessage());
            $this->view->redirect("/servers/{$id}");
        }
    }

    public function servicesAction(int $id): void
    {
        $server = $this->serverModel->findById($id);
        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        $action = $_POST['action'] ?? '';
        $service = $_POST['service'] ?? '';
        $allowed = ['start', 'stop', 'restart', 'reload', 'enable', 'disable'];

        if (empty($service) || !in_array($action, $allowed, true)) {
            $this->view->json(['success' => false, 'message' => 'Invalid action or service']);
        }

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }
            $this->auditService->logServerAction($id, "service_{$action}", "{$service} {$action}");

            $result = $ssh->exec("sudo systemctl {$action} {$service} 2>&1; echo EXIT:\$?");
            $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
            $ssh->disconnect();

            $this->view->json([
                'success' => $exitCode === 0,
                'message' => $exitCode === 0 ? "Service '{$service}' {$action}ed." : "Failed to {$action} '{$service}'.",
                'output' => trim($result['output']),
            ]);
        } catch (\Throwable $e) {
            $this->view->json(['success' => false, 'message' => 'Failed: ' . $e->getMessage()]);
        }
    }

    public function systemUsers(int $id): void
    {
        $server = $this->requireServerAccess($id, 'viewer');

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }
            $this->auditService->logCredentialAccess($id, 'View system users');

            $data = [];

            $usersResult = $ssh->exec("awk -F: '\$3>=1000||\$3==0{printf \"%s|%s|%s|%s\\n\",\$1,\$3,\$6,\$7}' /etc/passwd 2>/dev/null | sort -t: -k2 -n | head -100");
            $users = [];
            foreach (explode("\n", trim($usersResult['output'] ?? '')) as $line) {
                $parts = explode("|", $line);
                if (count($parts) >= 4 && is_numeric($parts[1])) {
                    $users[] = [
                        'username' => $parts[0],
                        'uid' => (int) $parts[1],
                        'home' => $parts[2],
                        'shell' => $parts[3],
                    ];
                }
            }
            $data['users'] = $users;

            $groupsResult = $ssh->exec("awk -F: '{print \$1}' /etc/group 2>/dev/null | sort | head -50");
            $data['groups'] = array_filter(explode("\n", trim($groupsResult['output'] ?? '')));

            $totalResult = $ssh->exec("awk -F: '\$3>=1000||\$3==0{print \$1}' /etc/passwd 2>/dev/null | wc -l");
            $data['total_users'] = (int) trim($totalResult['output'] ?? '0');

            $lastResult = $ssh->exec("last -5 -w 2>/dev/null | head -10 || echo 'No login history'");
            $data['last_logins'] = trim($lastResult['output'] ?? '');

            $ssh->disconnect();

            $this->view->display('servers.system_users', [
                'title' => 'System Users - ' . $server['name'] . ' - ServerManager',
                'server' => $server,
                'sysusers' => $data,
            ]);
        } catch (\Throwable $e) {
            Session::setFlash('error', 'Failed to get users: ' . $e->getMessage());
            $this->view->redirect("/servers/{$id}");
        }
    }

    public function systemUsersAction(int $id): void
    {
        $server = $this->serverModel->findById($id);
        if (!$server) {
            $this->view->json(['success' => false, 'message' => 'Server not found'], 404);
        }

        $userId = (int) Session::get('user_id');
        $role = $this->serverModel->getUserRole($id, $userId);
        if ($role !== 'owner' && $role !== 'manager') {
            $this->view->json(['success' => false, 'message' => 'Forbidden'], 403);
        }

        $action = $_POST['action'] ?? '';
        $allowed = ['add', 'delete', 'lock', 'unlock', 'addgroup'];
        if (!in_array($action, $allowed, true)) {
            $this->view->json(['success' => false, 'message' => 'Invalid action']);
        }

        try {
            $ssh = new SSHService();
            if (!$ssh->connect($server)) {
                throw new \RuntimeException('Could not establish SSH connection');
            }
            $this->auditService->logServerAction($id, "sysuser_{$action}", $action);

            if ($action === 'add') {
                $username = trim($_POST['username'] ?? '');
                $password = trim($_POST['password'] ?? '');
                $groups = trim($_POST['groups'] ?? '');
                $createHome = !empty($_POST['create_home']);

                if (empty($username) || empty($password)) {
                    $this->view->json(['success' => false, 'message' => 'Username and password required']);
                }
                if (!preg_match('/^[a-z_][a-z0-9_-]*$/', $username)) {
                    $this->view->json(['success' => false, 'message' => 'Invalid username']);
                }

                $homeFlag = $createHome ? '-m' : '-M';
                $groupsFlag = '';
                if (!empty($groups)) {
                    $sanitized = preg_replace('/[^a-z0-9_,-]/', '', $groups);
                    $groupsFlag = " -G {$sanitized}";
                }
                $cmd = "sudo useradd {$homeFlag}{$groupsFlag} {$username} 2>&1; echo EXIT:\$?";
                $result = $ssh->exec($cmd);
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                if ($exitCode === 0) {
                    $b64 = base64_encode("{$username}:{$password}");
                    $ssh->exec("echo {$b64} | base64 -d | sudo chpasswd 2>&1");
                }

                $result = $ssh->exec($cmd);
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();

                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? "User '{$username}' created." : 'Failed to create user.',
                    'output' => trim($result['output']),
                ]);
            } elseif ($action === 'delete') {
                $username = trim($_POST['username'] ?? '');
                $removeHome = !empty($_POST['remove_home']);
                if (empty($username)) {
                    $this->view->json(['success' => false, 'message' => 'Username required']);
                }
                $rmFlag = $removeHome ? '-r' : '';
                $result = $ssh->exec("sudo userdel {$rmFlag} {$username} 2>&1; echo EXIT:\$?");
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();
                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? "User '{$username}' deleted." : 'Failed to delete user.',
                ]);
            } elseif ($action === 'lock') {
                $username = trim($_POST['username'] ?? '');
                if (empty($username)) $this->view->json(['success' => false, 'message' => 'Username required']);
                $result = $ssh->exec("sudo passwd -l {$username} 2>&1; echo EXIT:\$?");
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();
                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? "User '{$username}' locked." : 'Failed to lock user.',
                ]);
            } elseif ($action === 'unlock') {
                $username = trim($_POST['username'] ?? '');
                if (empty($username)) $this->view->json(['success' => false, 'message' => 'Username required']);
                $result = $ssh->exec("sudo passwd -u {$username} 2>&1; echo EXIT:\$?");
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();
                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? "User '{$username}' unlocked." : 'Failed to unlock user.',
                ]);
            } elseif ($action === 'addgroup') {
                $group = trim($_POST['group'] ?? '');
                if (empty($group)) $this->view->json(['success' => false, 'message' => 'Group name required']);
                $result = $ssh->exec("sudo groupadd -f {$group} 2>&1; echo EXIT:\$?");
                $exitCode = (int) trim(explode('EXIT:', $result['output'] ?? 'EXIT:-1')[1] ?? '-1');
                $ssh->disconnect();
                $this->view->json([
                    'success' => $exitCode === 0,
                    'message' => $exitCode === 0 ? "Group '{$group}' created." : 'Failed to create group.',
                ]);
            }
        } catch (\Throwable $e) {
            $this->view->json(['success' => false, 'message' => 'Failed: ' . $e->getMessage()]);
        }
    }

    public function metrics(int $id): void
    {
        $server = $this->requireServerAccess($id, 'viewer');

        $monitoringService = new MonitoringService();
        $metrics = $monitoringService->getLatestMetrics($id);

        $this->view->json([
            'success' => true,
            'data' => $metrics,
        ]);
    }

}
