<?php
/**
 * xui.php
 * آداپتر X-UI برای موتور فروش VPN (نسخه متصل به دیتابیس)
 *
 * این فایل هیچ کاری با لاجیک پرداخت/کیف‌پول نمی‌کند؛
 * فقط بین سیستم خودت و پنل X-UI واسطه است.
 *
 * ورودی‌ها را از callback.php / panel_data.php می‌گیرد و با APIهای X-UI حرف می‌زند.
 *
 * ─────────────────────────────────────────────────────────────
 * اکشن‌های پشتیبانی‌شده:
 *
 * 1) ساخت سرویس جدید روی X-UI
 *    action       = create_service
 *    server_id    = panel_key از جدول vpn_panels (type = 'xui')
 *    inbound_id   = آیدی اینباند (از plans.xui_inbound_id می‌آید)
 *    client_email = ایمیل/شناسه کلاینت در X-UI (اجباری)
 *    total_gb     = حجم پلن به گیگ‌ (مثبت؛ ۰ یا منفی یعنی نامحدود)
 *    expire_days  = تعداد روز اعتبار از الان (۰ یعنی بدون انقضا)
 *    sub_id       = توکن ساب (اختیاری، اگر ندهی رندوم ساخته می‌شود)
 *
 *    خروجی:
 *    {
 *      "ok": true,
 *      "service": {
 *        "server_id": "xui",
 *        "inbound_id": 6,
 *        "client_email": "abc123xy",
 *        "client_uuid": "20714ae1-db48-4202-9639-58237500127d",
 *        "sub_id": "z70791vpexfxw57h",
 *        "total_gb": 200,
 *        "expire_days": 90
 *      }
 *    }
 *
 * 2) تمدید سرویس موجود (با قانون: اول ریست ترافیک، بعد ست حجم/زمان جدید)
 *    action       = renew_service
 *    server_id    = همان panel_key
 *    inbound_id   = آیدی اینباند
 *    client_email = همان ایمیل/شناسه کلاینت در X-UI
 *    client_uuid  = UUID کلاینت (id داخل settings.clients)
 *    total_gb     = حجم جدید (به گیگ، ۰ = نامحدود)
 *    expire_days  = روز جدید از الان (۰ = بدون انقضا)
 *
 *    خروجی مشابه create_service است.
 *
 * 3) گرفتن وضعیت کلاینت برای نمایش در پنل
 *    action       = get_client
 *    server_id    = panel_key
 *    client_email = ایمیل/شناسه کلاینت در X-UI
 *
 *    خروجی:
 *    {
 *      "ok": true,
 *      "service": {
 *        "client_email": "abc123xy",
 *        "client_uuid": "20714ae1-db48-4202-9639-58237500127d",
 *        "inbound_id": 6,
 *        "status": "active|expired|disabled",
 *        "used_gb": 1.23,
 *        "total_gb": 200,
 *        "remaining_gb": 198.77,
 *        "expire_days": 25
 *      }
 *    }
 *
 * توجه:
 *  - ساختن subscription URL این‌جا انجام نمی‌شود؛ فقط sub_id برگردانده می‌شود.
 *    خودت در سیستم اصلی‌ات (مثلاً با الگوهای `/subs/{subId}` یا ساب‌سرور خودت)
 *    لینک نهایی را می‌سازی و در دیتابیس ذخیره می‌کنی.
 */

require __DIR__ . '/db.php'; // این باید $pdo را بسازد

/*──────────────────────────────────────────────────────────────
  خروجی JSON استاندارد
  ──────────────────────────────────────────────────────────────*/
