<?php
/**
 * iTzAYDiN-BOT Rebecca adapter.
 *
 * Rebecca integrations should use personal rk_ API keys through
 * Authorization: Bearer <api_key>. Legacy username/password JWT login is kept
 * as an optional compatibility path for older installations.
 */

if (!function_exists('rb_base_url')) {
    function rb_base_url(array $panel): string
    {
        return rtrim(trim((string)($panel['url_panel'] ?? '')), '/');
    }
}

if (!function_exists('rb_json_array')) {
    function rb_json_array($value, array $default = []): array
    {
        if (is_array($value)) return $value;
        $decoded = json_decode((string)$value, true);
        return is_array($decoded) ? $decoded : $default;
    }
}

if (!function_exists('rb_http')) {
    function rb_http(string $url, string $method = 'GET', array $headers = [], $payload = null, bool $form = false, int $timeout = 20): array
    {
        if (!function_exists('curl_init')) {
            return ['ok'=>false,'http_code'=>0,'curl_errno'=>-1,'curl_error'=>'PHP cURL extension is not installed.','raw'=>'','json'=>null,'headers'=>''];
        }
        $responseHeaders = '';
        $ch = curl_init($url);
        $headers[] = 'Accept: application/json';
        $options = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST => strtoupper($method),
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_CONNECTTIMEOUT => min(8, $timeout),
            CURLOPT_TIMEOUT => $timeout,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS => 3,
            CURLOPT_USERAGENT => 'iTzAYDiN-BOT/6 RebeccaAdapter',
            CURLOPT_ENCODING => '',
            CURLOPT_HEADERFUNCTION => static function ($ch, $line) use (&$responseHeaders) {
                $responseHeaders .= $line;
                return strlen($line);
            },
        ];
        if ($payload !== null) {
            if ($form) {
                $options[CURLOPT_POSTFIELDS] = http_build_query($payload, '', '&', PHP_QUERY_RFC3986);
                $options[CURLOPT_HTTPHEADER][] = 'Content-Type: application/x-www-form-urlencoded';
            } else {
                $options[CURLOPT_POSTFIELDS] = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                $options[CURLOPT_HTTPHEADER][] = 'Content-Type: application/json';
            }
        }
        curl_setopt_array($ch, $options);
        $raw = curl_exec($ch);
        $errno = curl_errno($ch);
        $error = curl_error($ch);
        $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        $rawString = $raw === false ? '' : (string)$raw;
        $decoded = $rawString !== '' ? json_decode($rawString, true) : null;
        return [
            'ok' => $errno === 0 && $status >= 200 && $status < 300,
            'http_code' => $status,
            'curl_errno' => $errno,
            'curl_error' => $error,
            'raw' => $rawString,
            'json' => is_array($decoded) ? $decoded : null,
            'headers' => $responseHeaders,
        ];
    }
}

