<?php
// Async delete-user endpoint.
//
// Why this exists: the inline delete path in pages/user-management.php
// could exceed Apache's mod_proxy_fcgi default 60s timeout for users with
// many sites/DBs, leaving the browser with ERR_INCOMPLETE_CHUNKED_ENCODING
// even though the back-end completed. This endpoint:
//
//   - POST action=delete  — validates, writes spec+params, kicks off
//                           whp-delete-user@<job_id>.service via
//                           systemctl start --no-block. Returns 202
//                           with {status, job_id, accepted}.
//   - GET  action=status  — polls the JSONL stream for the job. Returns
//                           the parsed event list and a done flag.
//
// Pattern mirrors api/sites-bulk.php (2026.05.198 async-fork rollout).

header('Content-Type: application/json');

check_api_permission('user-management');

if ($_SERVER['REQUEST_METHOD'] !== 'POST' && $_SERVER['REQUEST_METHOD'] !== 'GET') {
    http_response_code(405);
    echo json_encode(['status' => 'error', 'message' => 'method_not_allowed']);
    exit;
}

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

// Status polls are GET-only and skip CSRF (read-only, repeated per tick).
$is_status_poll = ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'status');

if (!$is_status_poll && !validate_csrf_token()) {
    http_response_code(403);
    echo json_encode(['status' => 'error', 'message' => 'csrf']);
    exit;
}

$jobsDir = '/docker/whp/sql/delete-user-jobs';

function whp_delete_user_job_id(string $username): string {
    $stamp = gmdate('Ymd-His');
    $rand  = bin2hex(random_bytes(4));
    return "delete-user-{$username}-{$stamp}-{$rand}";
}

function whp_delete_user_read_status(string $job_id): array {
    $jobsDir = '/docker/whp/sql/delete-user-jobs';
    $path    = $jobsDir . '/' . $job_id . '.jsonl';
    if (!file_exists($path)) {
        return ['status' => 'pending', 'events' => [], 'done' => false];
    }
    $lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
    if ($lines === false) {
        return ['status' => 'error', 'message' => 'failed_to_read_jsonl', 'events' => [], 'done' => false];
    }

    $events    = [];
    $done      = false;
    $cancelled = false;
    $summary   = null;
    $finalMsg  = null;
    $errorTxt  = null;
    $duration  = null;
    $teardown  = null;
    $finalStatus = null;
    foreach ($lines as $line) {
        $row = json_decode($line, true);
        if (!is_array($row)) continue;
        if (!empty($row['__done__'])) {
            $done        = true;
            $finalStatus = $row['status']   ?? null;
            $finalMsg    = $row['msg']      ?? null;
            $summary     = $row['summary']  ?? null;
            $teardown    = $row['teardown'] ?? null;
            $duration    = $row['duration_s'] ?? null;
            $errorTxt    = $row['error']    ?? null;
            continue;
        }
        if (!empty($row['__cancelling__'])) {
            $cancelled = true;
            continue;
        }
        if (!empty($row['__queued__']) || !empty($row['__started__'])) continue;
        // Per-step events: {step, status, ...meta}
        $events[] = $row;
    }

    $out = [
        'status'    => 'ok',
        'done'      => $done,
        'events'    => $events,
        'cancelled' => $cancelled,
    ];
    if ($summary !== null)     $out['summary']      = $summary;
    if ($teardown !== null)    $out['teardown']     = $teardown;
    if ($finalMsg !== null)    $out['final_msg']    = $finalMsg;
    if ($finalStatus !== null) $out['final_status'] = $finalStatus;
    if ($duration !== null)    $out['duration_s']   = $duration;
    if ($errorTxt !== null)    $out['error']        = $errorTxt;
    return $out;
}