function respond_json(array $data, int $httpStatus = 200): void
{
    http_response_code($httpStatus);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

function respond_error(string $code, string $message = null, int $httpStatus = 400): void
{
    if ($message === null) {
        $message = $code;
    }
    respond_json([
        'ok'      => false,
        'error'   => $code,
        'message' => $message,
    ], $httpStatus);
}

/*──────────────────────────────────────────────────────────────
  ابزار کمکی
  ──────────────────────────────────────────────────────────────*/
function build_url(string $baseUrl, string $path): string
{
    $base = rtrim($baseUrl, '/');
    $p    = '/' . ltrim($path, '/');
    return $base . $p;
}

function generate_random_string(int $length = 16): string
{
    $chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
    $max   = strlen($chars) - 1;
    $out   = '';
    for ($i = 0; $i < $length; $i++) {
        $out .= $chars[random_int(0, $max)];
    }
    return $out;
}

function generate_uuid_v4(): string
{
    $data = random_bytes(16);
    // نسخه 4
    $data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
    // وریانت RFC 4122
    $data[8] = chr((ord($data[8]) & 0x3f) | 0x80);

    $hex = bin2hex($data);
    return sprintf(
        '%s-%s-%s-%s-%s',
        substr($hex, 0, 8),
        substr($hex, 8, 4),
        substr($hex, 12, 4),
        substr($hex, 16, 4),
        substr($hex, 20)
    );
}

/**
 * درخواست HTTP عمومی با cURL
 * - $cookieFile برای نگه‌داشتن سشن بین login و بقیه.
 */
function 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) {
        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 از دیتابیس بر اساس server_id = panel_key
 *
 * جدول vpn_panels:
 *  - panel_key
 *  - type = 'xui'
 *  - base_url
 *  - admin_user
 *  - admin_pass
 *  - is_active = 1
 */
function get_panel_config(array $params, PDO $pdo): array
{
    if (!isset($pdo) || !$pdo instanceof PDO) {
        respond_error('DB_NOT_READY', 'Database connection is not available', 500);
    }

    $serverId = $params['server_id'] ?? null;
    if (!$serverId) {
        respond_error('MISSING_SERVER_ID', 'server_id is required');
    }

    $sql = "
        SELECT panel_key, type, base_url, admin_user, admin_pass, is_active
        FROM vpn_panels
        WHERE panel_key = :k
          AND type = 'xui'
          AND is_active = 1
        LIMIT 1
    ";

    $stmt = $pdo->prepare($sql);
    $stmt->execute([':k' => $serverId]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$row) {
        respond_error('UNKNOWN_SERVER', 'X-UI server not found in database: ' . $serverId, 404);
    }

    if (empty($row['base_url']) || empty($row['admin_user']) || empty($row['admin_pass'])) {
        respond_error('SERVER_CONFIG_INCOMPLETE', 'X-UI server config is incomplete for ' . $serverId, 500);
    }

    return [
        'server_id' => $row['panel_key'],
        'base_url'  => $row['base_url'],
        'username'  => $row['admin_user'],
        'password'  => $row['admin_pass'],
    ];
}

/**
 * لاگین به X-UI؛ cookieFile را برمی‌گرداند تا برای بقیه درخواست‌ها استفاده شود.
 */
function xui_login(array $panelCfg, ?string &$cookieFile): void
{
    // اینجا خودمون مقدار درست رو ست می‌کنیم
    $cookieFile = tempnam(sys_get_temp_dir(), 'xui_');

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

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

    if (!$res['ok']) {
        @unlink($cookieFile);
        $msg = 'Login failed (HTTP ' . $res['status'] . ')';
        respond_error('XUI_LOGIN_FAILED', $msg, 502);
    }
}


/**
 * گرفتن اطلاعات inbound (برای renew به‌خصوص)
 */
function xui_get_inbound(array $panelCfg, string $cookieFile, int $inboundId): array
{
    $url = build_url($panelCfg['base_url'], '/panel/api/inbounds/get/' . $inboundId);
    $res = http_request($url, 'GET', ['Accept' => 'application/json'], null, $cookieFile, true);

    if (!$res['ok'] || !is_array($res['json'])) {
        respond_error('XUI_GET_INBOUND_FAILED', 'Failed to get inbound info', 502);
    }

    $json = $res['json'];
    if (empty($json['success']) || empty($json['obj'])) {
        respond_error('XUI_GET_INBOUND_INVALID', 'Invalid inbound response', 502);
    }

    return $json['obj'];
}

/**
 * گرفتن ترافیک کلاینت با ایمیل (برای get_client)
 */
function xui_get_client_traffic(array $panelCfg, string $cookieFile, string $email): array
{
    $url = build_url($panelCfg['base_url'], '/panel/api/inbounds/getClientTraffics/' . rawurlencode($email));
    $res = http_request($url, 'GET', ['Accept' => 'application/json'], null, $cookieFile, true);

    if (!$res['ok'] || !is_array($res['json'])) {
        respond_error('XUI_GET_TRAFFIC_FAILED', 'Failed to get client traffic', 502);
    }
    $json = $res['json'];
    if (empty($json['success']) || empty($json['obj']) || !is_array($json['obj'])) {
        respond_error('XUI_GET_TRAFFIC_INVALID', 'Invalid client traffic response', 502);
    }

    return $json['obj'];
}

/*──────────────────────────────────────────────────────────────
  لاجیک اکشن‌ها
  ──────────────────────────────────────────────────────────────*/

/**
 * ساخت سرویس جدید
 */
function action_create_service(array $params, PDO $pdo): void
{
    $panelCfg = get_panel_config($params, $pdo);

    $inboundId = isset($params['inbound_id']) ? (int)$params['inbound_id'] : 0;
    if ($inboundId <= 0) {
        respond_error('MISSING_INBOUND_ID', 'inbound_id must be a positive integer');
    }

    $clientEmail = trim($params['client_email'] ?? '');
    if ($clientEmail === '') {
        respond_error('MISSING_CLIENT_EMAIL', 'client_email is required');
    }

    $totalGb    = isset($params['total_gb']) ? (float)$params['total_gb'] : 0.0;
    $expireDays = isset($params['expire_days']) ? (int)$params['expire_days'] : 0;

    $subId = trim($params['sub_id'] ?? '');
    if ($subId === '') {
        $subId = generate_random_string(16);
    }

    // تبدیل گیگ به بایت برای totalGB (طبق نمونه داک)
    $totalBytes = $totalGb > 0 ? (int)round($totalGb * 1073741824) : 0;

    // expiryTime به میلی‌ثانیه یونیکس
    if ($expireDays > 0) {
        $expiryTime = (time() + $expireDays * 86400) * 1000;
    } else {
        $expiryTime = 0;
    }

    $clientUuid = generate_uuid_v4();

    $client = [
        'id'         => $clientUuid,
        'flow'       => '',
        'email'      => $clientEmail,
        'limitIp'    => 0,
        'totalGB'    => $totalBytes,
        'expiryTime' => $expiryTime,
        'enable'     => true,
        'tgId'       => '',
        'subId'      => $subId,
        'comment'    => '',
        'reset'      => 0,
    ];

    $settingsJson = json_encode(
        ['clients' => [$client]],
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
    );

    // مرحله ۱: لاگین
    xui_login($panelCfg, $cookieFile);

    // مرحله ۲: addClient
    $url    = build_url($panelCfg['base_url'], '/panel/api/inbounds/addClient');
    $fields = [
        'id'       => $inboundId,
        'settings' => $settingsJson,
    ];

    $res = http_request($url, 'POST', ['Accept' => 'application/json'], $fields, $cookieFile, true);

    @unlink($cookieFile);

    if (!$res['ok'] || !is_array($res['json'])) {
        respond_error('XUI_ADD_CLIENT_FAILED', 'Failed to add client', 502);
    }

    $json = $res['json'];
    if (empty($json['success'])) {
        $msg = isset($json['msg']) ? (string)$json['msg'] : 'X-UI addClient not successful';
        respond_error('XUI_ADD_CLIENT_NOT_SUCCESS', $msg, 502);
    }

    respond_json([
        'ok'      => true,
        'service' => [
            'server_id'    => $panelCfg['server_id'],
            'inbound_id'   => $inboundId,
            'client_email' => $clientEmail,
            'client_uuid'  => $clientUuid,
            'sub_id'       => $subId,
            'total_gb'     => $totalGb,
            'expire_days'  => $expireDays,
        ],
    ]);
}

/**
 * تمدید سرویس موجود
 * قانون: اول resetClientTraffic، بعد updateClient با حجم/تاریخ جدید.
 */
function action_renew_service(array $params, PDO $pdo): void
{
    $panelCfg = get_panel_config($params, $pdo);

    $inboundId = isset($params['inbound_id']) ? (int)$params['inbound_id'] : 0;
    if ($inboundId <= 0) {
        respond_error('MISSING_INBOUND_ID', 'inbound_id must be a positive integer');
    }

    $clientEmail = trim($params['client_email'] ?? '');
    if ($clientEmail === '') {
        respond_error('MISSING_CLIENT_EMAIL', 'client_email is required');
    }

    $clientUuid = trim($params['client_uuid'] ?? '');
    if ($clientUuid === '') {
        respond_error('MISSING_CLIENT_UUID', 'client_uuid is required');
    }

    $totalGb    = isset($params['total_gb']) ? (float)$params['total_gb'] : 0.0;
    $expireDays = isset($params['expire_days']) ? (int)$params['expire_days'] : 0;

    $totalBytes = $totalGb > 0 ? (int)round($totalGb * 1073741824) : 0;

    if ($expireDays > 0) {
        $expiryTime = (time() + $expireDays * 86400) * 1000;
    } else {
        $expiryTime = 0;
    }

    // مرحله ۱: لاگین
    xui_login($panelCfg, $cookieFile);

    // مرحله ۲: ریست ترافیک
    $resetUrl = build_url(
        $panelCfg['base_url'],
        '/panel/api/inbounds/' . $inboundId . '/resetClientTraffic/' . rawurlencode($clientEmail)
    );
    $resReset = http_request($resetUrl, 'POST', ['Accept' => 'application/json'], null, $cookieFile, true);
    if (!$resReset['ok']) {
        @unlink($cookieFile);
        respond_error('XUI_RESET_FAILED', 'Failed to reset client traffic', 502);
    }

    // مرحله ۳: گرفتن inbound و پیدا کردن کلاینت برای حفظ subId و سایر فیلدها
    $inboundObj = xui_get_inbound($panelCfg, $cookieFile, $inboundId);

    $settingsStr = $inboundObj['settings'] ?? '';
    $settingsArr = [];
    if ($settingsStr !== '') {
        $decoded = json_decode($settingsStr, true);
        if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
            $settingsArr = $decoded;
        }
    }

    $clients = $settingsArr['clients'] ?? [];
    if (!is_array($clients) || !count($clients)) {
        @unlink($cookieFile);
        respond_error('XUI_RENEW_CLIENT_NOT_FOUND', 'No clients found in inbound', 502);
    }

    $foundClient = null;
    foreach ($clients as $cl) {
        if (
            (isset($cl['id']) && $cl['id'] === $clientUuid) ||
            (isset($cl['email']) && $cl['email'] === $clientEmail)
        ) {
            $foundClient = $cl;
            break;
        }
    }

    if (!$foundClient) {
        @unlink($cookieFile);
        respond_error('XUI_RENEW_CLIENT_NOT_FOUND', 'Client not found in inbound settings', 502);
    }

    // به‌روزرسانی فیلدهای حجم و تاریخ
    $foundClient['totalGB']    = $totalBytes;
    $foundClient['expiryTime'] = $expiryTime;
    $foundClient['enable']     = true;

    // settings جدید فقط با همین کلاینت
    $newSettingsJson = json_encode(
        ['clients' => [$foundClient]],
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
    );

    // مرحله ۴: updateClient/{uuid}
    $updateUrl = build_url(
        $panelCfg['base_url'],
        '/panel/api/inbounds/updateClient/' . rawurlencode($clientUuid)
    );

    $body = json_encode(
        [
            'id'       => $inboundId,
            'settings' => $newSettingsJson,
        ],
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
    );

    $resUpdate = http_request(
        $updateUrl,
        'POST',
        [
            'Accept'       => 'application/json',
            'Content-Type' => 'application/json',
        ],
        $body,
        $cookieFile,
        true
    );

    @unlink($cookieFile);

    if (!$resUpdate['ok'] || !is_array($resUpdate['json'])) {
        respond_error('XUI_UPDATE_CLIENT_FAILED', 'Failed to update client', 502);
    }

    $json = $resUpdate['json'];
    if (empty($json['success'])) {
        $msg = isset($json['msg']) ? (string)$json['msg'] : 'X-UI updateClient not successful';
        respond_error('XUI_UPDATE_CLIENT_NOT_SUCCESS', $msg, 502);
    }

    respond_json([
        'ok'      => true,
        'service' => [
            'server_id'    => $panelCfg['server_id'],
            'inbound_id'   => $inboundId,
            'client_email' => $clientEmail,
            'client_uuid'  => $clientUuid,
            'total_gb'     => $totalGb,
            'expire_days'  => $expireDays,
        ],
    ]);
}

