<?php

if (!defined('ABSPATH')) {
    exit;
}

class BS2FA_Helpers {
    public static function get_option($key = null, $default = null) {
        $options = get_option('bs2fa_options', []);

        if (null === $key) {
            return is_array($options) ? $options : [];
        }

        return isset($options[$key]) ? $options[$key] : $default;
    }

    public static function update_options($new_options) {
        $defaults = self::defaults();
        $merged   = wp_parse_args($new_options, $defaults);
        update_option('bs2fa_options', $merged, false);
    }

    public static function defaults() {
        return [
            'enabled'                           => 1,
            'allowed_methods'                   => ['totp', 'webauthn'],
            'required_roles'                    => ['administrator'],
            'force_enrollment'                  => 1,
            'enrollment_grace_hours'            => 0,
            'passkey_user_verification'         => 'required',
            'hide_for_xmlrpc'                   => 1,
            'alert_new_enrollment'              => 1,
            'alert_admin_on_enrollment'         => 0,
            'rate_limit_enabled'                => 1,
            'primary_max_attempts'              => 5,
            'primary_lockout_minutes'           => 15,
            'challenge_max_attempts'            => 5,
            'challenge_lockout_minutes'         => 10,
            'log_retention_items'               => 300,
            'trusted_devices_enabled'           => 1,
            'trusted_device_days'               => 30,
            'alert_admin_on_lockout'            => 1,
            'block_application_passwords'       => 1,
            'revoke_other_sessions_on_change'   => 1,
            'rest_api_policy_enabled'           => 1,
            'rest_api_block_basic_auth'         => 1,
            'rest_api_status_endpoint'          => 1,
            'woocommerce_account_integration'   => 1,
            'woocommerce_force_account_security'=> 1,
            'dashboard_widget_enabled'          => 1,
            'rest_api_role_mode'                 => 'required_or_enrolled',
            'rest_api_protected_roles'           => [],
            'rest_api_allowlisted_users'         => '',
            'alert_user_on_new_location'         => 1,
            'alert_admin_on_new_location'        => 0,
            'woocommerce_checkout_notice'        => 1,
            'woocommerce_block_checkout_if_required' => 0,
        ];
    }

    public static function current_origin() {
        $home = home_url('/');
        $parts = wp_parse_url($home);
        if (empty($parts['scheme']) || empty($parts['host'])) {
            return '';
        }

        $origin = $parts['scheme'] . '://' . $parts['host'];
        if (!empty($parts['port'])) {
            $origin .= ':' . (int) $parts['port'];
        }

        return $origin;
    }

    public static function rp_id() {
        $host = wp_parse_url(home_url('/'), PHP_URL_HOST);
        return $host ? strtolower($host) : '';
    }

    public static function base64url_encode($data) {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }

    public static function base64url_decode($data) {
        $remainder = strlen($data) % 4;
        if ($remainder) {
            $data .= str_repeat('=', 4 - $remainder);
        }
        return base64_decode(strtr($data, '-_', '+/'));
    }

    public static function random_token($length = 32) {
        return self::base64url_encode(random_bytes($length));
    }

    public static function get_user_roles($user) {
        if ($user instanceof WP_User) {
            return (array) $user->roles;
        }

        if (is_numeric($user)) {
            $user = get_user_by('id', (int) $user);
            return $user ? (array) $user->roles : [];
        }

        return [];
    }

    public static function user_has_required_role($user) {
        $required_roles = (array) self::get_option('required_roles', []);
        if (empty($required_roles)) {
            return false;
        }

        $user_roles = self::get_user_roles($user);
        return (bool) array_intersect($required_roles, $user_roles);
    }

    public static function allowed_methods() {
        $methods = (array) self::get_option('allowed_methods', ['totp', 'webauthn']);
        $methods = array_values(array_intersect($methods, ['totp', 'webauthn']));
        return empty($methods) ? ['totp'] : $methods;
    }

