<?php
/**
 * VPS Alert Monitor
 * Chiamato da cron ogni 5 minuti.
 * Legge config da /root/push/centraledash/config/telegram-config.json
 * Manda alert Telegram solo se trova problemi.
 */

date_default_timezone_set('Europe/Rome');

// ─────────────────────────────────────
// CONFIG TELEGRAM
// ─────────────────────────────────────
$telegramConfigPath = '/root/push/centraledash/config/telegram-config.json';
if (!file_exists($telegramConfigPath)) {
    exit('telegram-config.json non trovato');
}
$tgConfig = json_decode(file_get_contents($telegramConfigPath), true);
$botToken = $tgConfig['bot_token'] ?? '';
$chatIds  = $tgConfig['chat_ids']  ?? [];

if (empty($botToken) || empty($chatIds)) {
    exit('bot_token o chat_ids mancanti nel config');
}

// ─────────────────────────────────────
// FUNZIONE INVIO TELEGRAM
// ─────────────────────────────────────
function sendTelegram(string $botToken, array $chatIds, string $msg): void
{
    foreach ($chatIds as $chatId) {
        $ch = curl_init("https://api.telegram.org/bot$botToken/sendMessage");
        curl_setopt_array($ch, [
            CURLOPT_POST           => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT        => 10,
            CURLOPT_POSTFIELDS     => http_build_query([
                'chat_id' => $chatId,
                'text'    => $msg,
            ]),
        ]);
        curl_exec($ch);
        curl_close($ch);
    }
}

$alerts   = [];
$resolved = [];

// ─────────────────────────────────────
// HELPER — contatore alert (max 3 invii)
// ─────────────────────────────────────
function alertCount(string $key): int {
    $f = "/tmp/vps_alert_{$key}.count";
    return file_exists($f) ? (int) file_get_contents($f) : 0;
}
function alertIncrement(string $key): void {
    $f = "/tmp/vps_alert_{$key}.count";
    file_put_contents($f, alertCount($key) + 1);
}
function alertSetActive(string $key): void {
    file_put_contents("/tmp/vps_alert_{$key}.active", '1');
}
function alertIsActive(string $key): bool {
    return file_exists("/tmp/vps_alert_{$key}.active");
}
function alertClearActive(string $key): void {
    $f = "/tmp/vps_alert_{$key}.active";
    if (file_exists($f)) unlink($f);
}

// ─────────────────────────────────────
// 1. TENTATIVI SSH FALLITI (ultimi 5 min)
// ─────────────────────────────────────
$authLog = '/var/log/auth.log';
if (file_exists($authLog) && is_readable($authLog)) {
    $sogliaSSH = time() - 300;
    $failedCount = 0;

    $fp = fopen($authLog, 'r');
    if ($fp) {
        fseek($fp, 0, SEEK_END);
        $size = ftell($fp);
        // BLOCCO BLINDATO — vedi nota in estraiErroriInvio: file vuoto blocca fread(). Non togliere.
        $chunk = min($size, 200000);
        fseek($fp, -$chunk, SEEK_END);
        $content = $chunk > 0 ? fread($fp, $chunk) : '';
        fclose($fp);

        foreach (explode("\n", $content) as $line) {
            if (strpos($line, 'sshd') === false) continue;
            if (strpos($line, 'Failed') === false && strpos($line, 'Invalid user') === false) continue;
            // Formato timestamp auth.log: "Jun 28 10:05:23"
            if (preg_match('/^(\w{3}\s+\d+\s+\d{2}:\d{2}:\d{2})/', $line, $m)) {
                $ts = strtotime(date('Y') . ' ' . $m[1]);
                if ($ts !== false && $ts >= $sogliaSSH) {
                    $failedCount++;
                }
            }
        }
    }

    if ($failedCount > 20) {
        $c = alertCount('ssh');
        if ($c < 3) {
            $alerts[] = "🚨 SSH ALERT\n{$failedCount} tentativi SSH falliti negli ultimi 5 minuti.\nControlla /var/log/auth.log";
            alertIncrement('ssh');
        } elseif ($c === 3) {
            $alerts[] = "🔕 SSH ALERT bloccato — vai su centraledash per sbloccare.";
            alertIncrement('ssh');
        }
    }
}

