<?php
/**
 * Error Log — SysAdmin-only view of the `system_errors` table.
 * Every uncaught exception, fatal PHP error, and explicit log_error_db() call lands here,
 * so the ops team can diagnose failures without needing cPanel/server access.
 */
require_once __DIR__ . '/../includes/config.php';
$me = require_login('internal');
if (!is_sysadmin()) { flash('error', 'You do not have access to the error log.'); redirect('/dashboard.php'); }

$pdo = db();

/* ---------------------------------------------------------------- view: app (DB table) | server (log files)
   The SERVER view tails the real web-server / PHP error logs off disk — the same entries cPanel's "Errors"
   page shows (500s, 429s, autoindex noise, PHP fatals) — so ops can diagnose without cPanel/SSH access.
   STRICTLY READ-ONLY, and the file is picked from a DISCOVERED whitelist (never a raw path from the query
   string), so this can never be turned into an arbitrary-file reader. */
$view = (($_GET['view'] ?? '') === 'server') ? 'server' : 'app';

/** Server/PHP error-log files that actually exist and are readable here (deduped by realpath). */
function srv_log_candidates(): array
{
    $app  = realpath(__DIR__ . '/..') ?: dirname(__DIR__);
    $home = rtrim((string) (getenv('HOME') ?: ''), '/');
    if ($home === '') $home = dirname($app);                       // …/public_html/medmecs → …/public_html
    $host = preg_replace('/:\d+$/', '', (string) ($_SERVER['HTTP_HOST'] ?? ''));
    $host = (string) preg_replace('/[^A-Za-z0-9.\-]/', '', (string) $host);

    $c   = [];
    $ini = (string) ini_get('error_log');
    if ($ini !== '' && $ini !== 'syslog') $c[] = $ini;             // PHP's own configured log (PHP fatals)
    $c[] = $app . '/error_log';                                    // cPanel per-directory PHP logs
    $c[] = $app . '/admin/error_log';
    $c[] = dirname($app) . '/error_log';                           // public_html/error_log
    foreach (array_unique([$home, dirname($app)]) as $base) {
        if (!$base) continue;
        $c[] = $base . '/logs/error_log';
        if ($host !== '') $c[] = $base . '/logs/' . $host . '.error.log';
        foreach (glob($base . '/logs/*error*') ?: [] as $g) $c[] = $g;
    }
    $out = [];
    foreach ($c as $p) {
        if (!is_string($p) || $p === '') continue;
        $rp = @realpath($p);
        if ($rp === false || !@is_file($rp) || !@is_readable($rp)) continue;
        $out[$rp] = true;                                          // realpath key = dedupe
    }
    return array_keys($out);
}

/** Last $lines lines of $file, read from the END so a 25 MB log costs one small seek (never file_get_contents). */
function srv_log_tail(string $file, int $lines, int $maxBytes = 512000): array
{
    if ($lines < 1) $lines = 200;              // array_slice($a, -0) returns EVERYTHING — never let 0 through
    $fh = @fopen($file, 'rb');
    if (!$fh) return [];
    $size = (int) (@filesize($file) ?: 0);
    $read = (int) min($size, $maxBytes);
    if ($read > 0) @fseek($fh, -$read, SEEK_END);
    $buf = $read > 0 ? (string) @fread($fh, $read) : '';
    @fclose($fh);
    $all = preg_split("/\r\n|\n|\r/", $buf) ?: [];
    if ($size > $maxBytes && $all) array_shift($all);              // first line is cut mid-way — drop it
    $all = array_values(array_filter($all, fn($l) => trim((string) $l) !== ''));
    return array_slice($all, -$lines);
}

$srvLogs = []; $srvFile = ''; $srvLines = []; $srvErr = '';
/* Hoist the default OUT of the test — `in_array($_GET[x] ?? D, [...D...]) ? $_GET[x] : D` re-reads the
   missing key in the true branch (undefined-array-key warning + null → 0). Known repo-wide trap. */
