<?php
// =============================================================================
// /api/admin_users.php
//
//   GET                          → { admins: [...] }
//   POST action=create           → create a new admin user (role-gated)
//   POST action=change_password  → reset password (self skips current_password
//                                  IFF the password is still the default;
//                                  cross-user requires admin role)
//   POST action=change_username  → rename a user (same gating rules as
//                                  change_password — self requires
//                                  current_password unless on default pw)
//   POST action=delete           → delete an admin (cross-user; admin-role only)
//
// Roles: 'admin' (full) and 'viewer' (read-only). The role column existed
// from day one but was never enforced. It is now.
// =============================================================================
require_once __DIR__ . '/_common.php';
// Viewers must reach the per-action logic so they can change their own password
// without being short-circuited by the global admin gate in _common.php.
require_csrf_unless_get(true);
list($action, $body) = read_action();

if ($_SERVER['REQUEST_METHOD'] === 'GET') {
    $rows = db_all('SELECT id, username, role, created_at, last_login_at FROM admin_users ORDER BY id ASC');
    json_response(['admins' => $rows]);
}

$me = auth_user();
$me_is_admin = ((string)($me['role'] ?? 'admin') === 'admin');

if ($action === 'create') {
    if (!$me_is_admin) json_error('Only admins can create users.', 403);
    $username = trim((string)($body['username'] ?? ''));
    $password = (string)($body['password'] ?? '');
    $role_in  = (string)($body['role'] ?? 'admin');
    if ($username === '' || $password === '') {
        json_error('Username and password are both required.', 400);
    }
    if (!preg_match('/^[A-Za-z0-9._-]{3,32}$/', $username)) {
        json_error('Username must be 3–32 characters: letters, numbers, dot, dash, underscore.', 400);
    }
    if (strlen($password) < 6) {
        json_error('Pick a password of at least 6 characters.', 400);
    }
    if (!in_array($role_in, ['admin', 'viewer'], true)) {
        json_error('Role must be either "admin" or "viewer".', 400);
    }
    if (db_one('SELECT id FROM admin_users WHERE username = ?', [$username])) {
        json_error('That username is already taken.', 400);
    }
    try {
        $hash = password_hash($password, PASSWORD_BCRYPT);
        $id = db_insert('INSERT INTO admin_users (username, password_hash, role) VALUES (?, ?, ?)',
                         [$username, $hash, $role_in]);
        json_response(['ok' => true, 'id' => $id]);
    } catch (Throwable $e) {
        panel_log_exception($e, ['endpoint' => 'admin_users/create', 'username' => $username]);
        json_error('Could not create the user. Try again.', 400);
    }
}

if ($action === 'change_password') {
    $id = !empty($body['id']) ? (int)$body['id'] : (int)$me['id'];
    if (empty($body['new_password'])) json_error('A new password is required.', 400);
    if (strlen($body['new_password']) < 6) json_error('Pick a password of at least 6 characters.', 400);

    if ($id === (int)$me['id']) {
        // Self-change rule: skip the current-password prompt ONLY if the user
        // is still using the default password (so the warning banner CTA works
        // out of the box). Otherwise require it.
        $row = db_one('SELECT password_hash FROM admin_users WHERE id = ?', [$id]);
        if (!$row) json_error('User not found.', 404);

        $is_default = defined('PANEL_DEFAULT_ADMIN_PASS')
                   && password_verify(PANEL_DEFAULT_ADMIN_PASS, $row['password_hash']);

        if (!$is_default) {
            if (empty($body['current_password'])) {
                json_error('Enter your current password to confirm the change.', 400);
            }
            if (!auth_check_password($me['username'], $body['current_password'])) {
                json_error('Current password is incorrect.', 403);
            }
        }
    } else {
        // Cross-user reset — admin role required.
        if (!$me_is_admin) json_error('Only admins can reset other users\' passwords.', 403);
    }

    $hash = password_hash($body['new_password'], PASSWORD_BCRYPT);
    // Best-effort: also write password_enc (AES-256-GCM via the master's
    // _secrets/customer_pwd.key) so the master's reveal flow shows the
    // current password. Buyer-shipped lite installs have no key and just
    // skip — bcrypt hash above is still the source of truth for login.
    $enc = _customer_pwd_encrypt_if_possible((string)$body['new_password']);
    if ($enc !== null) {
        db_exec('UPDATE admin_users SET password_hash = ?, password_enc = ? WHERE id = ?', [$hash, $enc, $id]);
    } else {
        db_exec('UPDATE admin_users SET password_hash = ? WHERE id = ?', [$hash, $id]);
    }
    json_response(['ok' => true]);
}