    public static function is_method_allowed($method) {
        return in_array($method, self::allowed_methods(), true);
    }

    public static function secure_cookie($name, $value, $expires = 0) {
        $is_ssl = is_ssl();
        $params = [
            'expires'  => $expires,
            'path'     => COOKIEPATH ? COOKIEPATH : '/',
            'domain'   => COOKIE_DOMAIN,
            'secure'   => $is_ssl,
            'httponly' => true,
            'samesite' => 'Lax',
        ];

        setcookie($name, $value, $params);
        $_COOKIE[$name] = $value;
    }

    public static function clear_cookie($name) {
        self::secure_cookie($name, '', time() - 3600);
        unset($_COOKIE[$name]);
    }

    public static function set_backup_codes_flash($codes) {
        $codes = array_values(array_filter(array_map(static function ($code) {
            $code = strtoupper(preg_replace('/[^A-Z0-9]/', '', (string) $code));
            return $code !== '' ? $code : null;
        }, (array) $codes)));

        if (empty($codes)) {
            self::clear_cookie('bs2fa_backup_codes_flash');
            return;
        }

        $payload = BS2FA_Crypto::encrypt(wp_json_encode(array_slice($codes, 0, 20)));
        self::secure_cookie('bs2fa_backup_codes_flash', $payload, time() + (5 * MINUTE_IN_SECONDS));
    }

    public static function consume_backup_codes_flash() {
        $raw = isset($_COOKIE['bs2fa_backup_codes_flash']) ? sanitize_text_field(wp_unslash($_COOKIE['bs2fa_backup_codes_flash'])) : '';
        self::clear_cookie('bs2fa_backup_codes_flash');

        if ($raw === '') {
            return [];
        }

        $raw = preg_replace('/[^A-Za-z0-9:_\-]/', '', $raw);
        $json = BS2FA_Crypto::decrypt($raw);
        $data = json_decode((string) $json, true);
        if (!is_array($data)) {
            return [];
        }

        $codes = [];
        foreach ($data as $code) {
            $code = strtoupper(preg_replace('/[^A-Z0-9]/', '', (string) $code));
            if ($code !== '') {
                $codes[] = $code;
            }
        }

        return array_slice($codes, 0, 20);
    }

    public static function json_input() {
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
        $raw = file_get_contents('php://input');
        if (!$raw) {
            return [];
        }

        $json = json_decode($raw, true);
        return is_array($json) ? $json : [];
    }

    public static function json_response($data, $status = 200) {
        status_header($status);
        nocache_headers();
        wp_send_json($data, $status);
    }

    public static function sanitize_role_list($roles) {
        $wp_roles = wp_roles();
        $valid    = array_keys($wp_roles->roles);
        $roles    = array_map('sanitize_key', (array) $roles);
        return array_values(array_intersect($roles, $valid));
    }

    public static function safe_substr($value, $start, $length = null) {
        $value = (string) $value;

        if (function_exists('mb_substr')) {
            return null === $length ? mb_substr($value, $start) : mb_substr($value, $start, $length);
        }

        return null === $length ? substr($value, $start) : substr($value, $start, $length);
    }

    public static function sanitize_redirect_target($redirect_to = '', $fallback = '') {
        $fallback = $fallback !== '' ? $fallback : admin_url();
        $redirect_to = is_string($redirect_to) ? trim(wp_unslash($redirect_to)) : '';

        if ($redirect_to === '') {
            return $fallback;
        }

        return wp_validate_redirect($redirect_to, $fallback);
    }

    public static function request_method() {
        if (empty($_SERVER['REQUEST_METHOD'])) {
            return '';
        }

        return strtoupper(sanitize_key(wp_unslash($_SERVER['REQUEST_METHOD'])));
    }