$srvNRaw = (int) ($_GET['n'] ?? 200);
$srvN    = in_array($srvNRaw, [100, 200, 500, 1000, 2000], true) ? $srvNRaw : 200;
$srvQ = trim((string) ($_GET['sq'] ?? ''));
if ($view === 'server') {
    $srvLogs = srv_log_candidates();
    $want    = (string) ($_GET['file'] ?? '');
    $srvFile = in_array($want, $srvLogs, true) ? $want : ($srvLogs[0] ?? '');   // whitelist → no traversal
    if ($srvFile === '') {
        $srvErr = 'No readable server error log was found on this host.';
    } else {
        $srvLines = srv_log_tail($srvFile, $srvN);
        if ($srvQ !== '') $srvLines = array_values(array_filter($srvLines, fn($l) => stripos((string) $l, $srvQ) !== false));
        $srvLines = array_reverse($srvLines);                       // newest first (matches cPanel)
    }
}

/* Filters */
$fSource = trim((string) ($_GET['source'] ?? ''));
$fLevel  = trim((string) ($_GET['level']  ?? ''));
$fQ      = trim((string) ($_GET['q']      ?? ''));
$page    = max(1, (int) ($_GET['page'] ?? 1));
$perPage = 50;
$offset  = ($page - 1) * $perPage;

$where = ['1=1']; $params = [];
if ($fSource !== '') { $where[] = 'source = ?';        $params[] = $fSource; }
if ($fLevel  !== '') { $where[] = 'level  = ?';        $params[] = $fLevel; }
if ($fQ      !== '') { $where[] = 'message LIKE ?';    $params[] = '%' . $fQ . '%'; }
$whereSql = implode(' AND ', $where);

/* Counts + page rows */
try {
    $total = (int) (function() use ($pdo, $whereSql, $params) {
        $st = $pdo->prepare("SELECT COUNT(*) FROM system_errors WHERE $whereSql");
        $st->execute($params);
        return $st->fetchColumn();
    })();

    $st = $pdo->prepare("SELECT e.*, u.name AS user_name, u.email AS user_email
                          FROM system_errors e
                     LEFT JOIN users u ON u.id = e.user_id
                         WHERE $whereSql
                      ORDER BY e.id DESC
                         LIMIT $perPage OFFSET $offset");
    $st->execute($params);
    $rows = $st->fetchAll();

    /* Distinct sources for the filter dropdown */
    $sources = $pdo->query("SELECT DISTINCT source FROM system_errors ORDER BY source")->fetchAll(PDO::FETCH_COLUMN);

    /* Lightweight stats card */
    $last24 = (int) $pdo->query("SELECT COUNT(*) FROM system_errors WHERE created_at >= NOW() - INTERVAL 1 DAY")->fetchColumn();
    $lastH  = (int) $pdo->query("SELECT COUNT(*) FROM system_errors WHERE created_at >= NOW() - INTERVAL 1 HOUR")->fetchColumn();
} catch (Throwable $t) {
    $total = 0; $rows = []; $sources = []; $last24 = $lastH = 0;
    $tableMissing = true;
}

/* Clear / Delete actions (POST) */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    csrf_check_or_die();
    $act = (string) ($_POST['action'] ?? '');
    if ($act === 'delete' && (int) ($_POST['id'] ?? 0) > 0) {
        $pdo->prepare("DELETE FROM system_errors WHERE id = ?")->execute([(int) $_POST['id']]);
        flash('success', 'Entry deleted.');
    } elseif ($act === 'purge_old') {
        $pdo->exec("DELETE FROM system_errors WHERE created_at < NOW() - INTERVAL 30 DAY");
        flash('success', 'Entries older than 30 days purged.');
    } elseif ($act === 'clear_all') {
        $pdo->exec("TRUNCATE TABLE system_errors");
        flash('success', 'All error log entries cleared.');
    }
    audit_log('errors.' . $act, 'system_errors');
    redirect('/admin/errors.php');
}

$pageTitle = 'Error Log';
require __DIR__ . '/../includes/header.php';

$pages = max(1, (int) ceil($total / $perPage));
$qs    = function (array $extra) use ($fSource, $fLevel, $fQ, $page) {
    $a = array_filter(['source'=>$fSource,'level'=>$fLevel,'q'=>$fQ,'page'=>$page] + $extra, fn($v) => $v !== '' && $v !== null);
    return http_build_query(array_replace($a, $extra));
};