function whp_delete_user_launch_worker(string $job_id): array {
    $unitInstalled = is_file('/etc/systemd/system/whp-delete-user@.service')
                  || is_file('/usr/lib/systemd/system/whp-delete-user@.service');

    if ($unitInstalled) {
        $paramsDir = '/var/lib/whp/delete-user';
        if (!is_dir($paramsDir)) {
            @mkdir($paramsDir, 0700, true);
        }
        $paramsPath = $paramsDir . '/' . $job_id . '.params';
        // EnvironmentFile parsing is not a shell; KEY=value taken literally.
        // $job_id already passed the [A-Za-z0-9_\-]+ gate so no shell metas
        // or newlines can leak into the env file.
        $paramsBody = "DELETE_USER_JOB_ID={$job_id}\n";
        if (file_put_contents($paramsPath, $paramsBody, LOCK_EX) === false) {
            error_log("delete-user-ajax: failed to write params file {$paramsPath}");
            return ['success' => false, 'error' => 'failed_to_stage_params'];
        }
        @chmod($paramsPath, 0600);

        $unitName = "whp-delete-user@{$job_id}.service";
        $startCmd = "/usr/bin/systemctl start --no-block " . escapeshellarg($unitName);
        $output = []; $returnCode = 0;
        exec($startCmd . ' 2>&1', $output, $returnCode);
        if ($returnCode !== 0) {
            error_log("delete-user-ajax: failed to start {$unitName} (rc={$returnCode}): " . implode("\n", $output));
            return ['success' => false, 'error' => 'unit_start_failed: ' . implode(' ', $output)];
        }
        return ['success' => true, 'mode' => 'systemd'];
    }

    // Legacy fallback (pre-staging-script path). Backgrounded PHP with output
    // appended to the per-job log file matching the systemd unit's
    // StandardOutput= path. Mirrors api/sites-bulk.php fallback.
    $scriptPath = '/docker/whp/web/scripts/run-delete-user.php';
    if (!file_exists($scriptPath)) {
        $scriptPath = '/root/whp/scripts/run-delete-user.php';
        if (!file_exists($scriptPath)) {
            return ['success' => false, 'error' => 'worker_script_not_found'];
        }
    }
    $logFile  = "/var/log/whp-delete-user-{$job_id}.log";
    $legacyCmd = "php " . escapeshellarg($scriptPath) . " " .
                 escapeshellarg($job_id) . " > " . escapeshellarg($logFile) . " 2>&1 &";
    $output = []; $returnCode = 0;
    exec($legacyCmd, $output, $returnCode);
    if ($returnCode !== 0) {
        error_log("delete-user-ajax: legacy spawn failed rc={$returnCode}");
        return ['success' => false, 'error' => 'legacy_spawn_failed'];
    }
    return ['success' => true, 'mode' => 'legacy'];
}

// -------------- Dispatcher --------------

if ($action === 'status') {
    $job_id = (string)($_REQUEST['job_id'] ?? '');
    $job_id = preg_replace('/[^A-Za-z0-9_\-]/', '', $job_id);
    if ($job_id === '') {
        http_response_code(400);
        echo json_encode(['status' => 'error', 'message' => 'missing_job_id']);
        exit;
    }
    echo json_encode(whp_delete_user_read_status($job_id));
    exit;
}

if ($action !== 'delete') {
    http_response_code(400);
    echo json_encode(['status' => 'error', 'message' => 'unknown_action']);
    exit;
}

// --- delete: validate, spawn worker, return 202 ---------------------------

$username = (string)($_POST['username'] ?? '');

// Conservative regex matches usermgmt::validate_username_or_throw.
if (!preg_match('/^[a-z][a-z0-9_-]{0,31}$/', $username)
    || str_contains($username, '..') || str_contains($username, '/')) {
    http_response_code(400);
    echo json_encode(['status' => 'error', 'message' => 'invalid_username']);
    exit;
}

if ($username === 'whp') {
    http_response_code(403);
    echo json_encode(['status' => 'error', 'message' => 'cannot_delete_system_user']);
    exit;
}

// Non-root may not delete other users.
$is_root = (defined('AUTH_USER') && AUTH_USER === 'root');
if (!$is_root) {
    http_response_code(403);
    echo json_encode(['status' => 'error', 'message' => 'forbidden']);
    exit;
}

// Confirm the UNIX user actually exists — refuse to spawn a worker for a
// no-op (also surfaces typos before the user wonders why no progress shows).
$idOut = trim((string)shell_exec('/usr/bin/getent passwd ' . escapeshellarg($username) . ' 2>/dev/null'));
if ($idOut === '') {
    http_response_code(404);
    echo json_encode(['status' => 'error', 'message' => 'user_not_found']);
    exit;
}

$actor = defined('AUTH_USER') ? AUTH_USER : 'unknown';

if (!is_dir($jobsDir)) {
    @mkdir($jobsDir, 0750, true);
}

$job_id = whp_delete_user_job_id($username);
$spec = [
    'job_id'     => $job_id,
    'username'   => $username,
    'actor'      => $actor,
    'created_at' => time(),
];
$specPath = $jobsDir . '/' . $job_id . '.json';
if (file_put_contents($specPath, json_encode($spec, JSON_PRETTY_PRINT), LOCK_EX) === false) {
    http_response_code(500);
    echo json_encode(['status' => 'error', 'message' => 'failed_to_write_spec']);
    exit;
}
@chmod($specPath, 0640);

$launch = whp_delete_user_launch_worker($job_id);
if (!$launch['success']) {
    @unlink($specPath);
    http_response_code(500);
    echo json_encode(['status' => 'error', 'message' => $launch['error']]);
    exit;
}

http_response_code(202);
echo json_encode([
    'status'   => 'ok',
    'job_id'   => $job_id,
    'accepted' => true,
    'mode'     => $launch['mode'] ?? null,
]);