    public static function sanitize_device_label($label, $fallback = '') {
        $label = sanitize_text_field((string) $label);
        $label = preg_replace('/\s+/', ' ', trim($label));
        if ($label === '') {
            $label = (string) $fallback;
        }
        $label = trim($label);
        if ($label === '') {
            $label = __('Trusted device', 'brightery-secure-2fa');
        }
        return self::safe_substr($label, 0, 80);
    }

    public static function user_is_enrolled($user_id) {
        return self::user_has_totp($user_id) || self::user_has_passkeys($user_id);
    }

    public static function user_has_totp($user_id) {
        $secret = BS2FA_Crypto::decrypt_user_meta($user_id, 'bs2fa_totp_secret');
        return !empty($secret);
    }

    public static function user_has_passkeys($user_id) {
        $credentials = get_user_meta($user_id, 'bs2fa_webauthn_credentials', true);
        return !empty($credentials) && is_array($credentials);
    }

    public static function backup_codes_left($user_id) {
        $codes = get_user_meta($user_id, 'bs2fa_backup_codes', true);
        return is_array($codes) ? count($codes) : 0;
    }

    public static function should_force_enrollment($user_id) {
        $user = get_user_by('id', $user_id);
        if (!$user) {
            return false;
        }

        if (!self::get_option('enabled', 1) || !self::get_option('force_enrollment', 1)) {
            return false;
        }

        if (!self::user_has_required_role($user)) {
            return false;
        }

        if (self::user_is_enrolled($user_id)) {
            return false;
        }

        $grace_hours = (int) self::get_option('enrollment_grace_hours', 0);
        if ($grace_hours <= 0) {
            return true;
        }

        $started_at = (int) get_user_meta($user_id, 'bs2fa_enrollment_started_at', true);
        if (!$started_at) {
            update_user_meta($user_id, 'bs2fa_enrollment_started_at', time());
            return false;
        }

        return (time() - $started_at) >= ($grace_hours * HOUR_IN_SECONDS);
    }

    public static function pretty_method_name($method) {
        switch ($method) {
            case 'totp':
                return __('Authenticator App', 'brightery-secure-2fa');
            case 'webauthn':
                return __('Passkey / Fingerprint', 'brightery-secure-2fa');
            default:
                return ucfirst($method);
        }
    }

    public static function get_login_pending() {
        $token = isset($_COOKIE['bs2fa_pending']) ? sanitize_text_field(wp_unslash($_COOKIE['bs2fa_pending'])) : '';
        if (!$token) {
            return null;
        }

        $pending = get_transient('bs2fa_pending_' . $token);
        if (!is_array($pending) || empty($pending['user_id'])) {
            self::clear_cookie('bs2fa_pending');
            return null;
        }

        $pending['token'] = $token;
        return $pending;
    }

    public static function destroy_login_pending($token) {
        delete_transient('bs2fa_pending_' . $token);
        self::clear_cookie('bs2fa_pending');
    }

    public static function begin_pending_login($user_id, $remember, $redirect_to = '') {
        $token = self::random_token(32);
        $data  = [
            'user_id'     => (int) $user_id,
            'remember'    => (bool) $remember,
            'redirect_to' => self::sanitize_redirect_target($redirect_to),
            'created_at'  => time(),
        ];

        set_transient('bs2fa_pending_' . $token, $data, 10 * MINUTE_IN_SECONDS);
        self::secure_cookie('bs2fa_pending', $token, time() + 10 * MINUTE_IN_SECONDS);

        return $token;
    }

    public static function get_setup_url() {
        return admin_url('admin.php?page=bs2fa-setup');
    }

    public static function plugin_url($path = '') {
        return BS2FA_URL . ltrim($path, '/');
    }

