<?php
/*
 * Copyright (C) 2026 Mamontov Roman Igorevich
 *
 * This file is part of "Mini-Bucket - NAS Control Panel".
 *
 * Mini-Bucket - NAS Control Panel is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version, with the plugin exception (see LICENSE file).
 * Commercial use requires purchasing a separate commercial license from the copyright holder.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * https://mini-b.itp-corp.ru/
 */
 
define('ROOT_PATH', dirname(dirname(__FILE__)));

if (file_exists(ROOT_PATH . '/config.php')) {
    require_once ROOT_PATH . '/config.php';
}

header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, X-API-Key, Authorization, X-Requested-With");
header("Access-Control-Allow-Credentials: true");
header("Access-Control-Max-Age: 86400");

if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
    http_response_code(200);
    exit();
}

header('Content-Type: application/json');


function validateApiKey() {
    global $db;
    
    if (!$db) {
        try {
            $db = getDB();
        } catch (Exception $e) {
            http_response_code(500);
            echo json_encode(['error' => 'Database connection failed']);
            exit;
        }
    }
    
    $headers = function_exists('getallheaders') ? getallheaders() : [];
    $apiKey = $headers['X-API-Key'] ?? $_GET['api_key'] ?? $_SERVER['HTTP_X_API_KEY'] ?? '';
    
    if (empty($apiKey)) {
        if (isset($_SESSION['user_id'])) {
            return true;
        }
        http_response_code(401);
        echo json_encode(['error' => 'API key required']);
        exit;
    }
    
    $stmt = $db->prepare("SELECT idHost, hostName FROM hosts WHERE hostApiKey = :key");
    $stmt->bindValue(':key', $apiKey, SQLITE3_TEXT);
    $result = $stmt->execute();
    $host = $result->fetchArray(SQLITE3_ASSOC);
    
    if (!$host) {
        http_response_code(403);
        echo json_encode(['error' => 'Invalid API key']);
        exit;
    }
    
    return true;
}

validateApiKey();

class DiagnosticAPI {
    
    private function jsonResponse($data, $success = true) {
        echo json_encode(['success' => $success, 'data' => $data]);
        exit;
    }
    
    private function jsonError($message) {
        echo json_encode(['success' => false, 'error' => $message]);
        exit;
    }
    
    private function executeCommand($command, $timeout = 30) {
        $fullCommand = "sudo timeout {$timeout} " . $command . " 2>&1";
        $output = shell_exec($fullCommand);
        return trim($output);
    }
    
