<?php
/**
 * subspace-audit.php — shared identity + audit + attribution helpers for the Subspace API layer.
 * Ray's order 2026-10-16 (via Solace): log EVERY interaction with the interactor's citadel id; attribute content to
 * Nikki (13) / Jon (27) / Art Desk (99) while recording the ACTUAL individual. Zora.
 *
 * Identity sources, in order: $_SESSION['apiUser'] (php-crud-api apiKeyDbAuth; authkeys.user_id/role),
 * Bearer JWT (api-content.php, img.php; claims user_id/role), else anonymous.
 * Never trusts a client-supplied uploaded_by/author_id: the server stamps them.
 */
if (!defined('SUBSPACE_AUDIT_LOADED')) {
define('SUBSPACE_AUDIT_LOADED', true);
define('SUBSPACE_ART_DESK', 99);

function subspace_request_id(): string {
    static $rid = null;
    if ($rid === null) $rid = substr(bin2hex(random_bytes(8)), 0, 16);
    return $rid;
}

/** @return array{user_id:?string, role:?string, auth_kind:string} */
function subspace_identity(?object $jwt_payload = null): array {
    if ($jwt_payload) {
        return ['user_id' => (string)($jwt_payload->user_id ?? $jwt_payload->sub ?? ''), 'role' => $jwt_payload->role ?? null, 'auth_kind' => 'jwt'];
    }
    if (!empty($_SESSION['apiUser']['user_id'])) {
        return ['user_id' => (string)$_SESSION['apiUser']['user_id'], 'role' => $_SESSION['apiUser']['role'] ?? null, 'auth_kind' => 'apikey'];
    }
    return ['user_id' => null, 'role' => null, 'auth_kind' => 'anon'];
}

/** Site-side policy row (site_members) for a citadel user; desk/Art Desk when absent. */
function subspace_member(PDO $pdo, ?string $user_id, int $site_id = 1): array {
    $m = ['site_role' => 'desk', 'artist_id' => SUBSPACE_ART_DESK, 'label' => null];
    if ($user_id === null || $user_id === '') return $m;
    try {
        $st = $pdo->prepare('SELECT site_role, artist_id, label FROM site_members WHERE site_id = ? AND user_id = ?');
        $st->execute([$site_id, $user_id]);
        if ($row = $st->fetch(PDO::FETCH_ASSOC)) {
            $m['site_role'] = $row['site_role']; $m['artist_id'] = $row['artist_id'] ?? SUBSPACE_ART_DESK; $m['label'] = $row['label'];
        }
    } catch (Throwable $e) { /* policy table missing: fall through to desk */ }
    return $m;
}

function subspace_is_admin(array $member, ?string $cred_role): bool {
    return in_array($member['site_role'], ['super_admin', 'admin'], true) || $cred_role === 'super_admin';
}

/** Display attribution for a write: artists attribute to themselves, admins may override to any artists.id, everyone else -> Art Desk. */
function subspace_resolve_artist(PDO $pdo, array $member, ?string $cred_role, $requested): int {
    if ($requested !== null && $requested !== '' && subspace_is_admin($member, $cred_role)) {
        $st = $pdo->prepare('SELECT id FROM artists WHERE id = ?'); $st->execute([(int)$requested]);
        if ($st->fetchColumn()) return (int)$requested;
    }
    return (int)($member['artist_id'] ?? SUBSPACE_ART_DESK);
}

/** Append one audit row. Never throws into the request path. */
function subspace_audit(PDO $pdo, array $id, string $action, string $target, $target_id = null, ?int $status = null, ?array $detail = null, ?string $label = null): void {
    try {
        $st = $pdo->prepare('INSERT INTO audit_log (user_id, user_label, role, auth_kind, action, target, target_id, method, path, status, ip, user_agent, detail, request_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
        $st->execute([
            $id['user_id'] ?: null, $label, $id['role'], $id['auth_kind'], $action, $target,
            $target_id === null ? null : (string)$target_id,
            $_SERVER['REQUEST_METHOD'] ?? 'CLI', substr($_SERVER['REQUEST_URI'] ?? '', 0, 255), $status,
            $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? null,
            substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255),
            $detail === null ? null : json_encode($detail, JSON_UNESCAPED_SLASHES), subspace_request_id(),
        ]);
    } catch (Throwable $e) {
        error_log('subspace-audit: ' . $e->getMessage());
    }
}

/** Tables whose writes carry attribution. */
function subspace_attributed_tables(): array {
    return ['posts', 'media', 'gallery_items', 'carousel_slides', 'content_blocks', 'artists', 'faqs', 'testimonials'];
}
}