    public static function client_ip() {
        $candidates = [];
        foreach (['HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR'] as $key) {
            if (!empty($_SERVER[$key])) {
                // Sanitize immediately upon extraction to satisfy PHPCS
                $value = sanitize_text_field(wp_unslash($_SERVER[$key]));

                if ($key === 'HTTP_X_FORWARDED_FOR') {
                    $parts = array_map('trim', explode(',', $value));
                    $value = $parts[0] ?? '';
                }
                $candidates[] = $value;
            }
        }

        foreach ($candidates as $candidate) {
            if (filter_var($candidate, FILTER_VALIDATE_IP)) {
                return $candidate;
            }
        }

        return '0.0.0.0';
    }

    public static function context_hash($value) {
        return hash_hmac('sha256', (string) $value, wp_salt('auth'));
    }

    public static function is_login_request() {
        if (!isset($_SERVER['SCRIPT_NAME'])) {
            return false;
        }

        if (self::request_method() !== 'POST') {
            return false;
        }

        return false !== strpos((string) sanitize_text_field(wp_unslash($_SERVER['SCRIPT_NAME'])), 'wp-login.php');
    }

    public static function user_label($user_id) {
        $user = get_user_by('id', (int) $user_id);
        if (!$user) {
            return __('Unknown user', 'brightery-secure-2fa');
        }

        return sprintf('%s (#%d)', $user->user_login, (int) $user->ID);
    }

    public static function format_time($timestamp) {
        if (!$timestamp) {
            return '—';
        }

        return date_i18n(get_option('date_format') . ' ' . get_option('time_format'), (int) $timestamp);
    }

    public static function count_required_users() {
        $roles = (array) self::get_option('required_roles', []);
        if (empty($roles)) {
            return ['required' => 0, 'enrolled' => 0, 'pending' => 0];
        }

        $users = get_users([
            'role__in' => $roles,
            'fields'   => 'ids',
            'number'   => 5000,
        ]);

        $required = is_array($users) ? count($users) : 0;
        $enrolled = 0;
        if (!empty($users)) {
            foreach ($users as $user_id) {
                if (self::user_is_enrolled((int) $user_id)) {
                    $enrolled++;
                }
            }
        }

        return [
            'required' => $required,
            'enrolled' => $enrolled,
            'pending'  => max(0, $required - $enrolled),
        ];
    }

    public static function count_recent_events($events, $seconds = DAY_IN_SECONDS) {
        $events = array_map('sanitize_key', (array) $events);
        $count = 0;
        $after = time() - max(60, (int) $seconds);
        foreach (BS2FA_Logger::all() as $log) {
            if ((int) ($log['time'] ?? 0) < $after) {
                continue;
            }
            if (in_array(sanitize_key((string) ($log['event'] ?? '')), $events, true)) {
                $count++;
            }
        }
        return $count;
    }

    public static function user_recent_logs($user_id, $limit = 8) {
        return BS2FA_Logger::get_logs(['user_id' => (int) $user_id, 'limit' => (int) $limit]);
    }

    public static function log_filters_from_request() {
        // phpcs:disable WordPress.Security.NonceVerification.Recommended
        $per_page = isset($_GET['bs2fa_per_page']) ? (int) $_GET['bs2fa_per_page'] : 20;
        $per_page = max(10, min(200, $per_page));
        $page = isset($_GET['bs2fa_log_page']) ? (int) $_GET['bs2fa_log_page'] : 1;
        $page = max(1, $page);

        return [
            'level'    => isset($_GET['bs2fa_level']) ? sanitize_key(wp_unslash($_GET['bs2fa_level'])) : '',
            'event'    => isset($_GET['bs2fa_event']) ? sanitize_key(wp_unslash($_GET['bs2fa_event'])) : '',
            'user_id'  => isset($_GET['bs2fa_user_id']) ? (int) $_GET['bs2fa_user_id'] : 0,
            'ip'       => isset($_GET['bs2fa_ip']) ? sanitize_text_field(wp_unslash($_GET['bs2fa_ip'])) : '',
            'search'   => isset($_GET['bs2fa_search']) ? sanitize_text_field(wp_unslash($_GET['bs2fa_search'])) : '',
            'limit'    => $per_page,
            'offset'   => ($page - 1) * $per_page,
            'per_page' => $per_page,
            'page'     => $page,
        ];
        // phpcs:enable WordPress.Security.NonceVerification.Recommended
    }