// ─────────────────────────────────────
// 2. FILE CONFIG MODIFICATI (ultime 24 ore)
// ─────────────────────────────────────
$pushBase   = '/root/push';
$soglia24h  = time() - 86400;
$configFilesModified = [];

foreach (glob($pushBase . '/*/') ?: [] as $folder) {
    $toCheck = [
        $folder . 'env.php',
        $folder . 'config/env.php',
        $folder . 'PRENOTAZIONI/config/env.php',
        $folder . 'config/config.php',
        $folder . 'PRENOTAZIONI/config/config.php',
    ];
    foreach ($toCheck as $f) {
        if (file_exists($f) && filemtime($f) >= $soglia24h) {
            $configFilesModified[] = str_replace($pushBase . '/', '', $f);
        }
    }
}

if (!empty($configFilesModified)) {
    $c = alertCount('config_modificati');
    if ($c < 3) {
        $elenco = implode("\n  • ", $configFilesModified);
        $alerts[] = "⚠️ CONFIG MODIFICATI\nFile modificati nelle ultime 24 ore:\n  • $elenco";
        alertIncrement('config_modificati');
    } elseif ($c === 3) {
        $alerts[] = "🔕 CONFIG MODIFICATI bloccato — vai su centraledash per sbloccare.";
        alertIncrement('config_modificati');
    }
}

// ─────────────────────────────────────
// 3. SPAZIO DISCO SOTTO 15%
// ─────────────────────────────────────
$dfOutput = shell_exec('df / --output=pcent 2>/dev/null | tail -1');
if ($dfOutput !== null) {
    $usedPct = (int) trim(str_replace('%', '', $dfOutput));
    $freePct = 100 - $usedPct;
    if ($freePct < 15) {
        $c = alertCount('disco');
        if ($c < 3) {
            $alerts[] = "💾 DISCO PIENO\nSpazio libero su /: {$freePct}% (usato: {$usedPct}%)\nPulisci log o backup vecchi.";
            alertSetActive('disco');
            alertIncrement('disco');
        } elseif ($c === 3) {
            $alerts[] = "🔕 DISCO PIENO bloccato — vai su centraledash per sbloccare.";
            alertSetActive('disco');
            alertIncrement('disco');
        }
    } else {
        if (alertIsActive('disco')) {
            $resolved[] = "✅ DISCO OK\nSpazio libero tornato sopra il 15%.";
            alertClearActive('disco');
        }
    }
}

// ─────────────────────────────────────
// 4. SESSIONE WHATSAPP — keepalive_wasender
// ─────────────────────────────────────
$soglia7h = time() - 25200;

foreach (glob($pushBase . '/*/PRENOTAZIONI/keepalive.log') ?: [] as $logFile) {
    if (!is_readable($logFile)) continue;

    $fp = fopen($logFile, 'r');
    if (!$fp) continue;
    fseek($fp, -4096, SEEK_END);
    $tail = fread($fp, 4096);
    fclose($fp);

    $lines    = array_values(array_filter(explode("\n", $tail)));
    $lastLine = end($lines);
    $appName  = basename(dirname(dirname($logFile)));
    $problema = false;

    if (empty($lastLine)) {
        $problema = true;
    } elseif (preg_match('/\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/', $lastLine, $m)) {
        $ts = strtotime($m[1]);
        if ($ts === false || $ts < $soglia7h) {
            $problema = true;
        }
    } else {
        $problema = true;
    }

    if ($problema) {
        $waKey = 'wa_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $appName);
        $c = alertCount($waKey);
        if ($c < 3) {
            $alerts[] = "📱 WHATSAPP OFFLINE\nKeepalive non girato nelle ultime 7 ore per: $appName\nVerifica connessione su wasenderapi.com";
            alertSetActive($waKey);
            alertIncrement($waKey);
        } elseif ($c === 3) {
            $alerts[] = "🔕 WHATSAPP OFFLINE ($appName) bloccato — vai su centraledash per sbloccare.";
            alertSetActive($waKey);
            alertIncrement($waKey);
        }
    } else {
        $waKey = 'wa_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $appName);
        if (alertIsActive($waKey)) {
            $resolved[] = "✅ WHATSAPP OK\n{$appName} — keepalive tornato regolare.";
            alertClearActive($waKey);
        }
    }
}