// AES-256-GCM with the master's customer_pwd.key. Returns null when the key
// file isn't accessible (standalone buyer install, file perms changed, etc.).
function _customer_pwd_encrypt_if_possible(string $plaintext): ?string {
    $key_path = getenv('CUSTOMER_PWD_KEY_PATH') ?: '/www/wwwroot/b3studios.online/_secrets/customer_pwd.key';
    if (!is_file($key_path) || !is_readable($key_path)) return null;
    $key = @file_get_contents($key_path);
    if ($key === false || strlen($key) !== 32) return null;
    try {
        $nonce = random_bytes(12);
        $tag   = '';
        $ct = openssl_encrypt($plaintext, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $nonce, $tag);
        if ($ct === false) return null;
        return base64_encode($nonce . $ct . $tag);
    } catch (Throwable $e) { return null; }
}

if ($action === 'change_username') {
    $id = !empty($body['id']) ? (int)$body['id'] : (int)$me['id'];
    $new_username = trim((string)($body['new_username'] ?? ''));
    if ($new_username === '') json_error('A new username is required.', 400);
    if (!preg_match('/^[A-Za-z0-9._-]{3,32}$/', $new_username)) {
        json_error('Username must be 3–32 characters: letters, numbers, dot, dash, underscore.', 400);
    }

    if ($id === (int)$me['id']) {
        // Same gate as change_password: skip the current-password prompt only
        // when still on the bundled default password.
        $row = db_one('SELECT password_hash FROM admin_users WHERE id = ?', [$id]);
        if (!$row) json_error('User not found.', 404);

        $is_default = defined('PANEL_DEFAULT_ADMIN_PASS')
                   && password_verify(PANEL_DEFAULT_ADMIN_PASS, $row['password_hash']);

        if (!$is_default) {
            if (empty($body['current_password'])) {
                json_error('Enter your current password to confirm the change.', 400);
            }
            if (!auth_check_password($me['username'], $body['current_password'])) {
                json_error('Current password is incorrect.', 403);
            }
        }
    } else {
        if (!$me_is_admin) json_error('Only admins can rename other users.', 403);
    }

    if ($new_username === $me['username'] && $id === (int)$me['id']) {
        json_response(['ok' => true, 'username' => $new_username]); // no-op
    }

    $clash = db_one('SELECT id FROM admin_users WHERE username = ? AND id != ?', [$new_username, $id]);
    if ($clash) json_error('That username is already taken.', 400);

    db_exec('UPDATE admin_users SET username = ? WHERE id = ?', [$new_username, $id]);
    if ($id === (int)$me['id']) {
        $_SESSION['admin_username'] = $new_username;
    }

    json_response(['ok' => true, 'username' => $new_username]);
}

if ($action === 'delete') {
    if (!$me_is_admin) json_error('Only admins can delete users.', 403);
    if (empty($body['id'])) json_error('Missing user id.', 400);
    if ((int)$body['id'] === (int)$me['id']) json_error('You can\'t delete yourself.', 400);
    db_exec('DELETE FROM admin_users WHERE id = ?', [(int)$body['id']]);
    json_response(['ok' => true]);
}

json_error('Unknown action.', 400);