    public static function current_admin_url($extra = []) {
        $base = admin_url('options-general.php?page=bs2fa-settings');
        // phpcs:disable WordPress.Security.NonceVerification.Recommended
        $params = [
            'bs2fa_level'    => isset($_GET['bs2fa_level']) ? sanitize_key(wp_unslash($_GET['bs2fa_level'])) : '',
            'bs2fa_event'    => isset($_GET['bs2fa_event']) ? sanitize_key(wp_unslash($_GET['bs2fa_event'])) : '',
            'bs2fa_user_id'  => isset($_GET['bs2fa_user_id']) ? (int) $_GET['bs2fa_user_id'] : '',
            'bs2fa_ip'       => isset($_GET['bs2fa_ip']) ? sanitize_text_field(wp_unslash($_GET['bs2fa_ip'])) : '',
            'bs2fa_search'   => isset($_GET['bs2fa_search']) ? sanitize_text_field(wp_unslash($_GET['bs2fa_search'])) : '',
            'bs2fa_per_page' => isset($_GET['bs2fa_per_page']) ? max(10, min(200, (int) $_GET['bs2fa_per_page'])) : 20,
            'bs2fa_log_page' => isset($_GET['bs2fa_log_page']) ? max(1, (int) $_GET['bs2fa_log_page']) : 1,
        ];
        // phpcs:enable WordPress.Security.NonceVerification.Recommended

        foreach ($extra as $key => $value) {
            $params[$key] = $value;
        }
        return add_query_arg(array_filter($params, static function ($value) {
            return !($value === '' || $value === null || $value === false);
        }), $base);
    }

    public static function send_user_mail($user_id, $subject, $message) {
        $user = get_user_by('id', (int) $user_id);
        if (!$user || empty($user->user_email)) {
            return;
        }

        wp_mail($user->user_email, wp_specialchars_decode($subject, ENT_QUOTES), $message);
    }

    public static function notify_user($user_id, $subject, $message) {
        if (!self::get_option('alert_new_enrollment', 1)) {
            return;
        }

        self::send_user_mail($user_id, $subject, $message);
    }

    public static function send_admin_mail($subject, $message) {
        $email = get_option('admin_email');
        if ($email) {
            wp_mail($email, wp_specialchars_decode($subject, ENT_QUOTES), $message);
        }
    }

    public static function notify_admin($subject, $message) {
        if (!self::get_option('alert_admin_on_enrollment', 0)) {
            return;
        }
        self::send_admin_mail($subject, $message);
    }



    public static function sanitize_identifier_list($value) {
        $parts = preg_split('/[\r\n,;]+/', sanitize_textarea_field((string) $value));
        $clean = [];
        foreach ((array) $parts as $part) {
            $part = sanitize_text_field(trim((string) $part));
            if ($part === '') {
                continue;
            }
            $clean[$part] = true;
        }

        return implode("\n", array_slice(array_keys($clean), 0, 200));
    }

    public static function parse_identifier_list($value) {
        $parts = preg_split('/[\r\n,;]+/', (string) $value);
        $clean = [];
        foreach ((array) $parts as $part) {
            $part = sanitize_text_field(trim((string) $part));
            if ($part === '') {
                continue;
            }
            $clean[$part] = true;
        }

        return array_slice(array_keys($clean), 0, 200);
    }