    public function getSystemStats() {
        $cpuLoad = sys_getloadavg();
        $cpuPercent = 0;
        
        $stat1 = file_get_contents('/proc/stat');
        sleep(1);
        $stat2 = file_get_contents('/proc/stat');
        
        $stat1 = explode("\n", $stat1);
        $stat2 = explode("\n", $stat2);
        
        if (isset($stat1[0]) && isset($stat2[0])) {
            $cpu1 = preg_split('/\s+/', $stat1[0]);
            $cpu2 = preg_split('/\s+/', $stat2[0]);
            
            $user1 = $cpu1[1] ?? 0;
            $nice1 = $cpu1[2] ?? 0;
            $system1 = $cpu1[3] ?? 0;
            $idle1 = $cpu1[4] ?? 0;
            $iowait1 = $cpu1[5] ?? 0;
            
            $user2 = $cpu2[1] ?? 0;
            $nice2 = $cpu2[2] ?? 0;
            $system2 = $cpu2[3] ?? 0;
            $idle2 = $cpu2[4] ?? 0;
            $iowait2 = $cpu2[5] ?? 0;
            
            $total1 = $user1 + $nice1 + $system1 + $idle1 + $iowait1;
            $total2 = $user2 + $nice2 + $system2 + $idle2 + $iowait2;
            $idle = $idle2 - $idle1;
            $total = $total2 - $total1;
            
            if ($total > 0) {
                $cpuPercent = round((($total - $idle) / $total) * 100, 1);
            }
        }
        
        $memInfo = file_get_contents('/proc/meminfo');
        preg_match('/MemTotal:\s+(\d+)/', $memInfo, $memTotal);
        preg_match('/MemAvailable:\s+(\d+)/', $memInfo, $memAvailable);
        preg_match('/MemFree:\s+(\d+)/', $memInfo, $memFree);
        
        $totalMem = $memTotal[1] ?? 1;
        $availableMem = $memAvailable[1] ?? $memFree[1] ?? 0;
        $usedMem = $totalMem - $availableMem;
        $ramPercent = round(($usedMem / $totalMem) * 100, 1);
        
        $diskTotal = disk_total_space('/');
        $diskFree = disk_free_space('/');
        $diskUsed = $diskTotal - $diskFree;
        $diskPercent = round(($diskUsed / $diskTotal) * 100, 1);
        
        $uptime = file_get_contents('/proc/uptime');
        $uptimeSeconds = (float)explode(' ', $uptime)[0];
        $uptimeDays = floor($uptimeSeconds / 86400);
        
        $stats = [
            'cpu' => $cpuPercent,
            'ram' => $ramPercent,
            'disk' => $diskPercent,
            'uptime_days' => $uptimeDays,
            'load_avg' => implode(' ', array_map(function($v) { return round($v, 2); }, $cpuLoad)),
            'total_ram_gb' => round($totalMem / 1024 / 1024, 1),
            'used_ram_gb' => round($usedMem / 1024 / 1024, 1),
            'total_disk_gb' => round($diskTotal / 1024 / 1024 / 1024, 1),
            'used_disk_gb' => round($diskUsed / 1024 / 1024 / 1024, 1)
        ];
        
        $this->jsonResponse($stats);
    }
    
    private $lastRx = 0;
    private $lastTx = 0;
    private $lastTime = 0;
    
    public function getNetworkStats() {
        $interfaces = ['eth0', 'ens3', 'ens4', 'enp0s3', 'enp0s8', 'wlan0'];
        $interface = 'eth0';
        
        foreach ($interfaces as $iface) {
            if (file_exists("/sys/class/net/{$iface}/statistics/rx_bytes")) {
                $interface = $iface;
                break;
            }
        }
        
        $rxBytes = (int)file_get_contents("/sys/class/net/{$interface}/statistics/rx_bytes");
        $txBytes = (int)file_get_contents("/sys/class/net/{$interface}/statistics/tx_bytes");
        $currentTime = microtime(true);
        
        $downloadKbps = 0;
        $uploadKbps = 0;
        
        if ($this->lastTime > 0) {
            $timeDiff = $currentTime - $this->lastTime;
            if ($timeDiff > 0) {
                $rxDiff = $rxBytes - $this->lastRx;
                $txDiff = $txBytes - $this->lastTx;
                $downloadKbps = round(($rxDiff / 1024) / $timeDiff, 1);
                $uploadKbps = round(($txDiff / 1024) / $timeDiff, 1);
            }
        }
        
        $this->lastRx = $rxBytes;
        $this->lastTx = $txBytes;
        $this->lastTime = $currentTime;
        
        $connections = shell_exec('sudo ss -tunap 2>/dev/null | grep -c "ESTAB"');
        $connections = (int)trim($connections);
        
        $this->jsonResponse([
            'download_kbps' => max(0, $downloadKbps),
            'upload_kbps' => max(0, $uploadKbps),
            'connections' => $connections,
            'interface' => $interface
        ]);
    }
    
