<?php
/**
 * API: System-Info für Einstellungen (IP, letzter Cron-Lauf, Status).
 * action=run:          Alle Cronjobs einmal manuell ausführen.
 * action=run_job:      Einzelnen Cronjob ausführen (job=name).
 * action=list_jobs:    Liste aller verfügbaren Cronjobs liefern.
 * action=reset_cache:  Wetter- und TTS-Cache leeren.
 * action=cleanup_scan: Verfügbare Cleanup-Elemente und Größen ermitteln.
 * action=cleanup_run:  Einzelnes Cleanup-Ziel bereinigen (POST: target).
 * action=system_cmd:   System-Befehl (POST: cmd = reboot|shutdown).
 */
require __DIR__ . '/../config.php';
require FUNCTIONS_ROOT . '/functions_common.php';

header('Content-Type: application/json; charset=utf-8');

$storageRoot = defined('STORAGE_ROOT') ? rtrim(STORAGE_ROOT, DIRECTORY_SEPARATOR) : (dirname(__DIR__) . DIRECTORY_SEPARATOR . 'storage');
$stateFile = $storageRoot . DIRECTORY_SEPARATOR . 'cronjob_master.json';
$weckerRoot = dirname(__DIR__);
$cronjobDir = $weckerRoot . DIRECTORY_SEPARATOR . 'cronjob';

/** Menschenlesbare Labels für bekannte Job-Namen */
function cron_job_label(string $name): string {
    $labels = [
        'tts_vorwecker'  => 'TTS Vorwecker',
        'refresh_wetter' => 'Wetter aktualisieren',
        'timer_pruefung' => 'Timer-Prüfung',
        'collect_stats'  => 'Statistiken erfassen',
    ];
    return $labels[$name] ?? ucwords(str_replace('_', ' ', $name));
}

// Liste aller verfügbaren Cronjob-Skripte (Dateisystem + DB-Info)
if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'list_jobs') {
    $jobs = [];
    if (is_dir($cronjobDir)) {
        $files = @glob($cronjobDir . DIRECTORY_SEPARATOR . '*.php');
        if ($files) {
            sort($files);
            // DB-Infos laden (optional, wenn verfügbar)
            $dbInfo = [];
            $pdo = $GLOBALS['pdo'] ?? null;
            if ($pdo) {
                try {
                    $stmt = $pdo->query("SELECT name, interval_sekunden, letzte_ausfuehrung, aktiv FROM cronjobs ORDER BY id");
                    if ($stmt) {
                        while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                            $dbInfo[$row['name']] = $row;
                        }
                    }
                } catch (Throwable $e) {}
            }
            foreach ($files as $file) {
                $name = basename($file, '.php');
                $safeName = preg_replace('/[^a-z0-9_-]/', '', strtolower($name));
                if ($safeName === '') continue;
                $db = $dbInfo[$safeName] ?? null;
                $jobs[] = [
                    'name'               => $safeName,
                    'label'              => cron_job_label($safeName),
                    'interval_sekunden'  => $db ? (int)$db['interval_sekunden'] : null,
                    'letzte_ausfuehrung' => $db ? ($db['letzte_ausfuehrung'] ?? null) : null,
                    'aktiv'              => $db ? (bool)$db['aktiv'] : true,
                ];
            }
        }
    }
    function_json_antwort(true, ['jobs' => $jobs]);
    exit;
}

// Einzelnen Cronjob ausführen
if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'run_job') {
    $jobName = isset($_REQUEST['job']) ? preg_replace('/[^a-z0-9_-]/', '', strtolower(trim((string)$_REQUEST['job']))) : '';
    if ($jobName === '') {
        function_json_antwort(false, ['message' => 'Kein Job angegeben.']);
        exit;
    }
    $script = $cronjobDir . DIRECTORY_SEPARATOR . $jobName . '.php';
    if (!is_file($script)) {
        function_json_antwort(false, ['message' => 'Job-Skript nicht gefunden: ' . $jobName]);
        exit;
    }
    $php = (defined('PHP_BINARY') && PHP_BINARY !== '') ? PHP_BINARY : 'php';
    $cmd = escapeshellarg($php) . ' ' . escapeshellarg($script) . ' 2>&1';
    $output = [];
    $code = -1;
    @exec($cmd, $output, $code);
    // letzte_ausfuehrung in DB aktualisieren
    $pdo = $GLOBALS['pdo'] ?? null;
    if ($pdo) {
        try {
            $pdo->prepare("UPDATE cronjobs SET letzte_ausfuehrung = NOW() WHERE name = ?")->execute([$jobName]);
        } catch (Throwable $e) {}
    }
    if ($code === 0) {
        function_json_antwort(true, ['message' => cron_job_label($jobName) . ' ausgeführt.']);
    } else {
        $err = trim(implode("\n", $output));
        function_json_antwort(false, ['message' => $err !== '' ? $err : $jobName . ' fehlgeschlagen.']);
    }
    exit;
}

