<?php
/**
 * Mesh Security Dashboard API
 * Uses existing dashboard bootstrap (credentials.enc → CredentialStore → Database)
 * Endpoint: /api/mesh-api.php?action=<endpoint>
 */
define('DASHBOARD_ROOT', realpath(__DIR__ . '/..'));
require_once DASHBOARD_ROOT . '/core/config.php';
require_once '/var/www/shared/db.php';

ini_set('display_errors', '0');
error_reporting(0);
header('Content-Type: application/json');

// Auth: WireGuard IP OR HMAC signature
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
$wg_net = '10.69.69.';
$hmac_key = ''; // loaded from credentials
try { $hmac_key = CredentialStore::get('mesh_hmac_key') ?? ''; } catch (\Throwable $e) {}
if (!$hmac_key) $hmac_key = 'mesh-v1-' . (defined('ENCRYPTION_KEY') ? substr(ENCRYPTION_KEY, 0, 16) : 'fallback');

$authorized = false;
if (strpos($ip, $wg_net) === 0 || $ip === '127.0.0.1' || $ip === '::1') {
    $authorized = true;
}
// HMAC auth for nodes without wg7 access
$sig_header = $_SERVER['HTTP_X_MESH_SIG'] ?? '';
$sig_ts = $_SERVER['HTTP_X_MESH_TS'] ?? '';
if ($sig_header && $sig_ts && abs(time() - (int)$sig_ts) < 300) {
    $body = file_get_contents('php://input');
    $expected = hash_hmac('sha256', $sig_ts . ':' . $body, $hmac_key);
    if (hash_equals($expected, $sig_header)) {
        $authorized = true;
    }
}
if (!$authorized) {
    http_response_code(403);
    exit(json_encode(['error' => 'unauthorized']));
}

function api_ok($data) {
    echo json_encode(['status' => 'ok', 'data' => $data, 'ts' => date('c')], JSON_PRETTY_PRINT);
    exit;
}

function api_err($code, $msg) {
    http_response_code($code);
    echo json_encode(['error' => $msg]);
    exit;
}

$action = $_GET['action'] ?? '';