// ─────────────────────────────────────
// 5. CHIAMATE SOSPETTE API (ultimi 60 min)
// ─────────────────────────────────────
$soglia60m = time() - 3600;
$ipCounts  = [];

$accessLogs = [
    '/var/log/nginx/access.log',
    '/var/log/nginx/puschpromozioni.it.access.log',
    '/var/log/apache2/access.log',
    '/var/log/apache2/other_vhosts_access.log',
];
$accessLog = '';
foreach ($accessLogs as $candidate) {
    if (file_exists($candidate) && is_readable($candidate)) {
        $accessLog = $candidate;
        break;
    }
}

if ($accessLog !== '') {
    $fp = fopen($accessLog, 'r');
    if ($fp) {
        fseek($fp, 0, SEEK_END);
        $size  = ftell($fp);
        // BLOCCO BLINDATO — vedi nota in estraiErroriInvio: file vuoto blocca fread(). Non togliere.
        $chunk = min($size, 500000);
        fseek($fp, -$chunk, SEEK_END);
        $content = $chunk > 0 ? fread($fp, $chunk) : '';
        fclose($fp);

        $lines = array_slice(array_values(array_filter(explode("\n", $content))), -1000);

        foreach ($lines as $line) {
            if (strpos($line, 'api.php') === false) continue;

            // Formato: 1.2.3.4 - - [28/Jun/2026:12:21:00 +0200] "POST /api.php ..."
            if (!preg_match('/^(\S+)\s+\S+\s+\S+\s+\[(\d{2}\/\w{3}\/\d{4}:\d{2}:\d{2}:\d{2})\s+[+-]\d{4}\]/', $line, $m)) continue;

            $ip      = $m[1];
            // "28/Jun/2026:12:21:00" → "28 Jun 2026 12:21:00"
            $dateStr = preg_replace('/^(\d{2})\/(\w{3})\/(\d{4}):/', '$1 $2 $3 ', $m[2]);
            $ts      = strtotime($dateStr);
            if ($ts === false || $ts < $soglia60m) continue;

            $ipCounts[$ip] = ($ipCounts[$ip] ?? 0) + 1;
        }
    }

    $ipSospetti = array_filter($ipCounts, fn($count) => $count > 50);
    arsort($ipSospetti);

    if (!empty($ipSospetti)) {
        $c = alertCount('api_abuse');
        if ($c < 3) {
            $elenco = '';
            foreach ($ipSospetti as $ip => $cnt) {
                $elenco .= "\n  • $ip → $cnt chiamate";
            }
            $alerts[] = "🔴 API ABUSE\nIP con >50 chiamate nell'ultima ora:$elenco\nLog: $accessLog";
            alertSetActive('api_abuse');
            alertIncrement('api_abuse');
        } elseif ($c === 3) {
            $alerts[] = "🔕 API ABUSE bloccato — vai su centraledash per sbloccare.";
            alertSetActive('api_abuse');
            alertIncrement('api_abuse');
        }
    } else {
        if (alertIsActive('api_abuse')) {
            $resolved[] = "✅ API OK\nTraffico API tornato nella norma.";
            alertClearActive('api_abuse');
        }
    }
}