// Cleanup-Scan: Verfügbare Elemente und Größen ermitteln
if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'cleanup_scan') {
    $dirSizeFn = function(string $dir): int {
        if (!is_dir($dir)) return 0;
        if (PHP_OS_FAMILY === 'Linux' && function_exists('shell_exec')) {
            $out = @shell_exec('du -sb ' . escapeshellarg($dir) . ' 2>/dev/null');
            if ($out !== null && preg_match('/^(\d+)/', trim($out), $m)) return (int) $m[1];
        }
        $size = 0;
        try {
            $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS));
            foreach ($it as $f) { if ($f->isFile()) $size += $f->getSize(); }
        } catch (Throwable $e) {}
        return $size;
    };

    // Backups
    $backupList = [];
    $scan = @scandir($weckerRoot);
    if ($scan) {
        foreach ($scan as $f) {
            if (strpos($f, 'backup_') === 0 && is_dir($weckerRoot . DIRECTORY_SEPARATOR . $f)) {
                $backupList[] = $f;
            }
        }
    }
    rsort($backupList, SORT_STRING);
    $backupSize = 0;
    foreach ($backupList as $b) {
        $backupSize += $dirSizeFn($weckerRoot . DIRECTORY_SEPARATOR . $b);
    }

    // Logs
    $logsDir = $weckerRoot . DIRECTORY_SEPARATOR . 'logs';
    $logCount = 0;
    $logSize = 0;
    if (is_dir($logsDir)) {
        $files = @glob($logsDir . DIRECTORY_SEPARATOR . '*');
        if ($files) {
            foreach ($files as $f) {
                if (is_file($f)) { $logCount++; $logSize += (int) filesize($f); }
            }
        }
    }

    // Update-ZIP
    $zipPath = $weckerRoot . DIRECTORY_SEPARATOR . 'update.zip';
    $zipExists = is_file($zipPath);

    // Wetter-Cache
    $wetterDir = $storageRoot . DIRECTORY_SEPARATOR . 'wetter_cache';
    $wetterCount = 0;
    $wetterSize = 0;
    if (is_dir($wetterDir)) {
        $files = @glob($wetterDir . DIRECTORY_SEPARATOR . '*.json');
        if ($files) {
            foreach ($files as $f) {
                if (is_file($f)) { $wetterCount++; $wetterSize += (int) filesize($f); }
            }
        }
    }

    function_json_antwort(true, [
        'items' => [
            'backups'     => ['count' => count($backupList), 'size' => $backupSize, 'list' => $backupList],
            'logs'        => ['count' => $logCount, 'size' => $logSize],
            'update_zip'  => ['exists' => $zipExists, 'size' => $zipExists ? (int) filesize($zipPath) : 0],
            'wetter_cache' => ['count' => $wetterCount, 'size' => $wetterSize],
        ]
    ]);
    exit;
}