$levelBadge = fn($l) => match ($l) {
    'fatal'     => 'danger',
    'error'     => 'danger',
    'exception' => 'danger',
    'warning'   => 'warning',
    'info'      => 'info',
    default     => 'secondary',
};
?>
<div class="page-header"><div class="container-xl">
    <div class="row align-items-center">
        <div class="col">
            <h2 class="page-title mt-1"><i class="ti ti-bug me-2 text-danger"></i>Error Log</h2>
            <div class="text-secondary">Uncaught exceptions, fatal PHP errors, and explicit engine logs land here. SysAdmin only.</div>
        </div>
        <?php if ($view === 'app'): ?>
        <div class="col-auto d-flex gap-2">
            <form method="POST" onsubmit="return confirm('Delete entries older than 30 days?')" class="d-inline">
                <?= csrf_field() ?><input type="hidden" name="action" value="purge_old">
                <button class="btn btn-outline-secondary btn-sm"><i class="ti ti-broom me-1"></i>Purge >30d</button>
            </form>
            <form method="POST" onsubmit="return confirm('Delete ALL error log entries? This cannot be undone.')" class="d-inline">
                <?= csrf_field() ?><input type="hidden" name="action" value="clear_all">
                <button class="btn btn-outline-danger btn-sm"><i class="ti ti-trash me-1"></i>Clear all</button>
            </form>
        </div>
        <?php endif; ?>
    </div>
</div></div>