// ─────────────────────────────────────
// 6. PERMESSI E PROPRIETARIO FILE CONFIG (env.php / config.php)
// ─────────────────────────────────────
$expectedOwner = 'www-data';
$filePermErrati = [];
$fileOwnerErrati = [];
foreach (glob($pushBase . '/*/') ?: [] as $folder) {
    foreach ([
        $folder . 'env.php',
        $folder . 'config/env.php',
        $folder . 'PRENOTAZIONI/config/env.php',
        $folder . 'config/config.php',
        $folder . 'PRENOTAZIONI/config/config.php',
    ] as $f) {
        if (!file_exists($f)) continue;
        $perms = fileperms($f) & 0777;
        if ($perms !== 0600) {
            $filePermErrati[] = str_replace($pushBase . '/', '', $f)
                . ' (permessi: ' . sprintf('%04o', $perms) . ')';
        }
        $owner = trim((string) shell_exec('stat -c %U ' . escapeshellarg($f) . ' 2>/dev/null'));
        if ($owner !== '' && $owner !== $expectedOwner) {
            $fileOwnerErrati[] = str_replace($pushBase . '/', '', $f)
                . " (proprietario: $owner, atteso: $expectedOwner)";
        }
    }
}
if (!empty($filePermErrati)) {
    $c = alertCount('permessi');
    if ($c < 3) {
        $elenco = implode("\n  • ", $filePermErrati);
        $alerts[] = "🔐 PERMESSI ERRATI\nFile config con permessi diversi da 600:\n  • $elenco\nEsegui: chmod 600 <file>";
        alertSetActive('permessi');
        alertIncrement('permessi');
    } elseif ($c === 3) {
        $alerts[] = "🔕 PERMESSI ERRATI bloccato — vai su centraledash per sbloccare.";
        alertSetActive('permessi');
        alertIncrement('permessi');
    }
} else {
    if (alertIsActive('permessi')) {
        $resolved[] = "✅ PERMESSI OK\nTutti i file config hanno permessi corretti.";
        alertClearActive('permessi');
    }
}
if (!empty($fileOwnerErrati)) {
    $c = alertCount('proprietario');
    if ($c < 3) {
        $elenco = implode("\n  • ", $fileOwnerErrati);
        $alerts[] = "👤 PROPRIETARIO ERRATO\nFile config con proprietario diverso da $expectedOwner:\n  • $elenco\nEsegui: chown $expectedOwner:$expectedOwner <file>";
        alertSetActive('proprietario');
        alertIncrement('proprietario');
    } elseif ($c === 3) {
        $alerts[] = "🔕 PROPRIETARIO ERRATO bloccato — vai su centraledash per sbloccare.";
        alertSetActive('proprietario');
        alertIncrement('proprietario');
    }
} else {
    if (alertIsActive('proprietario')) {
        $resolved[] = "✅ PROPRIETARIO OK\nTutti i file config hanno il proprietario corretto.";
        alertClearActive('proprietario');
    }
}

// ─────────────────────────────────────
// 7. ERRORI INVIO WHATSAPP/PUSH — scansione dinamica, due livelli
// Qualsiasi progetto sotto /root/push/ che scrive log nel formato
// standard viene controllato automaticamente, senza modificare questo
// file quando si aggiunge un nuovo progetto o canale.
//
// FRONTE A — risposta del provider (WhatsApp/wasenderapi.com, Push/FCM)
//   Log: PRENOTAZIONI/debug_logs/whatsapp_send.log e fcm_send.log
//   Formato riga: "[YYYY-MM-DD HH:MM:SS] ... http=CODICE OK|ERRORE"
// FRONTE B — fallimento della chiamata al nostro endpoint interno
//   (api.php chiama send_push_appointment.php)
//   Log: PRENOTAZIONI/api_debug.log, righe "[WHATSAPP_CALL_ERRORE]"
//
// Ogni codice HTTP e' classificato come:
//   - immediato: alert al primo caso (probabile problema di sistema)
//   - soglia: alert solo oltre $sogliaErrori casi in 2 ore (probabile
//     caso isolato legato al singolo cliente)
// ─────────────────────────────────────
$soglia2h = time() - 7200;
$sogliaErrori = 5;

function classificaErroreInvio(int $codice, array $tabella, array $default): array
{
    return $tabella[$codice] ?? $default;
}