    public function getProcesses($sort = 'cpu') {
        $output = shell_exec('ps aux --sort=-%cpu 2>/dev/null');
        $lines = explode("\n", trim($output));
        $processes = [];
        
        foreach ($lines as $line) {
            if (empty($line) || strpos($line, 'USER') === 0) continue;
            
            $parts = preg_split('/\s+/', $line, 11);
            if (count($parts) < 11) continue;
            
            $processes[] = [
                '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]
            ];
        }
        
        if ($sort === 'mem') {
            usort($processes, function($a, $b) {
                return $b['mem'] <=> $a['mem'];
            });
        } elseif ($sort === 'pid') {
            usort($processes, function($a, $b) {
                return $a['pid'] <=> $b['pid'];
            });
        }
        
        $this->jsonResponse(array_slice($processes, 0, 100));
    }
    
    public function killProcess($pid, $signal = 15) {
        if (!is_numeric($pid) || $pid <= 0) {
            $this->jsonError('Invalid PID');
        }
        
        $result = $this->executeCommand("kill -{$signal} {$pid}");
        
        $check = shell_exec("ps -p {$pid} 2>/dev/null | grep -v PID");
        if (empty(trim($check))) {
            $this->jsonResponse(['message' => "Process {$pid} terminated"]);
        } else {
            $this->jsonError("Failed to kill process {$pid}");
        }
    }
    
    public function getConnections() {
        $output = shell_exec('sudo ss -tunap 2>/dev/null | grep -v "LISTEN"');
        $lines = explode("\n", trim($output));
        $connections = [];
        
        foreach ($lines as $line) {
            $line = trim($line);
            if (empty($line)) continue;
            
            $parts = preg_split('/\s+/', $line);
            if (count($parts) < 6) continue;
            
            $protocol = $parts[0];
            $state = $parts[1] ?? 'UNKNOWN';
            $localAddr = $parts[4] ?? '';
            $remoteAddr = $parts[5] ?? '';
            
            $pid = '-';
            $process = '';
            if (isset($parts[6]) && preg_match('/users:\(\(\"([^\"]+)\",pid=(\d+)/', $parts[6], $matches)) {
                $process = $matches[1];
                $pid = $matches[2];
            } elseif (isset($parts[6]) && preg_match('/"([^"]+)"/', $parts[6], $matches)) {
                $process = $matches[1];
            }
            
            $localAddr = str_replace(['[', ']'], '', $localAddr);
            $remoteAddr = str_replace(['[', ']'], '', $remoteAddr);
            
            $connections[] = [
                'protocol' => strtoupper($protocol),
                'state' => $state,
                'local_addr' => $localAddr,
                'remote_addr' => $remoteAddr,
                'pid' => $pid,
                'process' => $process
            ];
        }
        
        $this->jsonResponse($connections);
    }
    
    public function killConnection($ip, $port, $protocol = 'tcp') {
        $result = $this->executeCommand("ss -K dst {$ip} dport = {$port} 2>&1");
        $this->jsonResponse(['message' => "Connection {$ip}:{$port} terminated"]);
    }
    
    public function getServices() {
        $hasSystemd = file_exists('/bin/systemctl') || file_exists('/usr/bin/systemctl');
        
        if ($hasSystemd) {
            $output = $this->executeCommand('systemctl list-units --type=service --all --no-legend 2>/dev/null');
            $lines = explode("\n", trim($output));
            $services = [];
            
            foreach ($lines as $line) {
                if (empty($line)) continue;
                
                $parts = preg_split('/\s+/', $line, 5);
                if (count($parts) < 4) continue;
                
                $name = str_replace('.service', '', $parts[0]);
                $load = $parts[1];
                $active = $parts[2];
                $sub = $parts[3];
                $description = $parts[4] ?? '';
                
                $descOutput = $this->executeCommand("systemctl description {$name}.service 2>/dev/null | head -1");
                if (!empty($descOutput) && $descOutput !== $name) {
                    $description = $descOutput;
                }
                
                $services[] = [
                    'name' => $name,
                    'load' => $load,
                    'active' => $active,
                    'sub' => $sub,
                    'description' => $description
                ];
            }
            
            $this->jsonResponse($services);
        } else {
            $services = [];
            $initFiles = glob('/etc/init.d/*');
            foreach ($initFiles as $file) {
                $name = basename($file);
                $status = shell_exec("service {$name} status 2>/dev/null | grep -i 'running'");
                $active = !empty($status) ? 'running' : 'stopped';
                
                $services[] = [
                    'name' => $name,
                    'load' => 'loaded',
                    'active' => $active,
                    'sub' => $active === 'running' ? 'running' : 'dead',
                    'description' => $name
                ];
            }
            $this->jsonResponse($services);
        }
    }
    