// Cleanup-Run: Einzelnes Ziel bereinigen
if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'cleanup_run') {
    $target = isset($_POST['target']) ? trim((string) $_POST['target']) : '';
    $log = [];
    $ok = true;

    $rmdirFn = function(string $dir) use (&$log): bool {
        if (!is_dir($dir)) return true;
        try {
            $it = new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS),
                RecursiveIteratorIterator::CHILD_FIRST
            );
            foreach ($it as $f) {
                $f->isDir() ? @rmdir($f->getPathname()) : @unlink($f->getPathname());
            }
        } catch (Throwable $e) {}
        return @rmdir($dir);
    };

    if ($target === 'backups_except_newest') {
        $scan = @scandir($weckerRoot);
        $backups = [];
        if ($scan) {
            foreach ($scan as $f) {
                if (strpos($f, 'backup_') === 0 && is_dir($weckerRoot . DIRECTORY_SEPARATOR . $f)) {
                    $backups[] = $f;
                }
            }
        }
        rsort($backups, SORT_STRING);
        $toDelete = array_slice($backups, 1);
        if (empty($toDelete)) {
            $log[] = 'Keine alten Backups zum Löschen vorhanden.';
        } else {
            foreach ($toDelete as $b) {
                $path = $weckerRoot . DIRECTORY_SEPARATOR . $b;
                if ($rmdirFn($path)) { $log[] = 'Gelöscht: ' . $b; }
                else { $log[] = 'Fehler beim Löschen: ' . $b; $ok = false; }
            }
        }
    } elseif ($target === 'backups_all') {
        $scan = @scandir($weckerRoot);
        $backups = [];
        if ($scan) {
            foreach ($scan as $f) {
                if (strpos($f, 'backup_') === 0 && is_dir($weckerRoot . DIRECTORY_SEPARATOR . $f)) {
                    $backups[] = $f;
                }
            }
        }
        if (empty($backups)) {
            $log[] = 'Keine Backups gefunden.';
        } else {
            foreach ($backups as $b) {
                $path = $weckerRoot . DIRECTORY_SEPARATOR . $b;
                if ($rmdirFn($path)) { $log[] = 'Gelöscht: ' . $b; }
                else { $log[] = 'Fehler beim Löschen: ' . $b; $ok = false; }
            }
        }
    } elseif ($target === 'logs') {
        $logsDir = $weckerRoot . DIRECTORY_SEPARATOR . 'logs';
        $deleted = 0;
        if (is_dir($logsDir)) {
            $files = @glob($logsDir . DIRECTORY_SEPARATOR . '*');
            if ($files) {
                foreach ($files as $f) {
                    if (is_file($f)) {
                        if (@unlink($f)) $deleted++;
                        else { $log[] = 'Fehler: ' . basename($f); $ok = false; }
                    }
                }
            }
        }
        $log[] = $deleted . ' Log-Datei(en) gelöscht.';
    } elseif ($target === 'update_zip') {
        $zipPath = $weckerRoot . DIRECTORY_SEPARATOR . 'update.zip';
        if (is_file($zipPath)) {
            if (@unlink($zipPath)) $log[] = 'update.zip gelöscht.';
            else { $log[] = 'Fehler beim Löschen von update.zip.'; $ok = false; }
        } else {
            $log[] = 'update.zip nicht vorhanden.';
        }
    } elseif ($target === 'wetter_cache') {
        $wetterDir = $storageRoot . DIRECTORY_SEPARATOR . 'wetter_cache';
        $deleted = 0;
        if (is_dir($wetterDir)) {
            $files = @glob($wetterDir . DIRECTORY_SEPARATOR . '*.json');
            if ($files) {
                foreach ($files as $f) {
                    if (is_file($f) && @unlink($f)) $deleted++;
                }
            }
        }
        $log[] = $deleted . ' Wetter-Cache-Datei(en) gelöscht.';
    } else {
        function_json_antwort(false, ['message' => 'Unbekanntes Ziel.', 'log' => []]);
        exit;
    }

    function_json_antwort($ok, ['log' => $log, 'message' => implode(' ', $log)]);
    exit;
}

// Cache zurücksetzen (Wetter-Cache + TTS-Cache)
if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'reset_cache') {
    $errors = [];
    $wetterDir = $storageRoot . DIRECTORY_SEPARATOR . 'wetter_cache';
    if (is_dir($wetterDir)) {
        $files = @glob($wetterDir . DIRECTORY_SEPARATOR . '*');
        if ($files !== false) {
            foreach ($files as $f) {
                if (is_file($f)) {
                    @unlink($f);
                }
            }
        }
    }
    $pdo = $GLOBALS['pdo'] ?? null;
    $ttsDir = $storageRoot . DIRECTORY_SEPARATOR . 'tts';
    if ($pdo && is_dir($ttsDir)) {
        try {
            $stmt = $pdo->query('SELECT audio_dateiname FROM wecker_tts_cache');
            if ($stmt) {
                while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                    if (!empty($row['audio_dateiname'])) {
                        $path = $ttsDir . DIRECTORY_SEPARATOR . basename($row['audio_dateiname']);
                        if (is_file($path) && basename($path) !== 'tts_models.json') {
                            @unlink($path);
                        }
                    }
                }
            }
            $pdo->exec('TRUNCATE TABLE wecker_tts_cache');
        } catch (Throwable $e) {
            $errors[] = 'TTS-Cache: ' . $e->getMessage();
        }
    }
    if (count($errors) > 0) {
        function_json_antwort(false, ['message' => implode(' ', $errors)]);
    } else {
        function_json_antwort(true, ['message' => 'Cache wurde zurückgesetzt (Wetter + TTS).']);
    }
    exit;
}

