<?php
// health_check.php - System health snapshot.
// - GET (HTML)        : Master/Endie user via authenticated session
// - GET ?format=json  : same gate; machine-readable output
// - GET ?cron=1&token=: token-gated (config.backup_sync.cron_key) for crons
// READ-ONLY: never writes to DB or app data. Only writes its own health log
// at backups/health_log.json (and rotates to .prev on each run).

require 'db.php';
require_once 'helpers.php';

$wantJson = (($_GET['format'] ?? '') === 'json');
$isCron   = isset($_GET['cron']);

if ($isCron) {
    $expected = (string) getConfig('backup_sync.cron_key', '');
    $provided = (string) ($_GET['token'] ?? '');
    if ($expected === '' || !hash_equals($expected, $provided)) {
        http_response_code(403);
        header('Content-Type: text/plain');
        die("Forbidden.");
    }
    $wantJson = true; // crons always read JSON
} else {
    if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
        http_response_code(401);
        header('Content-Type: text/plain');
        die("Authentication required.");
    }
    $username = strtolower((string)($_SESSION['user'] ?? ''));
    $role     = strtolower((string)($_SESSION['role'] ?? ''));
    if ($username !== 'endie' && $role !== 'master') {
        http_response_code(403);
        header('Content-Type: text/plain');
        die("Master/Endie access only.");
    }
}

function safeQuery(PDO $pdo, string $sql, array $params = []) {
    try {
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    } catch (Throwable $e) {
        return null;
    }
}

$report = [
    'generated_at' => date('c'),
    'server' => [
        'php_version'   => PHP_VERSION,
        'sapi'          => php_sapi_name(),
        'opcache'       => function_exists('opcache_get_status') ? @opcache_get_status(false) : null,
        'disk_free'     => @disk_free_space(__DIR__),
        'disk_total'    => @disk_total_space(__DIR__),
        'memory_limit'  => ini_get('memory_limit'),
        'max_exec_time' => ini_get('max_execution_time'),
        'session_save_path_writable' => is_writable(session_save_path() ?: sys_get_temp_dir()),
    ],
    'database' => ['connection' => 'unknown'],
    'tables'    => [],
    'indexes'   => [],
    'backup'    => [],
    'auth'      => [],
    'issues'    => [],
];

// --- Database connection -------------------------------------------------
try {
    $pdo->query("SELECT 1");
    $report['database']['connection'] = 'ok';
} catch (Throwable $e) {
    $report['database']['connection'] = 'fail';
    $report['issues'][] = 'database connection failed: ' . $e->getMessage();
}

// --- Required tables + row counts ---------------------------------------
// NOTE: Retail sales are NOT stored in a separate table. They live in the
// `jobs` table with model='Retail Sale' or remarks='Retail Order' (see
// api.php). We surface a derived count under report.retail_sales instead.
$required = ['jobs', 'stock_list', 'users', 'activity_logs', 'expenses', 'login_attempts', 'stock_movements'];
$existing = [];
$res = safeQuery($pdo, "SHOW TABLES");
if ($res) {
    while ($r = $res->fetch(PDO::FETCH_NUM)) { $existing[] = $r[0]; }
}
foreach ($required as $t) {
    if (!in_array($t, $existing, true)) {
        $report['tables'][$t] = ['exists' => false];
        $report['issues'][] = "missing table: $t";
        continue;
    }
    $count = null;
    $stmt = safeQuery($pdo, "SELECT COUNT(*) FROM `" . $t . "`");
    if ($stmt) { $count = (int) $stmt->fetchColumn(); }
    $size = null;
    $stmt = safeQuery($pdo, "SELECT ROUND((data_length + index_length) / 1024) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?", [$t]);
    if ($stmt) { $size = (int) $stmt->fetchColumn(); }
    $report['tables'][$t] = ['exists' => true, 'rows' => $count, 'size_kb' => $size];
}

// Derived: retail-sale rows inside the jobs table (informational, not gating)
$stmt = safeQuery($pdo,
    "SELECT COUNT(*) FROM jobs WHERE is_deleted = 0 AND (model = 'Retail Sale' OR remarks = 'Retail Order')"
);
if ($stmt) {
    $report['retail_sales'] = ['rows_in_jobs' => (int) $stmt->fetchColumn()];
}

// --- Critical indexes presence (subset of optimize_database.php list) ----
$expectedIndexes = [
    ['jobs', 'idx_jobs_job_id'],
    ['jobs', 'idx_jobs_branch_status_date'],
    ['stock_list', 'idx_stock_part_name_norm'],
    ['activity_logs', 'idx_activity_created_at'],
    ['login_attempts', 'idx_login_username_time'],
];
foreach ($expectedIndexes as [$tbl, $idx]) {
    if (!in_array($tbl, $existing, true)) { continue; }
    $stmt = safeQuery($pdo,
        "SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ? LIMIT 1",
        [$tbl, $idx]
    );
    $present = $stmt ? (bool) $stmt->fetchColumn() : false;
    $report['indexes'][$idx] = ['table' => $tbl, 'present' => $present];
    if (!$present) {
        $report['issues'][] = "missing index $idx on $tbl (run optimize_database.php)";
    }
}