switch ($action) {

    case 'node-health':
        $rows = Database::fetchAll("
            SELECT n.node_id,
                (SELECT MAX(started_at) FROM sec_tool_runs t WHERE t.node_id = n.node_id) AS last_heartbeat,
                (SELECT COUNT(*) FROM sec_tool_runs t2 WHERE t2.node_id = n.node_id AND t2.status_code = 5001) AS tool_run_count,
                COALESCE((SELECT MAX(level) FROM sec_lockdown_state l WHERE l.node_id = n.node_id AND l.status_code = 5001 AND l.deactivated_at IS NULL), 0) AS lockdown_level,
                (SELECT COUNT(*) FROM sec_canary_triggers c WHERE c.node_id = n.node_id) AS canary_count,
                (SELECT COUNT(*) FROM sec_bash_integrity b WHERE b.node_id = n.node_id AND b.matched = 0) AS integrity_fails
            FROM (SELECT DISTINCT node_id FROM sec_tool_runs UNION SELECT DISTINCT node_id FROM sec_lockdown_state) n
        ");
        api_ok($rows);
        break;

    case 'tool-status':
        $nid = $_GET['node_id'] ?? '';
        if (!$nid) api_err(400, 'missing_node_id');
        $rows = Database::fetchAll(
            "SELECT id, node_id, tool_name, exit_code, duration_ms, started_at, status_code
             FROM sec_tool_runs WHERE node_id = :nid ORDER BY started_at DESC LIMIT 100",
            [':nid' => $nid]
        );
        api_ok($rows);
        break;

    case 'security-events':
        $lim = min((int)($_GET['limit'] ?? 100), 500);
        $rows = Database::fetchAll(
            "SELECT id, node_id, source_tag, event_type, severity, created_at
             FROM sec_events ORDER BY created_at DESC LIMIT " . $lim
        );
        api_ok($rows);
        break;

    case 'lockdown-state':
        $rows = Database::fetchAll(
            "SELECT node_id, level, activated_at, deactivated_at, activated_by, status_code
             FROM sec_lockdown_state ORDER BY activated_at DESC LIMIT 50"
        );
        api_ok($rows);
        break;

    case 'canary-triggers':
        $rows = Database::fetchAll(
            "SELECT node_id, canary_type, detected_at, response_action, status_code
             FROM sec_canary_triggers ORDER BY detected_at DESC LIMIT 100"
        );
        api_ok($rows);
        break;

    case 'bash-integrity':
        $nid = $_GET['node_id'] ?? '';
        if (!$nid) api_err(400, 'missing_node_id');
        $rows = Database::fetchAll(
            "SELECT file_path, expected_hash, actual_hash, matched, checked_at
             FROM sec_bash_integrity WHERE node_id = :nid ORDER BY checked_at DESC LIMIT 100",
            [':nid' => $nid]
        );
        api_ok($rows);
        break;

    case 'key-rotations':
        $rows = Database::fetchAll(
            "SELECT node_id, key_type, rotated_at, verified, status_code
             FROM sec_key_rotations ORDER BY rotated_at DESC LIMIT 100"
        );
        api_ok($rows);
        break;

    // === WRITE ENDPOINTS (POST, HMAC-verified) ===

    case 'report-tool-run':
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') api_err(405, 'post_only');
        $in = json_decode(file_get_contents('php://input'), true);
        if (!$in || !isset($in['node_id'], $in['tool_name'])) api_err(400, 'missing_fields');
        Database::insert(
            "INSERT INTO sec_tool_runs (node_id, tool_name, command, exit_code, stdout_enc, stderr_enc, started_at, finished_at, duration_ms, status_code)
             VALUES (:nid, :tn, :cmd, :ec, :out, :err, :sa, :fa, :dur, 5001)",
            [
                ':nid' => $in['node_id'],
                ':tn'  => $in['tool_name'],
                ':cmd' => $in['command'] ?? '',
                ':ec'  => (int)($in['exit_code'] ?? 0),
                ':out' => $in['stdout_enc'] ?? '',
                ':err' => $in['stderr_enc'] ?? '',
                ':sa'  => $in['started_at'] ?? date('Y-m-d H:i:s'),
                ':fa'  => $in['finished_at'] ?? date('Y-m-d H:i:s'),
                ':dur' => (int)($in['duration_ms'] ?? 0),
            ]
        );
        api_ok(['recorded' => true]);
        break;

    case 'report-heartbeat':
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') api_err(405, 'post_only');
        $in = json_decode(file_get_contents('php://input'), true);
        if (!$in || !isset($in['node_id'])) api_err(400, 'missing_node_id');
        Database::insert(
            "INSERT INTO sec_tool_runs (node_id, tool_name, command, exit_code, started_at, duration_ms, status_code)
             VALUES (:nid, 'heartbeat', 'hb_send', 0, NOW(), 0, 5001)",
            [':nid' => $in['node_id']]
        );
        api_ok(['heartbeat' => 'received', 'node_id' => $in['node_id'], 'ts' => date('c')]);
        break;

    case 'report-event':
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') api_err(405, 'post_only');
        $in = json_decode(file_get_contents('php://input'), true);
        if (!$in || !isset($in['node_id'], $in['event_type'])) api_err(400, 'missing_fields');
        Database::insert(
            "INSERT INTO sec_events (node_id, source_tag, event_type, severity, details_enc, ip_address_enc, created_at, status_code)
             VALUES (:nid, :src, :et, :sev, :det, :ip, NOW(), 4001)",
            [
                ':nid' => $in['node_id'],
                ':src' => $in['source'] ?? 's3rv1c3agent',
                ':et'  => $in['event_type'],
                ':sev' => $in['severity'] ?? 'info',
                ':det' => $in['details'] ?? '',
                ':ip'  => $_SERVER['REMOTE_ADDR'] ?? '',
            ]
        );
        api_ok(['event_logged' => true]);
        break;

    default:
        api_err(404, 'unknown_action');
}