function estraiErroriInvio(string $logFile, int $soglia): array
{
    if (!file_exists($logFile) || !is_readable($logFile)) return [];
    $fp = fopen($logFile, 'r');
    if (!$fp) return [];
    fseek($fp, 0, SEEK_END);
    $size  = ftell($fp);
    // BLOCCO BLINDATO — bug storico 15/07/2026: file di log vuoto (0 byte)
    // faceva chiamare fread($fp, 0), che in PHP 8+ lancia un ValueError
    // fatale e blocca l'INTERO script (nessun alert Telegram parte piu').
    // Non togliere questo controllo.
    if ($size <= 0) {
        fclose($fp);
        return [];
    }
    $chunk = min($size, 300000);
    fseek($fp, -$chunk, SEEK_END);
    $content = fread($fp, $chunk);
    fclose($fp);

    $trovati = [];
    foreach (explode("\n", $content) as $line) {
        if (stripos($line, 'ERRORE') === false) continue;
        if (!preg_match('/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/', $line, $m)) continue;
        $ts = strtotime($m[1]);
        if ($ts === false || $ts < $soglia) continue;
        if (!preg_match('/http=(\d+)/', $line, $mc)) continue;
        $trovati[] = ['ts' => $ts, 'http' => (int)$mc[1]];
    }
    return $trovati;
}

$causeProvider = [
    401 => ['livello' => 'immediato', 'causa' => "Token WASENDER_TOKEN scaduto, revocato o sessione WhatsApp disconnessa", 'dove' => 'config/env.php + pannello centraledash "Collega WhatsApp"'],
    403 => ['livello' => 'immediato', 'causa' => "Token WASENDER_TOKEN scaduto, revocato o sessione WhatsApp disconnessa", 'dove' => 'config/env.php + pannello centraledash "Collega WhatsApp"'],
    404 => ['livello' => 'immediato', 'causa' => 'Endpoint/percorso account cambiato lato provider (raro)', 'dove' => 'Verificare configurazione account presso il provider'],
    500 => ['livello' => 'immediato', 'causa' => 'Problema lato server del provider', 'dove' => 'Attendere, verificare stato servizio provider'],
    502 => ['livello' => 'immediato', 'causa' => 'Problema lato server del provider', 'dove' => 'Attendere, verificare stato servizio provider'],
    503 => ['livello' => 'immediato', 'causa' => 'Problema lato server del provider', 'dove' => 'Attendere, verificare stato servizio provider'],
    0   => ['livello' => 'immediato', 'causa' => 'Connessione fallita (rete, DNS, firewall verso il provider)', 'dove' => 'Verificare connettivita del server VPS'],
    422 => ['livello' => 'soglia', 'causa' => 'Numero WhatsApp non valido o non registrato — controlla il numero inserito dal cliente', 'dove' => 'Controllare il singolo appointment_id nel log'],
    429 => ['livello' => 'soglia', 'causa' => 'Troppi messaggi in poco tempo (rate limit)', 'dove' => 'Verificare se c\'e di nuovo un loop di invii duplicati'],
];
$defaultProvider = ['livello' => 'soglia', 'causa' => 'Causa non chiara, spesso legata al singolo cliente (numero errato, ecc.)', 'dove' => 'Controllare il singolo appointment_id nel log'];

$causeInterno = [
    404 => ['livello' => 'immediato', 'causa' => 'URL sbagliato nel codice (endpoint non trovato)', 'dove' => 'curl_init(...) in api.php'],
    500 => ['livello' => 'immediato', 'causa' => 'Errore PHP dentro send_push_appointment.php', 'dove' => 'Quel file, controllare log errori PHP'],
    0   => ['livello' => 'immediato', 'causa' => 'Server/vhost non raggiungibile', 'dove' => 'Verificare Apache/vhost del dominio'],
    401 => ['livello' => 'immediato', 'causa' => 'Inatteso per un endpoint interno - probabile blocco imprevisto', 'dove' => 'Controllare .htaccess o regole firewall'],
    403 => ['livello' => 'immediato', 'causa' => 'Inatteso per un endpoint interno - probabile blocco imprevisto', 'dove' => 'Controllare .htaccess o regole firewall'],
];
$defaultInterno = ['livello' => 'soglia', 'causa' => 'Causa non chiara', 'dove' => 'Controllare il singolo appointment_id in api_debug.log'];