/**
 * گرفتن وضعیت کلاینت برای نمایش در پنل
 */
function action_get_client(array $params, PDO $pdo): void
{
    $panelCfg = get_panel_config($params, $pdo);

    $clientEmail = trim($params['client_email'] ?? '');
    if ($clientEmail === '') {
        respond_error('MISSING_CLIENT_EMAIL', 'client_email is required');
    }

    // لاگین
    xui_login($panelCfg, $cookieFile);

    // ترافیک بر اساس ایمیل
    $traffic = xui_get_client_traffic($panelCfg, $cookieFile, $clientEmail);

    @unlink($cookieFile);

    $inboundId  = isset($traffic['inboundId']) ? (int)$traffic['inboundId'] : 0;
    $uuid       = $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;

    $usedGb    = $usedBytes > 0 ? $usedBytes / 1073741824 : 0.0;
    $totalGb   = $totalBytes > 0 ? $totalBytes / 1073741824 : 0.0;
    $remainGb  = $totalGb > 0 ? max($totalGb - $usedGb, 0.0) : 0.0;

    $expireDays = null;
    $nowSec     = time();
    if ($expireMs > 0) {
        $expireSec = (int)floor($expireMs / 1000);
        $diff      = $expireSec - $nowSec;
        if ($diff <= 0) {
            $expireDays = 0;
        } else {
            $expireDays = (int)ceil($diff / 86400);
        }
    }

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

    respond_json([
        'ok'      => true,
        'service' => [
            'client_email'  => $clientEmail,
            'client_uuid'   => $uuid,
            'inbound_id'    => $inboundId,
            'status'        => $status,
            'used_gb'       => round($usedGb, 2),
            'total_gb'      => $totalGb > 0 ? round($totalGb, 2) : 0,
            'remaining_gb'  => $totalGb > 0 ? round($remainGb, 2) : 0,
            'expire_days'   => $expireDays,
        ],
    ]);
}

/*──────────────────────────────────────────────────────────────
  ورودی اصلی اسکریپت
  ──────────────────────────────────────────────────────────────*/

$rawInput = file_get_contents('php://input');
$bodyData = [];
if ($rawInput !== false && $rawInput !== '') {
    $decoded = json_decode($rawInput, true);
    if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
        $bodyData = $decoded;
    }
}

$params = array_merge($_GET, $_POST, $bodyData);

$action = $params['action'] ?? null;
if (!$action) {
    respond_error('MISSING_ACTION', 'action is required');
}

if (!isset($pdo) || !$pdo instanceof PDO) {
    respond_error('DB_NOT_READY', 'Database connection is not available', 500);
}

switch ($action) {
    case 'create_service':
        action_create_service($params, $pdo);
        break;

    case 'renew_service':
        action_renew_service($params, $pdo);
        break;

    case 'get_client':
        action_get_client($params, $pdo);
        break;

    default:
        respond_error('UNKNOWN_ACTION', 'Unknown action: ' . $action);
}
