<?php
// sync_services.php
// کران: همگام‌سازی اطلاعات سرویس‌ها با سرور (مصرف، انقضا، استاتوس، لینک اشتراک + configs)
// نسخه چندپنلی (Marzban + X-UI)

declare(strict_types=1);
ini_set('display_errors', '0');
error_reporting(E_ALL);
date_default_timezone_set('UTC');

require __DIR__ . '/db.php';
require __DIR__ . '/marzban.php';

if (!isset($pdo) || !($pdo instanceof PDO)) {
    error_log('[SYNC] db.php did not provide a valid $pdo');
    exit;
}
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

/* ======================================================================
 *  لود پنل‌ها (vpn_panels)
 * ====================================================================== */

try {
    $stmtPanels = $pdo->query("SELECT * FROM vpn_panels WHERE is_active = 1");
    $panels = $stmtPanels->fetchAll(PDO::FETCH_ASSOC) ?: [];
} catch (Throwable $e) {
    error_log('[SYNC] cannot load vpn_panels: ' . $e->getMessage());
    exit;
}

if (!$panels) {
    error_log('[SYNC] no active panels found; nothing to do');
    exit;
}

$marzbanPanels = [];   // panel_id => row
$xuiPanels     = [];   // panel_id => row

foreach ($panels as $p) {
    $t = strtolower(trim($p['type'] ?? ''));
    if ($t === 'marzban') {
        $marzbanPanels[(int)$p['id']] = $p;
    } elseif ($t === 'xui') {
        $xuiPanels[(int)$p['id']] = $p;
    }
}

/* ======================================================================
 *  کلاینت‌های Marzban (برای هر پنل Marzban)
 * ====================================================================== */

$marzbanClients = []; // panel_id => MarzbanService

foreach ($marzbanPanels as $pid => $pRow) {
    try {
        $marzbanClients[$pid] = new MarzbanService($pdo, $pRow);
    } catch (Throwable $e) {
        error_log("[SYNC] cannot init MarzbanService for panel_id={$pid}: " . $e->getMessage());
    }
}

/* ======================================================================
 *  Helperهای X-UI
 * ====================================================================== */

function xui_build_url(string $baseUrl, string $path): string
{
    $base = rtrim($baseUrl, '/');
    $p    = '/' . ltrim($path, '/');
    return $base . $p;
}

function xui_http_request(
    string $url,
    string $method = 'GET',
    ?array $headers = null,
    $body = null,
    ?string $cookieFile = null,
    bool $expectJson = true
): array {
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 20);

    // اغلب گواهی سلف‌ساین → برای راحتی خاموش
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);

    if ($cookieFile) {
        curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFile);
        curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieFile);
    }

    if ($headers && count($headers)) {
        $h = [];
        foreach ($headers as $k => $v) {
            $h[] = $k . ': ' . $v;
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $h);
    }

    if ($body !== null) {
        if (is_array($body)) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        } else {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        }
    }

    $responseBody = curl_exec($ch);
    $errno        = curl_errno($ch);
    $error        = curl_error($ch);
    $status       = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($errno) {
        return [
            'ok'     => false,
            'status' => 0,
            'error'  => 'cURL error: ' . $error,
            'body'   => null,
            'json'   => null,
        ];
    }

    $json = null;
    if ($expectJson && $responseBody !== false && $responseBody !== '') {
        $decoded = json_decode($responseBody, true);
        if (json_last_error() === JSON_ERROR_NONE) {
            $json = $decoded;
        }
    }

    return [
        'ok'     => ($status >= 200 && $status < 300),
        'status' => $status,
        'error'  => null,
        'body'   => $responseBody,
        'json'   => $json,
    ];
}

/**
 * لاگین به پنل X-UI (بر اساس رکورد vpn_panels)
 * panelRow شامل base_url, admin_user, admin_pass است
 */