foreach (glob($pushBase . '/*/') ?: [] as $folder) {
    $appName = basename(rtrim($folder, '/'));

    $fonti = [
        ['nome' => 'WhatsApp (provider)',       'file' => $folder . 'PRENOTAZIONI/debug_logs/whatsapp_send.log', 'tabella' => $causeProvider, 'default' => $defaultProvider],
        ['nome' => 'Push (provider)',           'file' => $folder . 'PRENOTAZIONI/debug_logs/fcm_send.log',       'tabella' => $causeProvider, 'default' => $defaultProvider],
        ['nome' => 'Chiamata interna WhatsApp', 'file' => $folder . 'PRENOTAZIONI/api_debug.log',                 'tabella' => $causeInterno,  'default' => $defaultInterno],
    ];

    foreach ($fonti as $fonte) {
        $errori = estraiErroriInvio($fonte['file'], $soglia2h);
        if (empty($errori)) continue;

        $baseKey    = preg_replace('/[^a-zA-Z0-9_]/', '_', $appName . '_' . $fonte['nome']);
        $immediati  = [];
        $sogliaCasi = [];

        foreach ($errori as $errore) {
            $classificazione = classificaErroreInvio($errore['http'], $fonte['tabella'], $fonte['default']);
            $errore = array_merge($errore, $classificazione);
            if ($classificazione['livello'] === 'immediato') {
                $immediati[] = $errore;
            } else {
                $sogliaCasi[] = $errore;
            }
        }

        // Livello immediato: alert al primo caso, una chiave per codice HTTP
        $perCodice = [];
        foreach ($immediati as $imm) {
            $perCodice[$imm['http']] = $imm;
        }

        // Recupera i codici gia' segnalati come attivi in giri precedenti,
        // per capire quali sono rientrati (non piu' presenti in $perCodice)
        $codiciAttiviPrima = [];
        foreach (glob('/tmp/vps_alert_immediato_' . $baseKey . '_*.active') ?: [] as $activeFile) {
            if (preg_match('/_(\d+)\.active$/', $activeFile, $mCode)) {
                $codiciAttiviPrima[] = (int)$mCode[1];
            }
        }

        foreach ($perCodice as $codice => $ultimo) {
            $keyImm = 'immediato_' . $baseKey . '_' . $codice;
            $c = alertCount($keyImm);
            if ($c < 3) {
                $alerts[] = "🚨 ERRORE INVIO — IMMEDIATO\nProgetto: {$appName}\nCanale: {$fonte['nome']}\nCodice: {$codice}\nCausa probabile: {$ultimo['causa']}\nVerifica: {$ultimo['dove']}\nUltimo caso: " . date('d/m/Y H:i', $ultimo['ts']);
                alertSetActive($keyImm);
                alertIncrement($keyImm);
            } elseif ($c === 3) {
                $alerts[] = "🔕 ERRORE INVIO IMMEDIATO ({$appName} / {$fonte['nome']} / {$codice}) bloccato — vai su centraledash per sbloccare.";
                alertSetActive($keyImm);
                alertIncrement($keyImm);
            }
        }

        foreach ($codiciAttiviPrima as $codiceVecchio) {
            if (!isset($perCodice[$codiceVecchio])) {
                $resolved[] = "✅ INVIO OK\n{$appName} ({$fonte['nome']}) — errore immediato (codice {$codiceVecchio}) rientrato.";
                alertClearActive('immediato_' . $baseKey . '_' . $codiceVecchio);
            }
        }

        // Livello soglia: solo sugli errori non classificati come immediati
        $keySoglia = 'soglia_' . $baseKey;
        if (count($sogliaCasi) > $sogliaErrori) {
            $c = alertCount($keySoglia);
            if ($c < 3) {
                $ultimo = end($sogliaCasi);
                $alerts[] = "📤 ERRORI INVIO — RIPETUTI\nProgetto: {$appName}\nCanale: {$fonte['nome']}\n" . count($sogliaCasi) . " errori nelle ultime 2 ore (es. codice {$ultimo['http']}).\nCausa probabile: {$ultimo['causa']}\nVerifica: {$ultimo['dove']}";
                alertSetActive($keySoglia);
                alertIncrement($keySoglia);
            } elseif ($c === 3) {
                $alerts[] = "🔕 ERRORI INVIO RIPETUTI ({$appName} / {$fonte['nome']}) bloccato — vai su centraledash per sbloccare.";
                alertSetActive($keySoglia);
                alertIncrement($keySoglia);
            }
        } else {
            if (alertIsActive($keySoglia)) {
                $resolved[] = "✅ INVIO OK\n{$appName} ({$fonte['nome']}) — errori ripetuti tornati sotto soglia.";
                alertClearActive($keySoglia);
            }
        }
    }
}