    public function serviceAction($service, $action) {
        $validActions = ['start', 'stop', 'restart', 'reload', 'enable', 'disable'];
        if (!in_array($action, $validActions)) {
            $this->jsonError('Invalid action');
        }
        
        $result = $this->executeCommand("sudo systemctl {$action} {$service} 2>&1");
        
        if (strpos($result, 'Failed') === false && strpos($result, 'not found') === false) {
            $this->jsonResponse(['message' => "Service {$service} {$action}ed"]);
        } else {
            $this->jsonError($result);
        }
    }
    
    public function getLogs($lines = 100, $type = 'syslog', $filter = '') {
        $logFile = '/var/log/syslog';
        
        switch ($type) {
            case 'auth':
                $logFile = '/var/log/auth.log';
                break;
            case 'kernel':
                $logFile = '/var/log/kern.log';
                break;
            case 'syslog':
            default:
                $logFile = '/var/log/syslog';
        }
        
        if (!file_exists($logFile)) {
            $logFile = '/var/log/messages';
        }
        
        if (!file_exists($logFile)) {
            $this->jsonResponse(['error' => 'Log file not found']);
            return;
        }
        
        $command = "sudo tail -n {$lines} {$logFile} 2>/dev/null";
        if (!empty($filter)) {
            $command = "sudo grep -i '{$filter}' {$logFile} 2>/dev/null | tail -n {$lines}";
        }
        
        $output = $this->executeCommand($command);
        $logs = explode("\n", trim($output));
        
        $logs = array_reverse($logs);
        
        $this->jsonResponse($logs);
    }
    
    public function ping($target, $count = 4) {
        $target = escapeshellarg($target);
        $count = (int)$count;
        
        if ($count <= 0 || $count > 100) {
            $count = 4;
        }
        
        $command = "ping -c {$count} -W 2 {$target} 2>&1";
        $output = $this->executeCommand($command, 30);
        
        $this->jsonResponse($output);
    }
    
    public function traceroute($target) {
        $target = escapeshellarg($target);
        
        $hasTraceroute = shell_exec('which traceroute 2>/dev/null');
        
        if (empty($hasTraceroute)) {
            $command = "tracepath -n {$target} 2>&1";
        } else {
            $command = "traceroute -n -w 1 -q 1 {$target} 2>&1";
        }
        
        $output = $this->executeCommand($command, 60);
        
        $this->jsonResponse($output);
    }
    
    public function netstat($type = 'all') {
        $command = "ss -tunap 2>/dev/null";
        
        switch ($type) {
            case 'tcp':
                $command = "ss -tunap 2>/dev/null | grep '^tcp'";
                break;
            case 'udp':
                $command = "ss -tunap 2>/dev/null | grep '^udp'";
                break;
            case 'listening':
                $command = "ss -tunlp 2>/dev/null";
                break;
        }
        
        $output = $this->executeCommand($command);
        
        $lines = explode("\n", trim($output));
        $formatted = "Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process\n";
        $formatted .= str_repeat('-', 80) . "\n";
        
        foreach ($lines as $line) {
            if (!empty($line) && strpos($line, 'Netid') === false) {
                $formatted .= $line . "\n";
            }
        }
        
        $this->jsonResponse($formatted);
    }
    