<div class="page-body"><div class="container-xl">

    <ul class="nav nav-tabs mb-3">
        <li class="nav-item"><a class="nav-link <?= $view==='app'?'active':'' ?>" href="?view=app"><i class="ti ti-bug me-1"></i>Application errors</a></li>
        <li class="nav-item"><a class="nav-link <?= $view==='server'?'active':'' ?>" href="?view=server"><i class="ti ti-server-cog me-1"></i>Server error log <span class="text-secondary small">(web server / PHP)</span></a></li>
    </ul>

    <?php if ($view === 'app'): ?>

    <?php if (!empty($tableMissing)): ?>
        <div class="alert alert-warning">The <code>system_errors</code> table isn't installed yet. Log in once after redeploying — the auto-migration creates it.</div>
    <?php endif; ?>

    <div class="row row-cards mb-3">
        <div class="col-6 col-md-3"><div class="card kpi-tile"><div class="card-body py-2">
            <div class="stat-label">Last hour</div><div class="stat-value text-<?= $lastH ? 'danger' : 'success' ?>"><?= number_format($lastH) ?></div>
        </div></div></div>
        <div class="col-6 col-md-3"><div class="card kpi-tile"><div class="card-body py-2">
            <div class="stat-label">Last 24 h</div><div class="stat-value text-<?= $last24 ? 'warning' : 'success' ?>"><?= number_format($last24) ?></div>
        </div></div></div>
        <div class="col-6 col-md-3"><div class="card kpi-tile"><div class="card-body py-2">
            <div class="stat-label">Showing</div><div class="stat-value"><?= number_format(count($rows)) ?></div>
            <div class="small text-secondary">of <?= number_format($total) ?> matching</div>
        </div></div></div>
        <div class="col-6 col-md-3"><div class="card kpi-tile"><div class="card-body py-2">
            <div class="stat-label">Sources</div><div class="stat-value"><?= count($sources) ?></div>
        </div></div></div>
    </div>

    <div class="card mb-3"><div class="card-body py-2">
        <form method="GET" class="row g-2 align-items-end">
            <div class="col-md-3">
                <label class="form-label mb-1 small">Source</label>
                <select name="source" class="form-select">
                    <option value="">All sources</option>
                    <?php foreach ($sources as $s): ?>
                        <option value="<?= e($s) ?>" <?= $s === $fSource ? 'selected' : '' ?>><?= e($s) ?></option>
                    <?php endforeach; ?>
                </select>
            </div>
            <div class="col-md-2">
                <label class="form-label mb-1 small">Level</label>
                <select name="level" class="form-select">
                    <option value="">All levels</option>
                    <?php foreach (['fatal','error','exception','warning','info'] as $l): ?>
                        <option value="<?= e($l) ?>" <?= $l === $fLevel ? 'selected' : '' ?>><?= ucfirst($l) ?></option>
                    <?php endforeach; ?>
                </select>
            </div>
            <div class="col-md-5">
                <label class="form-label mb-1 small">Search message</label>
                <input type="text" name="q" value="<?= e($fQ) ?>" class="form-control" placeholder="e.g. LOAD DATA, server has gone, FATAL">
            </div>
            <div class="col-md-2">
                <button class="btn btn-primary w-100"><i class="ti ti-search me-1"></i>Filter</button>
            </div>
        </form>
    </div></div>

    <div class="card"><div class="table-responsive">
        <table class="table table-sm table-vcenter card-table">
            <thead><tr>
                <th style="width:140px">When</th>
                <th style="width:90px">Level</th>
                <th style="width:120px">Source</th>
                <th>Message</th>
                <th style="width:150px">Who</th>
                <th style="width:60px"></th>
            </tr></thead>
            <tbody>
            <?php if (!$rows): ?>
                <tr><td colspan="6" class="text-center text-secondary py-4">No errors recorded. 🎉</td></tr>
            <?php else: foreach ($rows as $r):
                $msg = (string) $r['message'];
                $short = mb_strimwidth(strtok($msg, "\n"), 0, 200, '…');
                $hasMore = strlen($msg) > strlen($short) || $r['context'];
            ?>
                <tr>
                    <td class="text-secondary small text-nowrap"><?= e(fmt_dt($r['created_at'])) ?></td>
                    <td><span class="badge bg-<?= $levelBadge($r['level']) ?>-lt"><?= e($r['level']) ?></span></td>
                    <td><span class="badge bg-secondary-lt"><?= e($r['source']) ?></span></td>
                    <td>
                        <code class="text-body small d-block text-wrap" style="word-break:break-word"><?= e($short) ?></code>
                        <?php if ($hasMore): ?>
                            <details class="mt-1">
                                <summary class="small text-secondary" style="cursor:pointer">Show full</summary>
                                <pre class="small mb-1" style="white-space:pre-wrap;background:var(--m-surface-2);padding:.5rem;border-radius:.25rem"><?= e($msg) ?></pre>
                                <?php if (!empty($r['context'])): ?>
                                    <div class="small text-secondary">Context:</div>
                                    <pre class="small mb-0" style="white-space:pre-wrap;background:var(--m-surface-2);padding:.5rem;border-radius:.25rem"><?= e($r['context']) ?></pre>
                                <?php endif; ?>
                                <?php if (!empty($r['url'])): ?>
                                    <div class="small text-secondary mt-1">URL: <code><?= e($r['url']) ?></code></div>
                                <?php endif; ?>
                            </details>
                        <?php endif; ?>
                    </td>
                    <td class="small text-secondary"><?= $r['user_name'] ? e($r['user_name']) : '—' ?></td>
                    <td class="text-end">
                        <form method="POST" class="d-inline" onsubmit="return confirm('Delete this entry?')">
                            <?= csrf_field() ?>
                            <input type="hidden" name="action" value="delete">
                            <input type="hidden" name="id" value="<?= (int) $r['id'] ?>">
                            <button class="btn btn-sm btn-ghost-danger" title="Delete"><i class="ti ti-x"></i></button>
                        </form>
                    </td>
                </tr>
            <?php endforeach; endif; ?>
            </tbody>
        </table>
    </div></div>

    <?php if ($pages > 1): ?>
        <div class="d-flex align-items-center justify-content-between mt-2 small">
            <div class="text-secondary">Page <?= $page ?> of <?= $pages ?> · <?= number_format($total) ?> entries</div>
            <ul class="pagination mb-0">
                <?php if ($page > 1): ?><li class="page-item"><a class="page-link" href="?<?= $qs(['page' => $page - 1]) ?>">‹ Prev</a></li><?php endif; ?>
                <?php if ($page < $pages): ?><li class="page-item"><a class="page-link" href="?<?= $qs(['page' => $page + 1]) ?>">Next ›</a></li><?php endif; ?>
            </ul>
        </div>
    <?php endif; ?>

    <?php else: /* ==================== SERVER ERROR LOG (read-only tail of the real web-server / PHP logs) ==================== */ ?>

    <?php if ($srvErr !== ''): ?>
        <div class="alert alert-warning">
            <i class="ti ti-alert-triangle me-1"></i><?= e($srvErr) ?>
            <div class="small mt-1">Looked for PHP's <code>error_log</code> setting, <code>&lt;app&gt;/error_log</code>, <code>public_html/error_log</code> and <code>~/logs/*error*</code>.
            On cPanel the account-wide Apache log is often root-owned, so it may only be visible via <strong>cPanel → Errors</strong>. PHP errors from this app will appear here once PHP writes an <code>error_log</code> file.</div>
        </div>
    <?php else: ?>
    <div class="card mb-3"><div class="card-body py-2">
        <form method="GET" class="row g-2 align-items-end">
            <input type="hidden" name="view" value="server">
            <div class="col-md-5">
                <label class="form-label mb-1 small">Log file</label>
                <select name="file" class="form-select" onchange="this.form.submit()">
                    <?php foreach ($srvLogs as $lf): ?>
                        <option value="<?= e($lf) ?>" <?= $lf === $srvFile ? 'selected' : '' ?>><?= e($lf) ?> — <?= number_format((int) ((@filesize($lf) ?: 0) / 1024)) ?> KB</option>
                    <?php endforeach; ?>
                </select>
            </div>
            <div class="col-md-2">
                <label class="form-label mb-1 small">Lines</label>
                <select name="n" class="form-select" onchange="this.form.submit()">
                    <?php foreach ([100, 200, 500, 1000, 2000] as $n): ?><option value="<?= $n ?>" <?= $srvN === $n ? 'selected' : '' ?>><?= $n ?></option><?php endforeach; ?>
                </select>
            </div>
            <div class="col-md-3">
                <label class="form-label mb-1 small">Search</label>
                <input type="text" name="sq" value="<?= e($srvQ) ?>" class="form-control" placeholder="429, PHP Fatal, autoindex…">
            </div>
            <div class="col-md-2"><button class="btn btn-primary w-100"><i class="ti ti-search me-1"></i>Filter</button></div>
        </form>
    </div></div>

    <div class="card">
        <div class="card-header py-2">
            <h3 class="card-title small mb-0"><i class="ti ti-server me-1"></i><?= number_format(count($srvLines)) ?> line(s) — newest first
                <span class="text-secondary fw-normal">· <?= e(basename($srvFile)) ?> · updated <?= e(date('Y-m-d H:i', (int) (@filemtime($srvFile) ?: 0))) ?></span>
            </h3>
            <div class="card-actions">
                <a href="?<?= e(http_build_query(array_filter(['view'=>'server','file'=>$srvFile,'n'=>$srvN,'sq'=>$srvQ], fn($v)=>$v!=='' && $v!==null))) ?>" class="btn btn-sm btn-outline-secondary"><i class="ti ti-refresh me-1"></i>Refresh</a>
            </div>
        </div>
        <div class="card-body p-0">
            <?php if (!$srvLines): ?>
                <div class="text-center text-secondary py-4">No matching lines. 🎉</div>
            <?php else: ?>
                <pre class="mb-0 small" style="max-height:640px;overflow:auto;white-space:pre-wrap;word-break:break-word;padding:.75rem;background:var(--m-surface-2)"><?php foreach ($srvLines as $l) echo e((string) $l) . "\n"; ?></pre>
            <?php endif; ?>
        </div>
        <div class="card-footer small text-secondary">Read-only tail — the file is never modified. Only files discovered on this host can be opened.</div>
    </div>
    <?php endif; ?>

    <?php endif; /* view */ ?>

</div></div>
<?php require __DIR__ . '/../includes/footer.php'; ?>