// ─────────────────────────────────────
// 8. ERRORI PHP (Warning e Fatal) NEL LOG APACHE
// Log: /var/log/apache2/error.log
// Intercetta sia "php:warn" (es. require_once su file non leggibile,
// come il bug del 12/07/2026 con env.php di proprieta' errata)
// sia "php:error" (crash veri e propri).
// ─────────────────────────────────────
$sogliaPhpErr = time() - 900; // ultimi 15 minuti
$phpErrorLog = '/var/log/apache2/error.log';

if (file_exists($phpErrorLog) && is_readable($phpErrorLog)) {
    $fp = fopen($phpErrorLog, 'r');
    if ($fp) {
        fseek($fp, 0, SEEK_END);
        $size  = ftell($fp);
        // BLOCCO BLINDATO — vedi nota in estraiErroriInvio: file vuoto blocca fread(). Non togliere.
        $chunk = min($size, 500000);
        fseek($fp, -$chunk, SEEK_END);
        $content = $chunk > 0 ? fread($fp, $chunk) : '';
        fclose($fp);

        $erroriPhp = [];
        foreach (explode("\n", $content) as $line) {
            if (strpos($line, $pushBase . '/') === false) continue;
            if (strpos($line, 'php:warn') === false && strpos($line, 'php:error') === false) continue;

            if (!preg_match('/^\[\w+ (\w+ \d+ \d{2}:\d{2}:\d{2})(?:\.\d+)? (\d{4})\]/', $line, $m)) continue;
            $ts = strtotime($m[2] . ' ' . $m[1]);
            if ($ts === false || $ts < $sogliaPhpErr) continue;

            $appName = 'sconosciuto';
            if (preg_match('#' . preg_quote($pushBase, '#') . '/([^/]+)/#', $line, $mApp)) {
                $appName = $mApp[1];
            }
            $tipo = strpos($line, 'php:error') !== false ? 'FATAL' : 'WARNING';
            $erroriPhp[] = ['app' => $appName, 'tipo' => $tipo, 'ts' => $ts, 'line' => trim($line)];
        }

        foreach ($erroriPhp as $errore) {
            $key = 'phperr_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $errore['app']) . '_' . $errore['tipo'];
            $c = alertCount($key);
            if ($c < 3) {
                $estratto = mb_substr($errore['line'], 0, 300);
                $alerts[] = "🐛 ERRORE PHP ({$errore['tipo']})\nProgetto: {$errore['app']}\n{$estratto}";
                alertSetActive($key);
                alertIncrement($key);
            } elseif ($c === 3) {
                $alerts[] = "🔕 ERRORE PHP ({$errore['app']} / {$errore['tipo']}) bloccato — vai su centraledash per sbloccare.";
                alertSetActive($key);
                alertIncrement($key);
            }
        }
    }
}