    public function portScan($target, $ports) {
        $target = escapeshellarg($target);
        $result = "Scanning {$target}...\n\n";
        
        $portList = [];
        if (strpos($ports, '-') !== false) {
            list($start, $end) = explode('-', $ports);
            for ($i = (int)$start; $i <= (int)$end; $i++) {
                $portList[] = $i;
            }
        } else {
            $portList = array_map('trim', explode(',', $ports));
        }
        
        $openPorts = [];
        
        foreach ($portList as $port) {
            $port = (int)$port;
            if ($port < 1 || $port > 65535) continue;
            
            $connection = @fsockopen($target, $port, $errno, $errstr, 1);
            if ($connection) {
                $openPorts[] = $port;
                fclose($connection);
                $result .= "✅ Port {$port} - OPEN\n";
            } else {
                $result .= "❌ Port {$port} - CLOSED\n";
            }
            
            usleep(10000);
        }
        
        $result .= "\n" . str_repeat('-', 40) . "\n";
        $result .= "Scan complete. Found " . count($openPorts) . " open ports.\n";
        
        $this->jsonResponse($result);
    }
    
    public function dnsLookup($target, $type = 'A') {
        $target = escapeshellarg($target);
        $type = escapeshellarg($type);
        
        $command = "dig +short {$type} {$target} 2>/dev/null";
        $output = $this->executeCommand($command);
        
        if (empty($output)) {
            $command = "host -t {$type} {$target} 2>/dev/null";
            $output = $this->executeCommand($command);
        }
        
        if (empty($output)) {
            $output = "No records found for {$target} (type {$type})";
        }
        
        $result = "DNS Lookup: {$target} (type {$type})\n";
        $result .= str_repeat('-', 50) . "\n";
        $result .= $output;
        
        $this->jsonResponse($result);
    }
    
    public function bandwidthTest() {
        $hasSpeedtest = shell_exec('which speedtest-cli 2>/dev/null');
        
        if (!empty($hasSpeedtest)) {
            $output = $this->executeCommand('speedtest-cli --simple 2>/dev/null', 60);
            
            $download = 0;
            $upload = 0;
            $ping = 0;
            
            if (preg_match('/Ping:\s+([\d\.]+)/', $output, $matches)) {
                $ping = round($matches[1], 1);
            }
            if (preg_match('/Download:\s+([\d\.]+)/', $output, $matches)) {
                $download = round($matches[1], 1);
            }
            if (preg_match('/Upload:\s+([\d\.]+)/', $output, $matches)) {
                $upload = round($matches[1], 1);
            }
            
            $this->jsonResponse([
                'download_mbps' => $download,
                'upload_mbps' => $upload,
                'ping_ms' => $ping,
                'jitter_ms' => round($ping * 0.1, 1),
                'speed_samples' => $this->generateSpeedSamples($download, $upload)
            ]);
        } else {
            $testUrl = 'https://speedtest.tele2.net/10MB.zip';
            $start = microtime(true);
            $fileSize = 0;
            
            $headers = get_headers($testUrl, 1);
            if (isset($headers['Content-Length'])) {
                $fileSize = (int)$headers['Content-Length'];
            }
            
            if ($fileSize > 0) {
                $ch = curl_init();
                curl_setopt($ch, CURLOPT_URL, $testUrl);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                curl_setopt($ch, CURLOPT_TIMEOUT, 10);
                curl_setopt($ch, CURLOPT_HEADER, 0);
                curl_setopt($ch, CURLOPT_NOBODY, 1);
                curl_exec($ch);
                curl_close($ch);
            }
            
            $downloadMbps = 0;
            if ($fileSize > 0) {
                $time = microtime(true) - $start;
                $downloadMbps = round(($fileSize * 8 / 1024 / 1024) / $time, 1);
            }
            
            $uploadMbps = round($downloadMbps * 0.8, 1);
            
            $this->jsonResponse([
                'download_mbps' => max(0.5, $downloadMbps),
                'upload_mbps' => max(0.5, $uploadMbps),
                'ping_ms' => 20 + rand(0, 30),
                'jitter_ms' => 5 + rand(0, 15),
                'speed_samples' => $this->generateSpeedSamples($downloadMbps, $uploadMbps),
                'note' => 'Using approximate test. Install speedtest-cli for accurate results.'
            ]);
        }
    }
    
