<?php
declare(strict_types=1);

/**
 * SCLS Audit Stream — GET /api/audit/stream.php
 *
 * Server-Sent Events endpoint for real-time audit events.
 * Streams metadata only (no encrypted content).
 * Auth: session token passed as query param (SSE limitation).
 * Timeout: 30 minutes. Keepalive: every 15 seconds.
 */

define('DASHBOARD_ROOT', realpath(__DIR__ . '/../..'));
require_once DASHBOARD_ROOT . '/core/bootstrap.php';

// ── Auth via session (SSE uses query param token fallback) ────────────────
$authOk = AuthChain::isAuthenticated();

// SSE can't send custom headers easily, so we also accept a token param
if (!$authOk && !empty($_GET['token'])) {
    $token = $_GET['token'];
    // Validate against session store — the token is the PHP session ID
    // We already started the session in bootstrap; if the session is valid
    // and authenticated, we're good. The token param is just the SID.
    // (The session was already loaded by bootstrap above via session_start)
    $authOk = AuthChain::isAuthenticated();
}

if (!$authOk) {
    http_response_code(401);
    header('Content-Type: application/json');
    exit(json_encode(['status' => 'error', 'error' => 'Unauthorized']));
}

if (!RBAC::can('view.security')) {
    http_response_code(403);
    header('Content-Type: application/json');
    exit(json_encode(['status' => 'error', 'error' => 'Forbidden']));
}

// ── Engine DB connection ──────────────────────────────────────────────────
function getEngineDbStream(): PDO
{
    static $pdo = null;
    if ($pdo !== null) return $pdo;

    $host   = '172.28.41.2';
    $port   = 3306;
    $dbname = 'engine';
    $user   = 'dashboard';
    $pass   = '';

    if (class_exists('CredentialStore')) {
        try {
            $creds = CredentialStore::get('engine_db');
            if ($creds) {
                $host = $creds['host'] ?? $host;
                $port = (int)($creds['port'] ?? $port);
                $user = $creds['user'] ?? $user;
                $pass = $creds['pass'] ?? $pass;
            }
        } catch (\Throwable $e) {}
    }

    $envFile = '/var/www/private/engine_db.env';
    if ($pass === '' && file_exists($envFile)) {
        $lines = file($envFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
        foreach ($lines as $line) {
            if (str_starts_with(trim($line), '#')) continue;
            [$k, $v] = explode('=', $line, 2) + ['', ''];
            $k = trim($k);
            $v = trim($v, " \t\n\r\"'");
            match ($k) {
                'ENGINE_DB_HOST' => $host = $v,
                'ENGINE_DB_PORT' => $port = (int)$v,
                'ENGINE_DB_USER' => $user = $v,
                'ENGINE_DB_PASS' => $pass = $v,
                'ENGINE_DB_NAME' => $dbname = $v,
                default          => null,
            };
        }
    }

    $dsn = "mysql:host={$host};port={$port};dbname={$dbname};charset=utf8mb4";
    $pdo = new PDO($dsn, $user, $pass, [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
        PDO::ATTR_TIMEOUT            => 10,
    ]);
    return $pdo;
}

try {
    $pdo = getEngineDbStream();
} catch (\Throwable $e) {
    http_response_code(500);
    header('Content-Type: application/json');
    exit(json_encode(['status' => 'error', 'error' => 'Engine DB unavailable']));
}

// ── SSE headers ───────────────────────────────────────────────────────────
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
header('X-Accel-Buffering: no');

// Close the session early so other requests aren't blocked
if (session_status() === PHP_SESSION_ACTIVE) {
    session_write_close();
}

// Disable output buffering
if (function_exists('apache_setenv')) {
    apache_setenv('no-gzip', '1');
}
@ini_set('zlib.output_compression', '0');
while (ob_get_level()) {
    ob_end_flush();
}

// ── Track state ───────────────────────────────────────────────────────────
$lastId        = isset($_GET['last_id']) ? (int)$_GET['last_id'] : 0;
$startTime     = time();
$maxRuntime    = 1800; // 30 minutes
$keepaliveMs   = 15;   // seconds between keepalives
$pollMs        = 500;  // ms between polls
$lastKeepalive = time();

// If no last_id, get the current max to avoid replaying history
if ($lastId === 0) {
    try {
        $stmt = $pdo->query('SELECT COALESCE(MAX(id), 0) FROM sec_audit_log');
        $lastId = (int)$stmt->fetchColumn();
    } catch (\Throwable $e) {
        $lastId = 0;
    }
}

// ── Send initial connection event ─────────────────────────────────────────
echo "event: connected\n";
echo 'data: ' . json_encode([
    'status'  => 'ok',
    'last_id' => $lastId,
    'ts'      => date('c'),
]) . "\n\n";
flush();

// ── Main SSE loop ─────────────────────────────────────────────────────────
$stmt = $pdo->prepare(
    'SELECT id, event_uuid, node_id, tool_id, tool_name, tool_category,
            session_id, status, severity, exit_code, duration_ms,
            trust_score, network_zone, flags, started_at, completed_at
     FROM sec_audit_log
     WHERE id > ?
     ORDER BY id ASC
     LIMIT 50'
);

while (true) {
    // Check timeout
    if ((time() - $startTime) >= $maxRuntime) {
        echo "event: timeout\n";
        echo "data: {\"reason\":\"max_runtime\"}\n\n";
        flush();
        break;
    }

    // Check if client disconnected
    if (connection_aborted()) {
        break;
    }

    // Poll for new entries
    try {
        $stmt->execute([$lastId]);
        $rows = $stmt->fetchAll();

        if (!empty($rows)) {
            foreach ($rows as $row) {
                echo 'id: ' . $row['id'] . "\n";
                echo "event: audit\n";
                echo 'data: ' . json_encode($row) . "\n\n";
                $lastId = (int)$row['id'];
            }
            flush();
        }
    } catch (\Throwable $e) {
        echo "event: error\n";
        echo "data: {\"error\":\"query_failed\"}\n\n";
        flush();
        // Try to reconnect on next iteration
        try {
            $pdo  = null;
            $pdo  = getEngineDbStream();
            $stmt = $pdo->prepare(
                'SELECT id, event_uuid, node_id, tool_id, tool_name, tool_category,
                        session_id, status, severity, exit_code, duration_ms,
                        trust_score, network_zone, flags, started_at, completed_at
                 FROM sec_audit_log
                 WHERE id > ?
                 ORDER BY id ASC
                 LIMIT 50'
            );
        } catch (\Throwable $e2) {
            sleep(5);
        }
    }

    // Keepalive
    if ((time() - $lastKeepalive) >= $keepaliveMs) {
        echo ": keepalive " . date('c') . "\n\n";
        flush();
        $lastKeepalive = time();
    }

    // Sleep 500ms
    usleep($pollMs * 1000);
}