// System-Befehl: Reboot oder Shutdown
if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'system_cmd') {
    $cmd = isset($_POST['cmd']) ? trim((string) $_POST['cmd']) : '';
    if (!in_array($cmd, ['reboot', 'shutdown'], true)) {
        function_json_antwort(false, ['message' => 'Unbekannter Befehl.']);
        exit;
    }
    if (PHP_OS_FAMILY !== 'Linux') {
        function_json_antwort(false, ['message' => 'Nur auf Linux verfügbar.']);
        exit;
    }
    $rebootBin  = is_executable('/sbin/reboot')       ? '/sbin/reboot'       : '/usr/sbin/reboot';
    $shutdownBin = is_executable('/sbin/shutdown')     ? '/sbin/shutdown'     : '/usr/sbin/shutdown';
    if ($cmd === 'reboot') {
        $exec = 'sudo ' . $rebootBin . ' 2>&1';
        $label = 'Neustart';
    } else {
        $exec = 'sudo ' . $shutdownBin . ' -h now 2>&1';
        $label = 'Shutdown';
    }
    $out = [];
    $code = -1;
    @exec($exec, $out, $code);
    if ($code === 0 || $code === 1) {
        function_json_antwort(true, ['message' => $label . ' wird ausgeführt…']);
    } else {
        $err = trim(implode(' ', $out));
        function_json_antwort(false, ['message' => $err !== '' ? $err : $label . ' fehlgeschlagen (Exit ' . $code . '). Sudoers-Eintrag prüfen.']);
    }
    exit;
}

// CPU-Temperatur lesen
if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'cpu_temp') {
    $temp = null;
    // Raspberry Pi / Linux: sysfs thermal zone
    $sysfsPath = '/sys/class/thermal/thermal_zone0/temp';
    if (is_readable($sysfsPath)) {
        $raw = (int)trim(file_get_contents($sysfsPath));
        $temp = round($raw / 1000, 1); // Milligrad → Grad Celsius
    } elseif (PHP_OS_FAMILY === 'Linux') {
        // Fallback: vcgencmd (Raspberry Pi)
        $out = [];
        @exec('vcgencmd measure_temp 2>/dev/null', $out);
        if (!empty($out[0]) && preg_match('/temp=([\d.]+)/', $out[0], $m)) {
            $temp = (float)$m[1];
        }
    }
    if ($temp !== null) {
        echo json_encode(['ok' => true, 'temp' => $temp]);
    } else {
        echo json_encode(['ok' => false, 'temp' => null]);
    }
    exit;
}

// Stromverbrauch lesen (Raspberry Pi 4/5 via vcgencmd pmic_read_adc oder sysfs)
if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'power') {
    $watts = null;
    $volt  = null;
    $amp   = null;

    if (PHP_OS_FAMILY === 'Linux') {
        // Methode 1: Pi 5 – sysfs power_supply
        $chargerDir = '/sys/class/power_supply/CHARGER/';
        if (is_dir($chargerDir)) {
            $vRaw = @file_get_contents($chargerDir . 'voltage_now');
            $iRaw = @file_get_contents($chargerDir . 'current_now');
            if ($vRaw !== false && $iRaw !== false) {
                $volt  = round((int)trim($vRaw) / 1000000, 2); // µV → V
                $amp   = round((int)trim($iRaw) / 1000000, 2); // µA → A
                $watts = round($volt * $amp, 2);
            }
        }

        // Methode 2: Pi 4 – vcgencmd pmic_read_adc
        if ($watts === null) {
            $out = [];
            @exec('vcgencmd pmic_read_adc 2>/dev/null', $out);
            if (!empty($out)) {
                $extVolt   = null;
                $currents  = [];
                foreach ($out as $line) {
                    if (preg_match('/EXT5V_V\s+([\d.]+)V?/i', $line, $m)) {
                        $extVolt = (float)$m[1];
                    }
                    if (preg_match('/(\w+)_I\s+([\d.]+)A?/i', $line, $m)) {
                        $currents[$m[1]] = (float)$m[2];
                    }
                }
                if ($extVolt !== null && !empty($currents)) {
                    $totalAmp = array_sum($currents);
                    $volt     = round($extVolt, 2);
                    $amp      = round($totalAmp, 2);
                    $watts    = round($volt * $totalAmp, 2);
                }
            }
        }

        // Methode 3: Spannung aus sysfs (einfachere Boards)
        if ($watts === null) {
            foreach (glob('/sys/class/power_supply/*/') as $dir) {
                $vRaw = @file_get_contents($dir . 'voltage_now');
                $iRaw = @file_get_contents($dir . 'current_now');
                if ($vRaw !== false && $iRaw !== false && (int)trim($iRaw) > 0) {
                    $volt  = round((int)trim($vRaw) / 1000000, 2);
                    $amp   = round((int)trim($iRaw) / 1000000, 2);
                    $watts = round($volt * $amp, 2);
                    break;
                }
            }
        }
    }

    if ($watts !== null) {
        echo json_encode(['ok' => true, 'watts' => $watts, 'volt' => $volt, 'amp' => $amp]);
    } else {
        echo json_encode(['ok' => false, 'watts' => null, 'volt' => null, 'amp' => null]);
    }
    exit;
}

