<?php
// ============================================================
//  MediCore — System Status API
//  File: api/status.php
//  Powers the top status bar: server/DB health check, unread
//  counters (notifications / push messages / tasks), and a
//  lightweight SSE stream for near-real-time updates without
//  needing a separate WebSocket process.
// ============================================================
require_once '../includes/db.php';
require_once '../includes/auth.php';
require_once '../includes/notify.php';
require_login();

$uid = current_user_id();

// SSE is a GET request (EventSource can't send a JSON POST body);
// everything else in this app is POST+JSON, so branch on method.
$action = $_SERVER['REQUEST_METHOD'] === 'GET'
    ? trim($_GET['action'] ?? 'ping')
    : trim((json_decode(file_get_contents('php://input'), true) ?? [])['action'] ?? 'ping');

// Release the session lock immediately — both actions below can run
// for a little while (the SSE stream especially), and PHP's default
// file-based session handler locks the session file exclusively.
// Without this, every other tab/AJAX call from the same browser
// would queue behind this request until it finishes.
session_write_close();

try {
    match ($action) {
        'stream' => api_stream($uid),
        default  => api_ping($uid),
    };
} catch (Throwable $e) {
    fail($e->getMessage());
}

// ── One-shot health check + unread counts ───────────────────────
function api_ping(int $uid): void {
    try {
        $t0 = microtime(true);
        db_val('SELECT 1');
        $ms = round((microtime(true) - $t0) * 1000, 1);

        // Detect a down→up transition and log it (best-effort; if the
        // settings write itself fails, the outer catch below handles it).
        $cfg = get_settings();
        if (($cfg['server_health'] ?? 'ok') === 'down') {
            log_system_event('server_recovered', 'Database connection restored.');
            save_setting('server_health', 'ok');
        }

        generate_system_notifications(); // rate-limited internally to once/hour

        ok(array_merge(['server' => 'healthy', 'db_ms' => $ms], counts_for($uid)));
    } catch (Throwable $e) {
        try {
            save_setting('server_health', 'down');
            error_log('MediCore server health check failed: ' . $e->getMessage());
        } catch (Throwable $e2) {
            // DB is truly unreachable — nothing more we can log server-side.
        }
        http_response_code(503);
        fail('Server connection problem detected.');
    }
}

// ── SSE stream: pushes the same payload as api_ping every few
// seconds for up to ~55s, then ends the response. The browser's
// EventSource auto-reconnects, so this behaves like a continuous
// stream from the client's point of view without holding an Apache
// worker open indefinitely.
function api_stream(int $uid): void {
    while (ob_get_level() > 0) ob_end_clean();
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    header('Connection: keep-alive');
    header('X-Accel-Buffering: no'); // no-op on Apache, harmless; helps if fronted by nginx later
    set_time_limit(60);

    $lastPayload = null;
    $start = time();

    while (time() - $start < 55) {
        if (connection_aborted()) break;

        try {
            db_val('SELECT 1');
            $cfg = get_settings();
            if (($cfg['server_health'] ?? 'ok') === 'down') {
                log_system_event('server_recovered', 'Database connection restored.');
                save_setting('server_health', 'ok');
            }
            $payload = array_merge(['success' => true, 'server' => 'healthy'], counts_for($uid));
        } catch (Throwable $e) {
            try { save_setting('server_health', 'down'); } catch (Throwable $e2) {}
            $payload = ['success' => false, 'server' => 'error'];
        }

        $encoded = json_encode($payload);
        if ($encoded !== $lastPayload) {
            echo "data: $encoded\n\n";
            $lastPayload = $encoded;
        } else {
            echo ": heartbeat\n\n"; // SSE comment line — keeps the connection alive
        }

        if (ob_get_level() > 0) @ob_flush();
        @flush();

        sleep(3);
    }

    exit;
}

function counts_for(int $uid): array {
    ensure_topbar_tables();
    return [
        'unread_notifications' => (int) db_val(
            "SELECT COUNT(*) FROM notifications WHERE user_id = ? AND is_read = 0", [$uid]
        ),
        'unread_push' => (int) db_val(
            "SELECT COUNT(*) FROM push_message_recipients WHERE user_id = ? AND is_read = 0", [$uid]
        ),
        'pending_tasks' => (int) db_val(
            "SELECT COUNT(*) FROM tasks WHERE assigned_to = ? AND status != 'completed'", [$uid]
        ),
    ];
}

function ok(array $extra = []): void {
    header('Content-Type: application/json');
    echo json_encode(array_merge(['success' => true], $extra));
    exit;
}
function fail(string $msg): never {
    header('Content-Type: application/json');
    echo json_encode(['success' => false, 'error' => $msg]);
    exit;
}