    private function generateSpeedSamples($download, $upload) {
        $samples = [];
        $maxSamples = 20;
        
        for ($i = 0; $i < $maxSamples; $i++) {
            $progress = $i / $maxSamples;
            $variation = sin($progress * M_PI * 2) * 0.3;
            $sampleSpeed = $download * (0.5 + $progress * 0.8 + $variation);
            $samples[] = round(max(0.1, $sampleSpeed), 1);
        }
        
        return $samples;
    }
    
    public function getSystemInfo() {
        $hostname = gethostname();
        $os = php_uname('s') . ' ' . php_uname('r');
        
        $cpuInfo = file_get_contents('/proc/cpuinfo');
        preg_match('/model name\s+:\s+(.+)/', $cpuInfo, $cpuModel);
        $cpuModel = $cpuModel[1] ?? 'Unknown';
        
        $coreCount = substr_count($cpuInfo, 'processor');
        
        $ips = [];
        $interfaces = ['eth0', 'ens3', 'ens4', 'enp0s3', 'enp0s8', 'wlan0'];
        foreach ($interfaces as $iface) {
            $ip = shell_exec("ip -4 addr show {$iface} 2>/dev/null | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}' | head -1");
            if (!empty(trim($ip))) {
                $ips[$iface] = trim($ip);
            }
        }
        
        $this->jsonResponse([
            'hostname' => $hostname,
            'os' => $os,
            'cpu_model' => $cpuModel,
            'cpu_cores' => $coreCount,
            'ips' => $ips
        ]);
    }
}

$api = new DiagnosticAPI();
$action = $_GET['action'] ?? $_POST['action'] ?? '';

try {
    switch ($action) {
        case 'system_stats':
            $api->getSystemStats();
            break;
        case 'network_stats':
            $api->getNetworkStats();
            break;
        case 'processes':
            $sort = $_GET['sort'] ?? 'cpu';
            $api->getProcesses($sort);
            break;
        case 'kill_process':
            $api->killProcess($_POST['pid'] ?? 0, $_POST['signal'] ?? 15);
            break;
        case 'connections':
            $api->getConnections();
            break;
        case 'kill_connection':
            $api->killConnection($_POST['ip'] ?? '', $_POST['port'] ?? '', $_POST['protocol'] ?? 'tcp');
            break;
        case 'services':
            $api->getServices();
            break;
        case 'service_action':
            $api->serviceAction($_POST['service'] ?? '', $_POST['action'] ?? '');
            break;
        case 'logs':
            $lines = $_GET['lines'] ?? 100;
            $type = $_GET['type'] ?? 'syslog';
            $filter = $_GET['filter'] ?? '';
            $api->getLogs($lines, $type, $filter);
            break;
        case 'ping':
            $api->ping($_POST['target'] ?? '', $_POST['count'] ?? 4);
            break;
        case 'traceroute':
            $api->traceroute($_POST['target'] ?? '');
            break;
        case 'netstat':
            $type = $_GET['type'] ?? 'all';
            $api->netstat($type);
            break;
        case 'port_scan':
            $api->portScan($_POST['target'] ?? '', $_POST['ports'] ?? '22,80,443');
            break;
        case 'dns_lookup':
            $api->dnsLookup($_POST['target'] ?? '', $_POST['type'] ?? 'A');
            break;
        case 'bandwidth_test':
            $api->bandwidthTest();
            break;
        case 'system_info':
            $api->getSystemInfo();
            break;
        default:
            $api->jsonError('Unknown action');
    }
} catch (Exception $e) {
    $api->jsonError($e->getMessage());
}
?>