    public static function resolve_user_ids_from_identifiers($value) {
        $ids = [];
        foreach (self::parse_identifier_list($value) as $identifier) {
            $user = null;
            if (ctype_digit($identifier)) {
                $user = get_user_by('id', (int) $identifier);
            } elseif (strpos($identifier, '@') !== false) {
                $user = get_user_by('email', $identifier);
            } else {
                $user = get_user_by('login', $identifier);
            }

            if ($user instanceof WP_User) {
                $ids[(int) $user->ID] = (int) $user->ID;
            }
        }

        return array_values($ids);
    }

    public static function rest_policy_roles() {
        $roles = self::sanitize_role_list((array) self::get_option('rest_api_protected_roles', []));
        if (empty($roles)) {
            $roles = self::sanitize_role_list((array) self::get_option('required_roles', []));
        }
        return $roles;
    }

    public static function user_matches_roles($user, $roles) {
        return (bool) array_intersect(self::get_user_roles($user), self::sanitize_role_list((array) $roles));
    }

    public static function user_is_rest_allowlisted($user) {
        if (!($user instanceof WP_User)) {
            if (!is_numeric($user)) {
                return false;
            }
            $user = get_user_by('id', (int) $user);
        }

        if (!$user) {
            return false;
        }

        $ids = self::resolve_user_ids_from_identifiers(self::get_option('rest_api_allowlisted_users', ''));
        return in_array((int) $user->ID, $ids, true);
    }

    public static function client_country() {
        foreach (['HTTP_CF_IPCOUNTRY', 'GEOIP_COUNTRY_CODE', 'HTTP_X_COUNTRY_CODE'] as $key) {
            if (empty($_SERVER[$key])) {
                continue;
            }
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
            $country = strtoupper(sanitize_text_field(wp_unslash($_SERVER[$key])));
            if (preg_match('/^[A-Z]{2}$/', $country)) {
                return $country;
            }
        }
        return '';
    }