// Manuellen Cron-Lauf ausführen
if (isset($_REQUEST['action']) && $_REQUEST['action'] === 'run') {
    $weckerRoot = dirname(__DIR__);
    $script = $weckerRoot . DIRECTORY_SEPARATOR . 'cronjob_run_once.php';
    if (!is_file($script)) {
        function_json_antwort(false, ['message' => 'cronjob_run_once.php nicht gefunden.']);
        exit;
    }
    $php = (defined('PHP_BINARY') && PHP_BINARY !== '') ? PHP_BINARY : 'php';
    $cmd = escapeshellarg($php) . ' ' . escapeshellarg($script) . ' 2>&1';
    $output = [];
    $code = -1;
    @exec($cmd, $output, $code);
    if ($code === 0) {
        function_json_antwort(true, ['message' => 'Cron einmal ausgeführt.']);
    } else {
        $err = trim(implode("\n", $output));
        function_json_antwort(false, ['message' => $err !== '' ? $err : 'Cron-Ausführung fehlgeschlagen.']);
    }
    exit;
}

$ip = isset($_SERVER['SERVER_ADDR']) ? trim((string) $_SERVER['SERVER_ADDR']) : '';
$isLoopback = ($ip === '127.0.0.1' || $ip === '' || (strpos($ip, '127.') === 0));

// Wenn nur Localhost/Loopback: interne Netzwerk-IP ermitteln (z. B. 192.168.x.x)
if ($isLoopback) {
    $ip = '';
    // Linux: hostname -I liefert die Adressen der Netzwerk-Interfaces (erste ist meist die LAN-IP)
    if (PHP_OS_FAMILY === 'Linux' && function_exists('shell_exec')) {
        $out = @shell_exec('hostname -I 2>/dev/null');
        if ($out !== null && $out !== '') {
            $addrs = preg_split('/\s+/', trim($out), -1, PREG_SPLIT_NO_EMPTY);
            foreach ($addrs as $addr) {
                $addr = trim($addr);
                if ($addr !== '' && strpos($addr, '127.') !== 0) {
                    $ip = $addr;
                    break;
                }
            }
        }
    }
    // Fallback: gethostbyname (kann auf einigen Systemen die LAN-IP liefern)
    if ($ip === '' && function_exists('gethostname')) {
        $host = gethostname();
        if ($host && function_exists('gethostbyname')) {
            $resolved = gethostbyname($host);
            if ($resolved !== $host && strpos($resolved, '127.') !== 0) {
                $ip = $resolved;
            }
        }
    }
}
if ($ip === '' && !$isLoopback) {
    $ip = isset($_SERVER['SERVER_ADDR']) ? trim((string) $_SERVER['SERVER_ADDR']) : '';
}
if ($ip === '' && function_exists('gethostname') && function_exists('gethostbyname')) {
    $host = gethostname();
    if ($host) {
        $resolved = gethostbyname($host);
        if ($resolved !== $host && strpos($resolved, '127.') !== 0) {
            $ip = $resolved;
        }
    }
}

$cronLastActivity = null;
$cronStatus = 'error'; // kein Lauf / älter als 5 Min = rot
if (is_file($stateFile)) {
    $content = @file_get_contents($stateFile);
    if ($content !== false) {
        $state = @json_decode($content, true);
        if (is_array($state) && !empty($state['last_activity'])) {
            $cronLastActivity = $state['last_activity'];
            $ts = strtotime($cronLastActivity);
            if ($ts !== false) {
                $age = time() - $ts;
                if ($age <= 60) {
                    $cronStatus = 'ok';   // ≤ 1 Min = grün
                } elseif ($age <= 300) {
                    $cronStatus = 'warn'; // 1–5 Min = gelb
                }
                // > 5 Min bleibt 'error' (rot)
            }
        }
    }
}

function_json_antwort(true, [
    'ip'                 => $ip,
    'cron_last_activity' => $cronLastActivity,
    'cron_status'        => $cronStatus,
]);