// --- Backup recency ------------------------------------------------------
$statusFile = __DIR__ . '/backups/sync_last_status.json';
if (is_file($statusFile)) {
    $payload = json_decode((string) @file_get_contents($statusFile), true);
    if (is_array($payload)) {
        $report['backup'] = $payload;
        $when = isset($payload['time']) ? strtotime((string)$payload['time']) : false;
        if ($when && (time() - $when) > 36 * 3600) {
            $report['issues'][] = 'last successful backup older than 36 hours';
        }
        if (empty($payload['ok'])) {
            $report['issues'][] = 'last backup attempt did not succeed';
        }
    } else {
        $report['issues'][] = 'sync_last_status.json present but unreadable';
    }
} else {
    $report['issues'][] = 'no sync_last_status.json yet (run sync_to_backup.php cron at least once)';
}

// --- Recent auth signal --------------------------------------------------
$stmt = safeQuery($pdo,
    "SELECT COUNT(*) FROM login_attempts WHERE success = 0 AND attempt_time > DATE_SUB(NOW(), INTERVAL 1 HOUR)"
);
if ($stmt) {
    $report['auth']['failed_last_hour'] = (int) $stmt->fetchColumn();
    if ($report['auth']['failed_last_hour'] > 20) {
        $report['issues'][] = 'high failed-login volume in last hour: ' . $report['auth']['failed_last_hour'];
    }
}

// --- Persist a copy for trending ----------------------------------------
$logDir = __DIR__ . '/backups';
if (is_dir($logDir) && is_writable($logDir)) {
    $logFile = $logDir . '/health_log.json';
    if (is_file($logFile)) { @copy($logFile, $logFile . '.prev'); }
    @file_put_contents($logFile, json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}

// --- Output --------------------------------------------------------------
if ($wantJson) {
    header('Content-Type: application/json');
    echo json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    exit;
}

header('Content-Type: text/html; charset=utf-8');
$statusOk = empty($report['issues']);
?>
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>Health Check</title>
<style>
body{font-family:Arial,sans-serif;background:#f5f6fa;margin:20px;color:#222}
.container{max-width:1000px;margin:0 auto;background:#fff;padding:24px;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,0.08)}
h1{margin-top:0}
.status{padding:12px 16px;border-radius:6px;margin-bottom:16px;font-weight:bold}
.ok{background:#d4edda;color:#155724;border-left:6px solid #28a745}
.bad{background:#f8d7da;color:#721c24;border-left:6px solid #dc3545}
table{width:100%;border-collapse:collapse;margin:10px 0}
th,td{padding:8px;border-bottom:1px solid #e1e5e9;text-align:left;font-size:14px}
th{background:#34495e;color:#fff}
.kv td:first-child{width:200px;font-weight:bold;color:#555}
.pre{background:#f8f9fa;padding:12px;border-radius:6px;font-family:monospace;font-size:12px;white-space:pre-wrap;word-break:break-word}
.muted{color:#777}
</style></head>
<body><div class="container">
<h1>System Health</h1>
<div class="status <?= $statusOk ? 'ok' : 'bad' ?>">
    <?= $statusOk ? 'All checks passed.' : (count($report['issues']) . ' issue(s) found.') ?>
    <span class="muted">- generated <?= htmlspecialchars($report['generated_at']) ?></span>
</div>
<?php if (!$statusOk): ?>
    <h3>Issues</h3>
    <ul><?php foreach ($report['issues'] as $i): ?><li><?= htmlspecialchars($i) ?></li><?php endforeach; ?></ul>
<?php endif; ?>

<h3>Server</h3>
<table class="kv">
    <tr><td>PHP</td><td><?= htmlspecialchars(PHP_VERSION) ?> (<?= htmlspecialchars(php_sapi_name()) ?>)</td></tr>
    <tr><td>Memory Limit</td><td><?= htmlspecialchars(ini_get('memory_limit')) ?></td></tr>
    <tr><td>Disk Free</td><td><?= $report['server']['disk_free'] !== false ? number_format($report['server']['disk_free']/1024/1024, 1) . ' MB' : 'n/a' ?></td></tr>
    <tr><td>OPcache</td><td><?= isset($report['server']['opcache']['opcache_enabled']) && $report['server']['opcache']['opcache_enabled'] ? 'enabled' : 'disabled' ?></td></tr>
</table>

<h3>Tables</h3>
<table>
<tr><th>Table</th><th>Exists</th><th>Rows</th><th>Size (KB)</th></tr>
<?php foreach ($report['tables'] as $name => $info): ?>
<tr>
    <td><?= htmlspecialchars($name) ?></td>
    <td><?= !empty($info['exists']) ? '&#10003;' : '&#10007;' ?></td>
    <td><?= htmlspecialchars((string)($info['rows'] ?? '')) ?></td>
    <td><?= htmlspecialchars((string)($info['size_kb'] ?? '')) ?></td>
</tr>
<?php endforeach; ?>
</table>

<h3>Critical Indexes</h3>
<table>
<tr><th>Index</th><th>Table</th><th>Present</th></tr>
<?php foreach ($report['indexes'] as $name => $info): ?>
<tr>
    <td><?= htmlspecialchars($name) ?></td>
    <td><?= htmlspecialchars($info['table']) ?></td>
    <td><?= !empty($info['present']) ? '&#10003;' : '&#10007;' ?></td>
</tr>
<?php endforeach; ?>
</table>

<h3>Last Backup</h3>
<div class="pre"><?= htmlspecialchars(json_encode($report['backup'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) ?></div>

<h3>Auth (last hour)</h3>
<table class="kv">
    <tr><td>Failed logins</td><td><?= htmlspecialchars((string)($report['auth']['failed_last_hour'] ?? 'n/a')) ?></td></tr>
</table>

<p style="margin-top:24px"><a href="dashboard.php">&larr; Dashboard</a> | <a href="?format=json">JSON</a></p>
</div></body></html>