function xui_login_panel(array $panelRow, string &$cookieFile): bool
{
    $cookieFile = tempnam(sys_get_temp_dir(), 'xui_sync_');

    $baseUrl = trim((string)($panelRow['base_url'] ?? ''));
    $username = (string)($panelRow['admin_user'] ?? '');
    $password = (string)($panelRow['admin_pass'] ?? '');

    if ($baseUrl === '' || $username === '' || $password === '') {
        error_log('[SYNC][XUI] incomplete panel config for panel_id=' . ($panelRow['id'] ?? ''));
        return false;
    }

    $url  = xui_build_url($baseUrl, '/login/');
    $body = json_encode([
        'username' => $username,
        'password' => $password,
    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

    $res = xui_http_request(
        $url,
        'POST',
        ['Content-Type' => 'application/json'],
        $body,
        $cookieFile,
        false // فقط سشن مهمه
    );

    if (!$res['ok']) {
        error_log("[SYNC][XUI] login failed HTTP={$res['status']} panel_id=" . ($panelRow['id'] ?? ''));
        @unlink($cookieFile);
        $cookieFile = '';
        return false;
    }

    return true;
}

/**
 * گرفتن ترافیک کلاینت X-UI با ایمیل (username)
 * خروجی: آرایه obj خام از X-UI یا null
 */
function xui_get_client_traffic_panel(array $panelRow, string $cookieFile, string $email): ?array
{
    $baseUrl = trim((string)($panelRow['base_url'] ?? ''));
    if ($baseUrl === '') {
        return null;
    }

    $url = xui_build_url($baseUrl, '/panel/api/inbounds/getClientTraffics/' . rawurlencode($email));
    $res = xui_http_request($url, 'GET', ['Accept' => 'application/json'], null, $cookieFile, true);

    if (!$res['ok'] || !is_array($res['json'])) {
        error_log("[SYNC][XUI] getClientTraffics HTTP={$res['status']} email={$email}");
        return null;
    }

    $json = $res['json'];
    if (empty($json['success']) || empty($json['obj']) || !is_array($json['obj'])) {
        // یعنی پیدا نکرده یا خطای منطقی
        if (!empty($json['msg'])) {
            error_log("[SYNC][XUI] getClientTraffics invalid for {$email}: " . $json['msg']);
        }
        return null;
    }

    return $json['obj'];
}

/**
 * گرفتن اطلاعات اینباند X-UI بر اساس inboundId
 */
function xui_get_inbound_panel(array $panelRow, string $cookieFile, int $inboundId): ?array
{
    if ($inboundId <= 0) {
        return null;
    }

    $baseUrl = trim((string)($panelRow['base_url'] ?? ''));
    if ($baseUrl === '') {
        return null;
    }

    $url = xui_build_url($baseUrl, '/panel/api/inbounds/get/' . $inboundId);

    $res = xui_http_request(
        $url,
        'GET',
        ['Accept' => 'application/json'],
        null,
        $cookieFile,
        true
    );

    if (!$res['ok'] || !is_array($res['json'])) {
        error_log("[SYNC][XUI] getInbound HTTP={$res['status']} inboundId={$inboundId}");
        return null;
    }

    $json = $res['json'];
    if (empty($json['success']) || empty($json['obj']) || !is_array($json['obj'])) {
        if (!empty($json['msg'])) {
            error_log("[SYNC][XUI] getInbound invalid inboundId={$inboundId}: " . $json['msg']);
        }
        return null;
    }

    return $json['obj'];
}

/**
 * ساخت لینک vless برای کیس:
 *  - protocol = vless
 *  - network = tcp
 *  - headerType = http
 *  - security = none
 *
 *  هاست از ستون host جدول vpn_panels
 *  اسم کانفیگ از marzban_user جدول services (+ id برای یونیک بودن)
 */
function xui_build_vless_tcp_http_no_tls_link(
    array $panelRow,
    array $traffic,
    array $inbound,
    array $serviceRow
): ?string {
    $protocol = strtolower((string)($inbound['protocol'] ?? ''));
    if ($protocol !== 'vless') {
        return null;
    }

    // streamSettings ممکنه رشته JSON باشه
    $streamSettings = $inbound['streamSettings'] ?? null;
    if (is_string($streamSettings)) {
        $decoded = json_decode($streamSettings, true);
        if (json_last_error() === JSON_ERROR_NONE) {
            $streamSettings = $decoded;
        } else {
            $streamSettings = null;
        }
    }

    if (!is_array($streamSettings)) {
        return null;
    }

    $network  = strtolower((string)($streamSettings['network'] ?? 'tcp'));
    $security = strtolower((string)($streamSettings['security'] ?? 'none'));

    if ($network !== 'tcp') {
        return null;
    }
    if ($security !== 'none') {
        return null;
    }

    // headerType از tcpSettings → header → type
    $headerType = '';
    if (!empty($streamSettings['tcpSettings']['header'])
        && is_array($streamSettings['tcpSettings']['header'])) {
        $headerType = strtolower((string)($streamSettings['tcpSettings']['header']['type'] ?? ''));
    }
    if ($headerType !== 'http') {
        return null;
    }

    // uuid از traffic
    $uuid = (string)($traffic['uuid'] ?? '');
    if ($uuid === '') {
        return null;
    }

    // پورت از اینباند
    $port = (int)($inbound['port'] ?? 0);
    if ($port <= 0) {
        return null;
    }

    // هاست از ستون host جدول vpn_panels، در صورت خالی بودن از base_url
    $serverHost = '';
    if (!empty($panelRow['host'])) {
        $serverHost = (string)$panelRow['host'];
    } else {
        $baseUrl   = (string)($panelRow['base_url'] ?? '');
        $serverHost = parse_url($baseUrl, PHP_URL_HOST) ?: '';
    }

    if ($serverHost === '') {
        return null;
    }

    // path از request.path
    $path = '/';
    if (!empty($streamSettings['tcpSettings']['header']['request']['path'])
        && is_array($streamSettings['tcpSettings']['header']['request']['path'])
        && isset($streamSettings['tcpSettings']['header']['request']['path'][0])
        && $streamSettings['tcpSettings']['header']['request']['path'][0] !== '') {
        $path = $streamSettings['tcpSettings']['header']['request']['path'][0];
    }

    // اسم کانفیگ از marzban_user + id سرویس
    $serviceUser = trim((string)($serviceRow['marzban_user'] ?? ''));
    $serviceId   = (int)($serviceRow['id'] ?? 0);

    if ($serviceUser === '') {
        $serviceUser = $serverHost;
    }

    $name = $serviceUser;
    if ($serviceId > 0) {
        $name .= '-' . $serviceId;
    }

    // type=tcp&encryption=none&path=...&headerType=http&security=none
    $query = http_build_query([
        'type'        => 'tcp',
        'encryption'  => 'none',
        'path'        => $path,
        'headerType'  => 'http',
        'security'    => 'none',
    ]);

    $link = sprintf(
        'vless://%s@%s:%d?%s#%s',
        $uuid,
        $serverHost,
        $port,
        $query,
        rawurlencode($name)
    );

    return $link;
}

/* ======================================================================
 *  Helper: تشخیص پروتکل از لینک کانفیگ
 * ====================================================================== */
function detect_protocol_from_link(string $link): string
{
    $l = strtolower($link);

    if (strpos($l, 'vmess://') === 0) {
        return 'vmess';
    }
    if (strpos($l, 'vless://') === 0) {
        return 'vless';
    }
    if (strpos($l, 'trojan://') === 0) {
        return 'trojan';
    }
    if (strpos($l, 'ss://') === 0 || strpos($l, 'ssr://') === 0) {
        return 'shadowsocks';
    }
    if (strpos($l, 'http://') === 0 || strpos($l, 'https://') === 0) {
        // معمولاً لینک اشتراک (Subscription) روی HTTP/HTTPS است
        return 'subscription';
    }

    return 'unknown';
}

/* ======================================================================
 *  سرویس‌ها (services + نوع پنل)
 * ====================================================================== */

try {
    $stmtServices = $pdo->query("
        SELECT s.*, p.type AS panel_type
        FROM services s
        JOIN vpn_panels p ON s.panel_id = p.id
        WHERE p.is_active = 1
        ORDER BY s.id DESC
    ");
    $services = $stmtServices->fetchAll(PDO::FETCH_ASSOC) ?: [];
} catch (Throwable $e) {
    error_log('[SYNC] cannot load services: ' . $e->getMessage());
    exit;
}

if (!$services) {
    error_log('[SYNC] no services found; nothing to do');
    exit;
}

/* ======================================================================
 *  استیتمنت‌های آپدیت / لاگ / نوتیف
 * ====================================================================== */

// آپدیت برای Marzban
$updOkMarzban = $pdo->prepare("
  UPDATE services
     SET status            = :status,
         data_limit_bytes  = :data_limit_bytes,
         used_bytes        = :used_bytes,
         up_bytes          = :up_bytes,
         down_bytes        = :down_bytes,
         usage_alert_level = :alert_level,
         expire_unix       = :expire_unix,
         expire_at         = CASE WHEN :expire2 IS NULL OR :expire2 = 0 THEN NULL ELSE FROM_UNIXTIME(:expire3) END,
         inbound_tag       = :inbound_tag,
         subscription_url  = CASE WHEN :sub_url IS NULL OR :sub_url = '' THEN subscription_url ELSE :sub_url END,
         configs           = CASE WHEN :configs IS NULL OR :configs = '' THEN configs ELSE :configs END,
         last_synced_at    = NOW()
   WHERE id = :id
");

// آپدیت برای X-UI
$updOkXui = $pdo->prepare("
  UPDATE services
     SET status            = :status,
         data_limit_bytes  = :data_limit_bytes,
         used_bytes        = :used_bytes,
         up_bytes          = :up_bytes,
         down_bytes        = :down_bytes,
         usage_alert_level = :alert_level,
         expire_unix       = :expire_unix,
         expire_at         = CASE WHEN :expire2 IS NULL OR :expire2 = 0 THEN NULL ELSE FROM_UNIXTIME(:expire3) END,
         inbound_id        = CASE WHEN :inbound_id IS NULL OR :inbound_id = 0 THEN inbound_id ELSE :inbound_id END,
         marzban_uid       = CASE WHEN :muid IS NULL OR :muid = '' THEN marzban_uid ELSE :muid END,
         configs           = CASE WHEN :configs IS NULL OR :configs = '' THEN configs ELSE :configs END,
         last_synced_at    = NOW()
   WHERE id = :id
");

// اگر یوزر روی سرور پیدا نشه → expired
$upd404 = $pdo->prepare("
  UPDATE services
     SET status         = 'expired',
         last_synced_at = NOW()
   WHERE id = :id
");

// ثبت لاگ مصرف در جدول service_usage_logs
// ثبت لاگ مصرف در جدول service_usage_logs
$insUsageLog = $pdo->prepare("
  INSERT INTO service_usage_logs
    (service_id, snapshot_time, status, data_limit_bytes, used_bytes, up_bytes, down_bytes)
  VALUES
    (:service_id, NOW(), :status, :data_limit_bytes, :used_bytes, :up_bytes, :down_bytes)
");

// نوتیف مصرف (۸۰ / ۹۵ / ۱۰۰ درصد)
$insUsageNotif = $pdo->prepare("
  INSERT INTO notifications (user_id, type, title, body, related_service_id, is_read)
  VALUES (:user_id, 'usage', :title, :body, :service_id, 0)
");

/* ======================================================================
 *  حلقه اصلی سینک
 * ====================================================================== */

foreach ($services as $svc) {
    $id        = (int)$svc['id'];
    $uname     = (string)($svc['marzban_user'] ?? '');
    $panelId   = (int)($svc['panel_id'] ?? 0);
    $panelType = strtolower(trim($svc['panel_type'] ?? ''));

    if ($uname === '' || $panelId <= 0) {
        // اطلاعات ناقص، رد می‌شه
        continue;
    }

    // برای اینکه سرور له نشه
    usleep(150000); // 0.15 ثانیه

    /* --------------------------- Marzban --------------------------- */
    if ($panelType === 'marzban') {

        if (empty($marzbanClients[$panelId])) {
            error_log("[SYNC][MB] no MarzbanService for panel_id={$panelId}");
            continue;
        }
        /** @var MarzbanService $mb */
        $mb = $marzbanClients[$panelId];

        $r = $mb->getUser($uname);

        if ($r['code'] === 404) {
            $upd404->execute([':id' => $id]);
            continue;
        }
        if ($r['code'] !== 200 || !$r['json']) {
            error_log("[SYNC][MB] getUser {$uname} failed code={$r['code']} err={$r['err']} raw={$r['raw']}");
            continue;
        }

        $data = $r['json'];

        $status     = $data['status']       ?? 'active';
        $dataLimit  = $data['data_limit']   ?? null;
        $used       = $data['used_traffic'] ?? null;
        $up         = $data['up']           ?? null;
        $down       = $data['down']         ?? null;
        if ($used === null && $up !== null && $down !== null) {
            $used = (int)$up + (int)$down;
        }
        $expire     = $data['expire']       ?? null;

        // inbound_tag
        $inboundTag = null;
        if (!empty($data['inbounds']['vless']) && is_array($data['inbounds']['vless'])) {
            $tags = $data['inbounds']['vless'];
            if (in_array('GRPC443', $tags, true)) {
                $inboundTag = 'GRPC443';
            } else {
                $inboundTag = $tags[0] ?? null;
            }
        }

        // subscription_url (فول)
        $sub_rel = '';
        if (!empty($data['subscription_url'])) {
            $sub_rel = (string)$data['subscription_url'];
        } elseif (!empty($data['links']) && is_array($data['links'])) {
            $first = reset($data['links']);
            if (is_string($first)) {
                $sub_rel = $first;
            }
        }

        $sub_full = null;
        if ($sub_rel !== '') {
            if (strpos($sub_rel, '/sub/') === 0) {
                // نسبی است → چسباندن به base_url
                $sub_full = rtrim($mb->getBaseUrl(), '/') . $sub_rel;
            } else {
                $sub_full = $sub_rel;
            }
        }

        $expireInt = ($expire === null ? null : (int)$expire);

        // ----------------- ساخت configs برای این سرویس (Marzban) -----------------
        $existingSub      = (string)($svc['subscription_url'] ?? '');
        $sub_for_configs  = $sub_full !== null && $sub_full !== '' ? $sub_full : $existingSub;
        $configsArr       = [];

        // ۱) لینک اشتراک (Subscription)
        if ($sub_for_configs !== '') {
            $prot = detect_protocol_from_link($sub_for_configs);
            $configsArr[] = [
                'id'       => 'subscription',
                'label'    => 'لینک اشتراک',
                'type'     => 'subscription',
                'protocol' => $prot,
                'client'   => 'all',
                'link'     => $sub_for_configs,
                'is_main'  => true,
            ];
        }

        // ۲) لینک‌های تکی (links) اگر وجود داشته باشه
        if (!empty($data['links']) && is_array($data['links'])) {
            $i = 1;
            foreach ($data['links'] as $lnk) {
                if (!is_string($lnk) || $lnk === '') {
                    continue;
                }
                $prot = detect_protocol_from_link($lnk);
                $configsArr[] = [
                    'id'       => 'link-' . $i,
                    'label'    => 'کانفیگ ' . $i,
                    'type'     => 'single',
                    'protocol' => $prot,
                    'client'   => 'all',
                    'link'     => $lnk,
                    'is_main'  => ($sub_for_configs === '' && $i === 1),
                ];
                $i++;
            }
        }

        $configsJson = $configsArr
            ? json_encode($configsArr, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
            : null;

        // ----------------- ثبت لاگ مصرف در service_usage_logs -----------------
        try {
            $insUsageLog->execute([
                ':service_id'       => $id,
                ':status'           => $status,
                ':data_limit_bytes' => ($dataLimit === null ? null : (int)$dataLimit),
                ':used_bytes'       => ($used      === null ? null : (int)$used),
                ':up_bytes'         => ($up        === null ? null : (int)$up),
                ':down_bytes'       => ($down      === null ? null : (int)$down),
            ]);
        } catch (Throwable $e) {
            error_log("[SYNC][MB] insUsageLog failed for service_id={$id}: " . $e->getMessage());
        }

        // ----------------- محاسبه درصد مصرف و نوتیف -----------------
        $oldAlertLevel = (int)($svc['usage_alert_level'] ?? 0);
        $newAlertLevel = $oldAlertLevel;
        $usagePercent  = null;

        if (!empty($dataLimit) && $dataLimit > 0 && $used !== null && $used >= 0) {
            $usagePercent = ($used / $dataLimit) * 100;

            if ($usagePercent >= 100) {
                $newAlertLevel = 3;
            } elseif ($usagePercent >= 95) {
                $newAlertLevel = 2;
            } elseif ($usagePercent >= 80) {
                $newAlertLevel = 1;
            } else {
                $newAlertLevel = 0;
            }
        }

        if ($usagePercent !== null && $newAlertLevel > $oldAlertLevel) {
            $title = '';
            $body  = '';

            if ($newAlertLevel === 1) {
                $title = 'هشدار: ۸۰٪ حجم سرویس مصرف شد';
                $body  = "مصرف سرویس شما ({$uname}) از ۸۰٪ حجم پلن بیشتر شده است.";
            } elseif ($newAlertLevel === 2) {
                $title = 'هشدار تمدید: ۹۵٪ حجم سرویس مصرف شد';
                $body  = "مصرف سرویس شما ({$uname}) از ۹۵٪ حجم پلن بیشتر شده است. در صورت تمایل اکانت خود را تمدید کنید.";
            } elseif ($newAlertLevel === 3) {
                $title = 'حجم سرویس شما به پایان رسید';
                $body  = "حجم سرویس شما ({$uname}) به پایان رسید .";
            }

            try {
                $insUsageNotif->execute([
                    ':user_id'    => (int)$svc['user_id'],
                    ':title'      => $title,
                    ':body'       => $body,
                    ':service_id' => $id,
                ]);
            } catch (Throwable $e) {
                error_log("[SYNC][MB] insUsageNotif failed for service_id={$id}: " . $e->getMessage());
            }
        }

        // ----------------- آپدیت DB -----------------
        $updOkMarzban->execute([
            ':status'           => $status,
            ':data_limit_bytes' => ($dataLimit === null ? null : (int)$dataLimit),
            ':used_bytes'       => ($used      === null ? null : (int)$used),
            ':up_bytes'         => ($up        === null ? null : (int)$up),
            ':down_bytes'       => ($down      === null ? null : (int)$down),
            ':alert_level'      => $newAlertLevel,
            ':expire_unix'      => $expireInt,
            ':expire2'          => $expireInt,
            ':expire3'          => $expireInt,
            ':inbound_tag'      => $inboundTag,
            ':sub_url'          => $sub_full,
            ':configs'          => $configsJson,
            ':id'               => $id,
        ]);

        continue;
    }

    /* --------------------------- X-UI --------------------------- */
    if ($panelType === 'xui') {

        if (empty($xuiPanels[$panelId])) {
            error_log("[SYNC][XUI] no panelRow for panel_id={$panelId}");
            continue;
        }
        $panelRow = $xuiPanels[$panelId];

        $cookieFile = '';
        if (!xui_login_panel($panelRow, $cookieFile)) {
            // لاگین نشد
            continue;
        }

        $traffic = xui_get_client_traffic_panel($panelRow, $cookieFile, $uname);

        if (!$traffic) {
            if ($cookieFile && is_file($cookieFile)) {
                @unlink($cookieFile);
            }
            // اگر پیدا نشد، می‌تونیم expired کنیم
            $upd404->execute([':id' => $id]);
            continue;
        }

        // ساختار traffic مثل obj در X-UI:
        // inboundId, uuid, enable, up, down, total, expiryTime
        $inboundId = isset($traffic['inboundId']) ? (int)$traffic['inboundId'] : 0;
        $uuid      = (string)($traffic['uuid'] ?? '');
        $enable    = !empty($traffic['enable']);

        $upBytes    = isset($traffic['up'])    ? (float)$traffic['up']    : 0.0;
        $downBytes  = isset($traffic['down'])  ? (float)$traffic['down']  : 0.0;
        $totalBytes = isset($traffic['total']) ? (float)$traffic['total'] : 0.0;
        $expireMs   = isset($traffic['expiryTime']) ? (int)$traffic['expiryTime'] : 0;

        $usedBytes = $upBytes + $downBytes;

        $expireUnix = null;
        if ($expireMs > 0) {
            $expireUnix = (int)floor($expireMs / 1000);
        }

        // وضعیت
        $status = 'active';
        if (!$enable) {
            $status = 'disabled';
        }
        if ($expireUnix !== null && $expireUnix <= time() && $expireMs > 0) {
            $status = 'expired';
        }

        // ----------------- ساخت configs برای این سرویس (X-UI) -----------------
        $configsArr = [];
        $hasMain    = false;

        // اگر اینباند داشتیم، سعی کن info اینباند را بگیری
        $inboundObj = null;
        if ($inboundId > 0) {
            $inboundObj = xui_get_inbound_panel($panelRow, $cookieFile, $inboundId);
        }

        // اگر inbound مطابق شرط tcp + http + بدون tls بود → لینک vless بساز
        if ($inboundObj && $uuid !== '') {
            $vlessLink = xui_build_vless_tcp_http_no_tls_link(
                $panelRow,
                $traffic,
                $inboundObj,
                $svc // کل ردیف سرویس برای name/host
            );

            if ($vlessLink) {
                $configsArr[] = [
                    'id'       => 'xui-vless-main',
                    'label'    => 'کانفیگ اصلی (TCP-HTTP)',
                    'type'     => 'single',
                    'protocol' => 'vless',
                    'client'   => 'all',
                    'link'     => $vlessLink,
                    'is_main'  => true,
                ];
                $hasMain = true;
            }
        }

        // لینک اشتراک اگر در services ذخیره شده
        $sub_for_configs = (string)($svc['subscription_url'] ?? '');
        if ($sub_for_configs !== '') {
            $prot = detect_protocol_from_link($sub_for_configs);
            $configsArr[] = [
                'id'       => 'subscription',
                'label'    => 'لینک اشتراک',
                'type'     => 'subscription',
                'protocol' => $prot,
                'client'   => 'all',
                'link'     => $sub_for_configs,
                'is_main'  => !$hasMain, // اگر main نداشتیم، این main باشد
            ];
        }

        $configsJson = $configsArr
            ? json_encode($configsArr, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
            : null;

        if ($cookieFile && is_file($cookieFile)) {
            @unlink($cookieFile);
        }

        // ----------------- ثبت لاگ مصرف در service_usage_logs -----------------
        try {
            $insUsageLog->execute([
                ':service_id'       => $id,
                ':status'           => $status,
                ':data_limit_bytes' => ($totalBytes > 0 ? (int)$totalBytes : null),
                ':used_bytes'       => (int)$usedBytes,
                ':up_bytes'         => (int)$upBytes,
                ':down_bytes'       => (int)$downBytes,
            ]);
        } catch (Throwable $e) {
            error_log("[SYNC][XUI] insUsageLog failed for service_id={$id}: " . $e->getMessage());
        }

        // ----------------- محاسبه درصد مصرف و نوتیف -----------------
        $oldAlertLevel = (int)($svc['usage_alert_level'] ?? 0);
        $newAlertLevel = $oldAlertLevel;
        $usagePercent  = null;

        if ($totalBytes > 0 && $usedBytes >= 0) {
            $usagePercent = ($usedBytes / $totalBytes) * 100;

            if ($usagePercent >= 100) {
                $newAlertLevel = 3;
            } elseif ($usagePercent >= 95) {
                $newAlertLevel = 2;
            } elseif ($usagePercent >= 80) {
                $newAlertLevel = 1;
            } else {
                $newAlertLevel = 0;
            }
        }

        if ($usagePercent !== null && $newAlertLevel > $oldAlertLevel) {
            $title = '';
            $body  = '';

            if ($newAlertLevel === 1) {
                $title = 'هشدار: ۸۰٪ حجم سرویس مصرف شد';
                $body  = "مصرف سرویس شما ({$uname}) از ۸۰٪ حجم پلن بیشتر شده است.";
            } elseif ($newAlertLevel === 2) {
                $title = 'هشدار: ۹۵٪ حجم سرویس مصرف شد';
                $body  = "مصرف سرویس شما ({$uname}) از ۹۵٪ حجم پلن بیشتر شده است.";
            } elseif ($newAlertLevel === 3) {
                $title = 'هشدار: ۱۰۰٪ حجم سرویس مصرف شد';
                $body  = "حجم سرویس شما ({$uname}) کاملاً مصرف شده است.";
            }

            try {
                $insUsageNotif->execute([
                    ':user_id'    => (int)$svc['user_id'],
                    ':title'      => $title,
                    ':body'       => $body,
                    ':service_id' => $id,
                ]);
            } catch (Throwable $e) {
                error_log("[SYNC][XUI] insUsageNotif failed for service_id={$id}: " . $e->getMessage());
            }
        }

        // ----------------- آپدیت DB -----------------
        $updOkXui->execute([
            ':status'           => $status,
            ':data_limit_bytes' => ($totalBytes > 0 ? (int)$totalBytes : null),
            ':used_bytes'       => ($usedBytes  > 0 ? (int)$usedBytes  : null),
            ':up_bytes'         => ($upBytes    > 0 ? (int)$upBytes    : null),
            ':down_bytes'       => ($downBytes  > 0 ? (int)$downBytes  : null),
            ':alert_level'      => $newAlertLevel,
            ':expire_unix'      => $expireUnix,
            ':expire2'          => $expireUnix,
            ':expire3'          => $expireUnix,
            ':inbound_id'       => ($inboundId > 0 ? $inboundId : null),
            ':muid'             => ($uuid !== '' ? $uuid : null),
            ':configs'          => $configsJson,
            ':id'               => $id,
        ]);

        continue;
    }

    // اگر نوع پنل چیز دیگه‌ای بود فعلاً نادیده می‌گیریم
    continue;
}

error_log('[SYNC] completed at ' . date('c'));