if (!function_exists('rb_error_message')) {
    function rb_error_message(array $response): string
    {
        if (!empty($response['curl_error'])) return (string)$response['curl_error'];
        $json = $response['json'] ?? null;
        if (is_array($json)) {
            foreach (['detail','message','error','msg'] as $key) {
                if (!empty($json[$key])) {
                    return is_scalar($json[$key]) ? (string)$json[$key] : json_encode($json[$key], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
                }
            }
        }
        $raw = trim(strip_tags((string)($response['raw'] ?? '')));
        if ($raw !== '') return mb_substr($raw, 0, 700, 'UTF-8');
        return 'Rebecca API returned HTTP ' . (int)($response['http_code'] ?? 0);
    }
}

if (!function_exists('rb_login')) {
    function rb_login(array $panel, bool $force = false): array
    {
        $mode = strtolower(trim((string)($panel['rb_auth_mode'] ?? 'api_key')));
        $token = trim((string)($panel['api_key'] ?? ''));
        if ($token === '') $token = trim((string)($panel['rb_access_token'] ?? ''));
        if (in_array($mode, ['api_key','apikey','bearer','token'], true) && $token !== '') {
            return ['ok'=>true,'token'=>$token,'source'=>$mode];
        }

        static $cache = [];
        $key = sha1(($panel['code_panel'] ?? $panel['name_panel'] ?? '') . '|' . ($panel['username_panel'] ?? ''));
        if (!$force && isset($cache[$key]) && ($cache[$key]['until'] ?? 0) > time()) {
            return ['ok'=>true,'token'=>$cache[$key]['token'],'source'=>'memory'];
        }
        $username = trim((string)($panel['username_panel'] ?? ''));
        $password = (string)($panel['password_panel'] ?? '');
        if ($username === '' || $password === '') return ['ok'=>false,'error'=>'API Key یا اطلاعات ورود Rebecca تنظیم نشده است.'];

        // Rebecca keeps this only for older Marzban-compatible integrations.
        $r = rb_http(rb_base_url($panel) . '/api/admin/token', 'POST', [], ['username'=>$username,'password'=>$password], true);
        $jwt = trim((string)($r['json']['access_token'] ?? ''));
        if (!$r['ok'] || $jwt === '') return ['ok'=>false,'error'=>rb_error_message($r),'response'=>$r];
        $cache[$key] = ['token'=>$jwt,'until'=>time()+600];
        return ['ok'=>true,'token'=>$jwt,'source'=>'password'];
    }
}

if (!function_exists('rb_request')) {
    function rb_request(array $panel, string $method, string $path, $payload = null, bool $retry = true): array
    {
        $auth = rb_login($panel, false);
        if (empty($auth['ok'])) return ['ok'=>false,'http_code'=>0,'curl_errno'=>0,'curl_error'=>$auth['error'] ?? 'Rebecca authentication failed','raw'=>'','json'=>null];
        $r = rb_http(rb_base_url($panel) . '/' . ltrim($path, '/'), $method, ['Authorization: Bearer ' . $auth['token']], $payload, false);
        if ($r['http_code'] === 401 && $retry && in_array(($auth['source'] ?? ''), ['password','memory'], true)) {
            $fresh = rb_login($panel, true);
            if (!empty($fresh['ok'])) return rb_http(rb_base_url($panel) . '/' . ltrim($path, '/'), $method, ['Authorization: Bearer ' . $fresh['token']], $payload, false);
        }
        return $r;
    }
}

if (!function_exists('rb_test_connection')) {
    function rb_test_connection(array $panel): array
    {
        $r = rb_request($panel, 'GET', '/api/admin');
        return $r['ok']
            ? ['ok'=>true,'message'=>'اتصال Rebecca برقرار است.','admin'=>$r['json']]
            : ['ok'=>false,'message'=>rb_error_message($r),'http_code'=>$r['http_code'] ?? 0];
    }
}

if (!function_exists('rb_extract_list')) {
    function rb_extract_list($json, array $keys = []): array
    {
        if (!is_array($json)) return [];
        foreach ($keys as $key) if (isset($json[$key]) && is_array($json[$key])) return array_values($json[$key]);
        foreach (['items','results','data'] as $key) {
            if (isset($json[$key]) && is_array($json[$key])) {
                foreach ($keys as $nested) if (isset($json[$key][$nested]) && is_array($json[$key][$nested])) return array_values($json[$key][$nested]);
                if (array_is_list($json[$key])) return array_values($json[$key]);
            }
        }
        return array_is_list($json) ? array_values($json) : [];
    }
}

if (!function_exists('rb_list_services')) {
    function rb_list_services(array $panel): array
    {
        $r = rb_request($panel, 'GET', '/api/v2/services');
        if (!$r['ok']) return ['ok'=>false,'items'=>[],'error'=>rb_error_message($r),'response'=>$r];
        return ['ok'=>true,'items'=>rb_extract_list($r['json'], ['services']),'raw'=>$r['json']];
    }
}

if (!function_exists('rb_list_users')) {
    function rb_list_users(array $panel, int $limit = 50, int $offset = 0, string $search = ''): array
    {
        $query = http_build_query(['limit'=>max(1,min(500,$limit)),'offset'=>max(0,$offset)] + ($search !== '' ? ['search'=>$search] : []));
        $r = rb_request($panel, 'GET', '/api/users?' . $query);
        if (!$r['ok']) return ['ok'=>false,'items'=>[],'error'=>rb_error_message($r),'response'=>$r];
        $items = rb_extract_list($r['json'], ['users']);
        return ['ok'=>true,'items'=>$items,'total'=>(int)($r['json']['total'] ?? count($items)),'raw'=>$r['json']];
    }
}

if (!function_exists('rb_list_nodes')) {
    function rb_list_nodes(array $panel): array
    {
        $r = rb_request($panel, 'GET', '/api/nodes');
        return $r['ok'] ? ['ok'=>true,'items'=>rb_extract_list($r['json'], ['nodes']),'raw'=>$r['json']] : ['ok'=>false,'items'=>[],'error'=>rb_error_message($r),'response'=>$r];
    }
}

if (!function_exists('rb_list_hosts')) {
    function rb_list_hosts(array $panel): array
    {
        $r = rb_request($panel, 'GET', '/api/hosts');
        return $r['ok'] ? ['ok'=>true,'items'=>rb_extract_list($r['json'], ['hosts']),'raw'=>$r['json']] : ['ok'=>false,'items'=>[],'error'=>rb_error_message($r),'response'=>$r];
    }
}

if (!function_exists('rb_list_inbounds')) {
    function rb_list_inbounds(array $panel, bool $full = true): array
    {
        $r = rb_request($panel, 'GET', $full ? '/api/inbounds/full' : '/api/inbounds');
        return $r['ok'] ? ['ok'=>true,'items'=>rb_extract_list($r['json'], ['inbounds']),'raw'=>$r['json']] : ['ok'=>false,'items'=>[],'error'=>rb_error_message($r),'response'=>$r];
    }
}

if (!function_exists('rb_get_system')) {
    function rb_get_system(array $panel): array { return rb_request($panel, 'GET', '/api/system'); }
}
if (!function_exists('rb_get_system_metrics')) {
    function rb_get_system_metrics(array $panel): array { return rb_request($panel, 'GET', '/api/system'); }
}
if (!function_exists('rb_get_maintenance')) {
    function rb_get_maintenance(array $panel): array { return rb_request($panel, 'GET', '/api/maintenance/status'); }
}
if (!function_exists('rb_get_maintenance_info')) {
    function rb_get_maintenance_info(array $panel): array { return rb_request($panel, 'GET', '/api/maintenance/info'); }
}
if (!function_exists('rb_get_node_settings')) {
    function rb_get_node_settings(array $panel): array { return rb_request($panel, 'GET', '/api/node/settings'); }
}
if (!function_exists('rb_get_user_usage')) {
    function rb_get_user_usage(array $panel, string $username, ?int $start = null, ?int $end = null): array
    {
        $q=[]; if($start!==null)$q['start']=$start; if($end!==null)$q['end']=$end;
        $path='/api/user/'.rawurlencode($username).'/usage'.($q?'?'.http_build_query($q):'');
        return rb_request($panel,'GET',$path);
    }
}
if (!function_exists('rb_get_users_usage')) {
    function rb_get_users_usage(array $panel, ?int $start = null, ?int $end = null): array
    {
        $q=[]; if($start!==null)$q['start']=$start; if($end!==null)$q['end']=$end;
        return rb_request($panel,'GET','/api/users/usage'.($q?'?'.http_build_query($q):''));
    }
}
if (!function_exists('rb_get_nodes_usage')) {
    function rb_get_nodes_usage(array $panel, ?int $start = null, ?int $end = null): array
    {
        $q=[]; if($start!==null)$q['start']=$start; if($end!==null)$q['end']=$end;
        return rb_request($panel,'GET','/api/nodes/usage'.($q?'?'.http_build_query($q):''));
    }
}
if (!function_exists('rb_activate_next_plan')) {
    function rb_activate_next_plan(array $panel, string $username): array
    {
        $r=rb_request($panel,'POST','/api/user/'.rawurlencode($username).'/active-next',[]);
        return $r['ok']?['ok'=>true,'user'=>$r['json'],'response'=>$r]:['ok'=>false,'error'=>rb_error_message($r),'response'=>$r];
    }
}
if (!function_exists('rb_list_service_users')) {
    function rb_list_service_users(array $panel, int $serviceId, int $limit = 50, int $offset = 0): array
    {
        if($serviceId<=0)return ['ok'=>false,'items'=>[],'error'=>'Service ID معتبر نیست.'];
        $q=http_build_query(['limit'=>max(1,min(500,$limit)),'offset'=>max(0,$offset)]);
        $r=rb_request($panel,'GET','/api/v2/services/'.$serviceId.'/users?'.$q);
        if(!$r['ok'])return ['ok'=>false,'items'=>[],'error'=>rb_error_message($r),'response'=>$r];
        $items=rb_extract_list($r['json'],['users']);
        return ['ok'=>true,'items'=>$items,'total'=>(int)($r['json']['total']??count($items)),'raw'=>$r['json']];
    }
}
if (!function_exists('rb_reset_service_usage')) {
    function rb_reset_service_usage(array $panel, int $serviceId): array
    {
        if($serviceId<=0)return ['ok'=>false,'error'=>'Service ID معتبر نیست.'];
        $r=rb_request($panel,'POST','/api/v2/services/'.$serviceId.'/reset-usage',[]);
        return $r['ok']?['ok'=>true,'result'=>$r['json'],'response'=>$r]:['ok'=>false,'error'=>rb_error_message($r),'response'=>$r];
    }
}
if (!function_exists('rb_service_bulk_user_action')) {
    function rb_service_bulk_user_action(array $panel, int $serviceId, string $action, array $options = []): array
    {
        if($serviceId<=0)return ['ok'=>false,'error'=>'Service ID معتبر نیست.'];
        $payload=['action'=>$action]+$options;
        $r=rb_request($panel,'POST','/api/v2/services/'.$serviceId.'/users/actions',$payload);
        return $r['ok']?['ok'=>true,'result'=>$r['json'],'response'=>$r]:['ok'=>false,'error'=>rb_error_message($r),'response'=>$r];
    }
}

if (!function_exists('rb_get_openapi')) {
    function rb_get_openapi(array $panel): array
    {
        // Public by design in Rebecca; do not attach secrets unless a reverse proxy unexpectedly protects it.
        $r = rb_http(rb_base_url($panel) . '/openapi.json', 'GET', [], null, false, 15);
        if (!$r['ok']) return ['ok'=>false,'spec'=>null,'error'=>rb_error_message($r),'response'=>$r];
        return ['ok'=>true,'spec'=>$r['json']];
    }
}

if (!function_exists('rb_openapi_operations')) {
    function rb_openapi_operations(array $spec): array
    {
        $out = [];
        foreach (($spec['paths'] ?? []) as $path => $methods) {
            if (!is_array($methods)) continue;
            foreach ($methods as $method => $meta) {
                $method = strtoupper((string)$method);
                if (!in_array($method, ['GET','POST','PUT','PATCH','DELETE'], true) || !is_array($meta)) continue;
                $out[] = [
                    'method'=>$method,
                    'path'=>(string)$path,
                    'summary'=>(string)($meta['summary'] ?? ''),
                    'operationId'=>(string)($meta['operationId'] ?? ''),
                    'tags'=>array_values((array)($meta['tags'] ?? [])),
                ];
            }
        }
        return $out;
    }
}



if (!function_exists('rb_openapi_match')) {
    /**
     * Match an actual Rebecca API path against its live OpenAPI definition.
     * Query strings are ignored and {path_parameters} are treated as a single
     * URL segment. This keeps the generic explorer limited to operations the
     * connected Rebecca build explicitly advertises.
     */
    function rb_openapi_match(array $spec, string $method, string $actualPath): ?array
    {
        $method = strtoupper(trim($method));
        if (!in_array($method, ['GET','POST','PUT','PATCH','DELETE'], true)) return null;
        $pathOnly = parse_url($actualPath, PHP_URL_PATH);
        if (!is_string($pathOnly) || $pathOnly === '' || strpos($pathOnly, '/api/') !== 0) return null;
        $actualSeg = array_values(array_filter(explode('/', trim($pathOnly, '/')), static fn($v) => $v !== ''));
        foreach (($spec['paths'] ?? []) as $template => $methods) {
            if (!is_array($methods) || !isset($methods[strtolower($method)]) || !is_array($methods[strtolower($method)])) continue;
            $tplSeg = array_values(array_filter(explode('/', trim((string)$template, '/')), static fn($v) => $v !== ''));
            if (count($tplSeg) !== count($actualSeg)) continue;
            $ok = true;
            foreach ($tplSeg as $i => $seg) {
                if (preg_match('/^\{[^{}]+\}$/', $seg)) continue;
                if (rawurldecode($seg) !== rawurldecode((string)$actualSeg[$i])) { $ok = false; break; }
            }
            if (!$ok) continue;
            $meta = $methods[strtolower($method)];
            return [
                'method'=>$method,
                'path'=>(string)$template,
                'summary'=>(string)($meta['summary'] ?? ''),
                'operationId'=>(string)($meta['operationId'] ?? ''),
                'tags'=>array_values((array)($meta['tags'] ?? [])),
            ];
        }
        return null;
    }
}

if (!function_exists('rb_service_summary')) {
    function rb_service_summary(array $service): array
    {
        return [
            'id'=>(int)($service['id'] ?? 0),
            'name'=>(string)($service['name'] ?? $service['title'] ?? ('Service #' . (int)($service['id'] ?? 0))),
            'enabled'=>!array_key_exists('enabled', $service) || (bool)$service['enabled'],
            'raw'=>$service,
        ];
    }
}

if (!function_exists('rb_get_user')) {
    function rb_get_user(array $panel, string $username): array
    {
        $r = rb_request($panel, 'GET', '/api/user/' . rawurlencode($username));
        return $r['ok'] ? ['ok'=>true,'user'=>$r['json'],'response'=>$r] : ['ok'=>false,'error'=>rb_error_message($r),'response'=>$r];
    }
}

if (!function_exists('rb_resolve_service_id')) {
    function rb_resolve_service_id(array $panel, array $product = []): int
    {
        $prov = rb_json_array($product['provisioning_json'] ?? '');
        $fromProduct = (int)($prov['rb_service_id'] ?? 0);
        if ($fromProduct > 0) return $fromProduct;
        return (int)($panel['rb_default_service_id'] ?? 0);
    }
}

if (!function_exists('rb_create_user')) {
    function rb_create_user(array $panel, string $username, int $dataLimitBytes, int $expireUnix, string $note = '', array $product = []): array
    {
        $serviceId = rb_resolve_service_id($panel, $product);
        if ($serviceId <= 0) return ['ok'=>false,'error'=>'برای پنل Rebecca یک Service معتبر انتخاب نشده است. از Rebecca Hub سرویس پیش‌فرض را Sync و انتخاب کن.'];
        $payload = [
            'username'=>$username,
            'service_id'=>$serviceId,
            'status'=>'active',
            'data_limit'=>$dataLimitBytes > 0 ? $dataLimitBytes : null,
            'expire'=>$expireUnix > 0 ? $expireUnix : null,
            'note'=>$note !== '' ? $note : null,
        ];
        $prov = rb_json_array($product['provisioning_json'] ?? '');
        if (!empty($prov['rb_reset_strategy'])) $payload['data_limit_reset_strategy'] = (string)$prov['rb_reset_strategy'];
        if (isset($prov['rb_ip_limit']) && (int)$prov['rb_ip_limit'] >= 0) $payload['ip_limit'] = (int)$prov['rb_ip_limit'];
        if (isset($prov['rb_auto_delete_days']) && (int)$prov['rb_auto_delete_days'] >= 0) $payload['auto_delete_in_days'] = (int)$prov['rb_auto_delete_days'];
        if (!empty($prov['rb_flow'])) $payload['flow'] = (string)$prov['rb_flow'];
        if (isset($prov['rb_on_hold_timeout']) && (int)$prov['rb_on_hold_timeout'] > 0) $payload['on_hold_timeout'] = (int)$prov['rb_on_hold_timeout'];
        if (isset($prov['rb_on_hold_expire_duration']) && (int)$prov['rb_on_hold_expire_duration'] > 0) $payload['on_hold_expire_duration'] = (int)$prov['rb_on_hold_expire_duration'];
        if (!empty($prov['rb_next_plans'])) {
            $nextPlans = is_array($prov['rb_next_plans']) ? $prov['rb_next_plans'] : rb_json_array($prov['rb_next_plans']);
            if ($nextPlans) $payload['next_plans'] = $nextPlans;
        }
        $payload = array_filter($payload, static fn($v) => $v !== null);
        $r = rb_request($panel, 'POST', '/api/v2/users', $payload);
        if (!$r['ok']) return ['ok'=>false,'error'=>rb_error_message($r),'response'=>$r];
        $user = is_array($r['json']) ? $r['json'] : [];
        // Some builds wrap the object.
        if (isset($user['user']) && is_array($user['user'])) $user = $user['user'];
        return ['ok'=>true,'user'=>$user,'response'=>$r];
    }
}

if (!function_exists('rb_modify_user')) {
    function rb_modify_user(array $panel, string $username, array $patch): array
    {
        $allowed = ['status','service_id','expire','data_limit','data_limit_reset_strategy','note','telegram_id','contact_number','on_hold_expire_duration','on_hold_timeout','ip_limit','auto_delete_in_days','next_plans','flow','credential_key'];
        $payload = [];
        foreach ($allowed as $key) if (array_key_exists($key, $patch)) $payload[$key] = $patch[$key];
        if (!$payload) return ['ok'=>true,'user'=>null,'message'=>'No changes'];
        $r = rb_request($panel, 'PUT', '/api/v2/users/' . rawurlencode($username), $payload);
        return $r['ok'] ? ['ok'=>true,'user'=>$r['json'],'response'=>$r] : ['ok'=>false,'error'=>rb_error_message($r),'response'=>$r];
    }
}

if (!function_exists('rb_delete_user')) {
    function rb_delete_user(array $panel, string $username): array
    {
        $r = rb_request($panel, 'DELETE', '/api/user/' . rawurlencode($username));
        return $r['ok'] ? ['ok'=>true,'response'=>$r] : ['ok'=>false,'error'=>rb_error_message($r),'response'=>$r];
    }
}

if (!function_exists('rb_reset_user')) {
    function rb_reset_user(array $panel, string $username): array
    {
        $r = rb_request($panel, 'POST', '/api/user/' . rawurlencode($username) . '/reset', []);
        return $r['ok'] ? ['ok'=>true,'response'=>$r] : ['ok'=>false,'error'=>rb_error_message($r),'response'=>$r];
    }
}

if (!function_exists('rb_revoke_subscription')) {
    function rb_revoke_subscription(array $panel, string $username): array
    {
        $r = rb_request($panel, 'POST', '/api/user/' . rawurlencode($username) . '/revoke_sub', []);
        return $r['ok'] ? ['ok'=>true,'user'=>$r['json'],'response'=>$r] : ['ok'=>false,'error'=>rb_error_message($r),'response'=>$r];
    }
}

if (!function_exists('rb_bulk_user_action')) {
    function rb_bulk_user_action(array $panel, string $action, array $options = []): array
    {
        $allowed = ['extend_expire','reduce_expire','increase_traffic','decrease_traffic','cleanup_status','activate_users','disable_users','change_service'];
        if (!in_array($action, $allowed, true)) return ['ok'=>false,'error'=>'Rebecca bulk action نامعتبر است.'];
        $payload = ['action'=>$action] + $options;
        $r = rb_request($panel, 'POST', '/api/users/actions', $payload);
        return $r['ok'] ? ['ok'=>true,'result'=>$r['json'],'response'=>$r] : ['ok'=>false,'error'=>rb_error_message($r),'response'=>$r];
    }
}

if (!function_exists('rb_subscription_url')) {
    function rb_subscription_url(array $user): string
    {
        foreach (['subscription_url','key_subscription_url','subadress'] as $key) {
            $v = trim((string)($user[$key] ?? ''));
            if ($v !== '') return $v;
        }
        $urls = $user['subscription_urls'] ?? null;
        if (is_array($urls)) {
            foreach ($urls as $v) if (is_string($v) && trim($v) !== '') return trim($v);
        }
        return '';
    }
}

if (!function_exists('rb_normalize_user_for_bot')) {
    function rb_normalize_user_for_bot(array $user): array
    {
        return [
            'username'=>(string)($user['username'] ?? ''),
            'status'=>(string)($user['status'] ?? ''),
            'used_traffic'=>(int)($user['used_traffic'] ?? 0),
            'lifetime_used_traffic'=>(int)($user['lifetime_used_traffic'] ?? 0),
            'data_limit'=>isset($user['data_limit']) ? (int)$user['data_limit'] : 0,
            'expire'=>isset($user['expire']) ? (int)$user['expire'] : 0,
            'service_id'=>isset($user['service_id']) ? (int)$user['service_id'] : 0,
            'service_name'=>(string)($user['service_name'] ?? ''),
            'subscription_url'=>rb_subscription_url($user),
            'links'=>array_values(array_filter((array)($user['links'] ?? []), 'is_string')),
            'online_at'=>$user['online_at'] ?? null,
            'ip_limit'=>(int)($user['ip_limit'] ?? 0),
            'note'=>$user['note'] ?? null,
        ];
    }
}