    public static function masked_ip($ip = '') {
        $ip = $ip ?: self::client_ip();
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            $parts = explode('.', $ip);
            if (count($parts) === 4) {
                $parts[3] = 'x';
                return implode('.', $parts);
            }
        }

        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            $parts = explode(':', $ip);
            return implode(':', array_slice($parts, 0, 4)) . ':*';
        }

        return $ip;
    }

    public static function network_bucket($ip = '') {
        $ip = $ip ?: self::client_ip();
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            $parts = explode('.', $ip);
            if (count($parts) === 4) {
                return $parts[0] . '.' . $parts[1] . '.' . $parts[2] . '.0/24';
            }
        }

        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            $parts = array_values(array_filter(explode(':', strtolower($ip)), static function ($part) {
                return $part !== '';
            }));
            if (!empty($parts)) {
                return implode(':', array_slice($parts, 0, min(4, count($parts)))) . '::/64';
            }
        }

        return $ip;
    }

    public static function user_agent_label() {
        if (empty($_SERVER['HTTP_USER_AGENT'])) {
            return '';
        }

        return self::safe_substr(sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])), 0, 180);
    }

    public static function current_login_context() {
        $ip = self::client_ip();
        $country = self::client_country();
        $network = self::network_bucket($ip);
        $user_agent = self::user_agent_label();

        return [
            'time' => time(),
            'ip' => $ip,
            'masked_ip' => self::masked_ip($ip),
            'country' => $country,
            'network' => $network,
            'user_agent' => $user_agent,
            'hash' => self::context_hash(strtolower($network . '|' . $country . '|' . $user_agent)),
        ];
    }

    public static function get_login_contexts($user_id) {
        $items = get_user_meta((int) $user_id, 'bs2fa_login_contexts', true);
        return is_array($items) ? $items : [];
    }

    public static function has_login_context($user_id, $hash) {
        foreach (self::get_login_contexts($user_id) as $item) {
            if (!empty($item['hash']) && hash_equals((string) $item['hash'], (string) $hash)) {
                return true;
            }
        }
        return false;
    }

    public static function remember_login_context($user_id, $context, $limit = 10) {
        if (empty($context['hash'])) {
            return;
        }

        $items = [];
        foreach (self::get_login_contexts($user_id) as $item) {
            if (empty($item['hash']) || $item['hash'] === $context['hash']) {
                continue;
            }
            $items[] = $item;
        }

        array_unshift($items, [
            'time' => (int) ($context['time'] ?? time()),
            'hash' => (string) $context['hash'],
            'masked_ip' => (string) ($context['masked_ip'] ?? ''),
            'country' => (string) ($context['country'] ?? ''),
            'network' => (string) ($context['network'] ?? ''),
            'user_agent' => (string) ($context['user_agent'] ?? ''),
        ]);

        update_user_meta((int) $user_id, 'bs2fa_login_contexts', array_slice($items, 0, max(1, (int) $limit)));
    }

    public static function login_context_summary($context) {
        $parts = [];
        if (!empty($context['country'])) {
            $parts[] = (string) $context['country'];
        }
        if (!empty($context['network'])) {
            $parts[] = (string) $context['network'];
        }
        if (!empty($context['masked_ip'])) {
            $parts[] = (string) $context['masked_ip'];
        }
        return implode(' • ', array_unique($parts));
    }


    public static function is_woocommerce_active() {
        return class_exists('WooCommerce');
    }

    public static function request_has_authorization_header() {
        foreach (['HTTP_AUTHORIZATION', 'REDIRECT_HTTP_AUTHORIZATION', 'Authorization'] as $key) {
            if (!empty($_SERVER[$key])) {
                return true;
            }
        }
        return false;
    }

    public static function request_has_rest_nonce() {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        return !empty($_SERVER['HTTP_X_WP_NONCE']) || !empty($_REQUEST['_wpnonce']);
    }


    public static function user_needs_api_policy($user_id) {
        $user = get_user_by('id', (int) $user_id);
        if (!$user) {
            return false;
        }

        if (self::user_is_rest_allowlisted($user)) {
            return false;
        }

        $mode = (string) self::get_option('rest_api_role_mode', 'required_or_enrolled');
        if ($mode === 'custom_roles') {
            return self::user_matches_roles($user, self::rest_policy_roles());
        }

        if ($mode === 'custom_roles_or_enrolled') {
            return self::user_matches_roles($user, self::rest_policy_roles()) || self::user_is_enrolled($user_id);
        }

        return self::user_has_required_role($user) || self::user_is_enrolled($user_id);
    }

    public static function wc_security_url() {
        if (self::is_woocommerce_active() && function_exists('wc_get_account_endpoint_url')) {
            return wc_get_account_endpoint_url('bs2fa_security');
        }

        return self::get_setup_url();
    }

    public static function is_wc_security_endpoint() {
        return self::is_woocommerce_active() && function_exists('is_wc_endpoint_url') && is_wc_endpoint_url('bs2fa_security');
    }

    public static function maybe_invalidate_other_sessions($user_id, $reason = '') {
        if (!self::get_option('revoke_other_sessions_on_change', 1) || !class_exists('WP_Session_Tokens')) {
            return;
        }

        $manager = WP_Session_Tokens::get_instance((int) $user_id);
        if (!$manager) {
            return;
        }

        if (is_user_logged_in() && (int) get_current_user_id() === (int) $user_id && function_exists('wp_get_session_token')) {
            $token = wp_get_session_token();
            if ($token && method_exists($manager, 'destroy_others')) {
                $manager->destroy_others($token);
            } elseif (method_exists($manager, 'destroy_all')) {
                $manager->destroy_all();
            }
        } elseif (method_exists($manager, 'destroy_all')) {
            $manager->destroy_all();
        }

        BS2FA_Logger::add('sessions_revoked_after_security_change', [
            'user_id' => (int) $user_id,
            'details' => $reason ? sanitize_text_field($reason) : __('Other sessions revoked after security change.', 'brightery-secure-2fa'),
        ], 'warning');
    }
}