// ─────────────────────────────────────
// 9. ERRORI NELLO SCHEDULER PROMEMORIA (reminder_trace.log)
// Log: PRENOTAZIONI/debug_logs/reminder_trace.log, righe "ERROR: ..."
// Sono errori catturati dal try/catch di notifiche_scheduler.php (es.
// "attempt to write a readonly database") — non generano un errore PHP
// vero, quindi il punto 8 (log Apache) non li vede. Scoperto il
// 15/07/2026: un database in sola lettura ha fatto ripartire lo stesso
// WhatsApp 17 volte senza che Telegram avvisasse nessuno.
// ─────────────────────────────────────
function estraiErroriScheduler(string $logFile, int $soglia): array
{
    if (!file_exists($logFile) || !is_readable($logFile)) return [];
    $fp = fopen($logFile, 'r');
    if (!$fp) return [];
    fseek($fp, 0, SEEK_END);
    $size  = ftell($fp);
    // BLOCCO BLINDATO — vedi nota in estraiErroriInvio: file vuoto (0 byte)
    // fa fallire fread() e blocca tutto lo script. Non togliere.
    if ($size <= 0) {
        fclose($fp);
        return [];
    }
    $chunk = min($size, 300000);
    fseek($fp, -$chunk, SEEK_END);
    $content = fread($fp, $chunk);
    fclose($fp);

    $trovati = [];
    foreach (explode("\n", $content) as $line) {
        if (!preg_match('/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]\s*ERROR:\s*(.+)$/', $line, $m)) continue;
        $ts = strtotime($m[1]);
        if ($ts === false || $ts < $soglia) continue;
        $trovati[] = ['ts' => $ts, 'msg' => trim($m[2])];
    }
    return $trovati;
}

foreach (glob($pushBase . '/*/PRENOTAZIONI/debug_logs/reminder_trace.log') ?: [] as $logFile) {
    $appName = basename(dirname(dirname($logFile)));
    $errori  = estraiErroriScheduler($logFile, $soglia2h);
    if (empty($errori)) {
        $keySched = 'scheduler_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $appName);
        if (alertIsActive($keySched)) {
            $resolved[] = "✅ SCHEDULER OK\n{$appName} — errori nel promemoria rientrati.";
            alertClearActive($keySched);
        }
        continue;
    }

    $keySched = 'scheduler_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $appName);
    $c = alertCount($keySched);
    if ($c < 3) {
        $ultimo = end($errori);
        $alerts[] = "🚨 ERRORE SCHEDULER PROMEMORIA\nProgetto: {$appName}\n" . count($errori) . " errore/i nelle ultime 2 ore.\nUltimo: {$ultimo['msg']}\nRischio: WhatsApp puo' ripartire piu' volte senza essere salvato come inviato.\nLog: {$logFile}";
        alertSetActive($keySched);
        alertIncrement($keySched);
    } elseif ($c === 3) {
        $alerts[] = "🔕 ERRORE SCHEDULER PROMEMORIA ({$appName}) bloccato — vai su centraledash per sbloccare.";
        alertSetActive($keySched);
        alertIncrement($keySched);
    }
}

// ─────────────────────────────────────
// INVIO MESSAGGI RISOLTI
// ─────────────────────────────────────
if (!empty($resolved)) {
    $timestamp = date('d/m/Y H:i');
    $msg = "🖥️ VPS MONITOR — $timestamp\n" . str_repeat('─', 28) . "\n";
    $msg .= implode("\n", $resolved);
    sendTelegram($botToken, $chatIds, $msg);
}

// ─────────────────────────────────────
// INVIO ALERT (solo se ci sono problemi)
// ─────────────────────────────────────
if (!empty($alerts)) {
    $timestamp = date('d/m/Y H:i');
    $separatore = str_repeat('─', 28);
    $msg  = "🖥️ VPS ALERT — $timestamp\n$separatore\n";
    $msg .= implode("\n$separatore\n", $alerts);

    sendTelegram($botToken, $chatIds, $msg);
}
