<?php
/**
 * FULL CONFIG EXTRACTOR (Links + Full V2RayNG JSON)
 * - Supports: vless, vmess, trojan, ss, hysteria, hy2, hysteria2
 * - Fixes for ALL schemes:
 *    1) spaces/unicode in #fragment  => URL-encode
 *    2) trailing junk chars          => trim
 * - SS fix:
 *    - handles SIP002 where base64 contains %3D (URL-encoded "=")
 * - Renames output to CFG_NAME
 * - Cache upstream response
 * - Extracts link configs + full V2RayNG JSON configs
 * - Dedupe by SHA-256
 * - OUTPUT: only ONE config with equal probability
 */

declare(strict_types=1);

// ================= CACHE =================
$cacheFile = __DIR__ . '/cache.txt';
$cacheTime = 30 * 60; // 30 min

$url = "https://melomelo.xyz/server_app/SurgeVPN.php";

if (is_file($cacheFile) && (time() - filemtime($cacheFile)) < $cacheTime) {
    $response = file_get_contents($cacheFile);
} else {
    $ctx = stream_context_create([
        'http' => [
            'timeout' => 10,
            'header'  => "User-Agent: Mozilla/5.0\r\n",
        ],
        'ssl' => [
            'verify_peer'      => false,
            'verify_peer_name' => false,
        ],
    ]);

    $response = @file_get_contents($url, false, $ctx);
    if ($response === false || $response === null) {
        header('Content-Type: text/plain; charset=UTF-8');
        die("خطا در دریافت داده از API");
    }
    file_put_contents($cacheFile, $response, LOCK_EX);
}

define('CFG_NAME', '@shenvpn');

// ================= Helpers =================
function is_uuid($s): bool {
    return (bool)preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i', (string)$s);
}

function is_host_like($h): bool {
    $h = (string)$h;
    if ($h === '') return false;
    if (preg_match('/^\[[0-9a-f:]+\]$/i', $h)) return true;
    if (filter_var($h, FILTER_VALIDATE_IP)) return true;
    return (bool)preg_match('/^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i', $h);
}

function is_port($p): bool {
    $p = (string)$p;
    if (!ctype_digit($p)) return false;
    $n = (int)$p;
    return $n >= 1 && $n <= 65535;
}

function base64url_to_base64($s): string {
    $s = strtr((string)$s, '-_', '+/');
    $pad = strlen($s) % 4;
    if ($pad) $s .= str_repeat('=', 4 - $pad);
    return $s;
}

// فقط یک‌بار urldecode برای %XX (بدون خراب کردن + و ...)
function safe_urldecode_once($s): string {
    $s = (string)$s;
    return preg_replace_callback('/%[0-9A-Fa-f]{2}/', fn($m) => rawurldecode($m[0]), $s);
}

/**
 * ✅ Universal sanitizer for ALL link schemes:
 * - trims dangerous trailing chars
 * - fixes #fragment (spaces/unicode) => urlencoded
 */
function sanitize_link(string $u): string {
    $u = trim($u);
    $u = rtrim($u, " \t\r\n)];,.!?>");

    if (strpos($u, '#') !== false) {
        [$base, $frag] = explode('#', $u, 2);
        $frag = rawurlencode(rawurldecode($frag)); // spaces => %20
        $u = $base . '#' . $frag;
    }
    return $u;
}

function normalize_fragment_name(string $name): string {
    return rawurlencode($name);
}

// ================= Rename =================
function renameLinkConfig(string $cfg): string {
    $cfg = sanitize_link($cfg);

    // VMESS: update ps
    if (stripos($cfg, 'vmess://') === 0) {
        $b64 = base64url_to_base64(trim(substr($cfg, 8)));
        $dec = base64_decode($b64, true);
        if ($dec === false) return $cfg;
        $json = json_decode($dec, true);
        if (!is_array($json)) return $cfg;
        $json['ps'] = CFG_NAME;
        return 'vmess://' . base64_encode(json_encode($json, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
    }

    // SS: parse + rebuild to SIP002, always set fragment to CFG_NAME
    if (stripos($cfg, 'ss://') === 0) {
        $x = ss_parse($cfg);
        if ($x && ss_validate_parsed($x)) {
            return ss_build_sip002($x['method'], $x['pass'], $x['host'], $x['port'], $x['query'] ?? '', normalize_fragment_name(CFG_NAME));
        }
        if (strpos($cfg, '#') !== false) return preg_replace('/#.*/', '#' . normalize_fragment_name(CFG_NAME), $cfg);
        return $cfg . '#' . normalize_fragment_name(CFG_NAME);
    }

    // Others: replace/set fragment safely
    if (strpos($cfg, '#') !== false) return preg_replace('/#.*/', '#' . normalize_fragment_name(CFG_NAME), $cfg);
    return $cfg . '#' . normalize_fragment_name(CFG_NAME);
}

function renameJsonConfig(string $jsonRaw): string {
    $decoded = json_decode($jsonRaw, true);
    if (!is_array($decoded)) return $jsonRaw;
    $decoded['remarks'] = CFG_NAME;

    if (isset($decoded['outbounds']) && is_array($decoded['outbounds'])) {
        foreach ($decoded['outbounds'] as &$ob) {
            if (is_array($ob)) $ob['tag'] = CFG_NAME;
        }
        unset($ob);
    }

    return json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

// ================= Validators =================
function validate_vless(string $url): bool {
    $url = sanitize_link($url);
    $p = @parse_url($url);
    if (!$p || strtolower($p['scheme'] ?? '') !== 'vless') return false;

    if (empty($p['user']) || !is_uuid($p['user'])) return false;
    if (empty($p['host']) || !is_host_like($p['host'])) return false;
    if (empty($p['port']) || !is_port($p['port'])) return false;

    parse_str($p['query'] ?? '', $q);
    $sec = strtolower((string)($q['security'] ?? ''));

    if ($sec === 'reality') {
        if (empty($q['pbk']) || strlen((string)$q['pbk']) < 10) return false;
        if (!empty($q['sni']) && !is_host_like((string)$q['sni'])) return false;
    } else {
        if ($sec === 'tls' && !empty($q['sni']) && !is_host_like((string)$q['sni'])) return false;
    }
    return true;
}

function validate_vmess(string $url): bool {
    $url = sanitize_link($url);
    if (stripos($url, 'vmess://') !== 0) return false;

    $b64 = base64url_to_base64(trim(substr($url, 8)));
    $dec = base64_decode($b64, true);
    if ($dec === false) return false;

    $j = json_decode($dec, true);
    if (!is_array($j)) return false;

    $add  = (string)($j['add'] ?? '');
    $port = (string)($j['port'] ?? '');
    $id   = (string)($j['id'] ?? '');

    if ($add === '' || !is_host_like($add)) return false;
    if ($port === '' || !is_port($port)) return false;
    if ($id === '' || !is_uuid($id)) return false;

    $tls = strtolower((string)($j['tls'] ?? ''));
    $sni = (string)($j['sni'] ?? '');
    if ($tls !== '' && $tls !== 'none' && $sni !== '' && !is_host_like($sni)) return false;

    return true;
}

function validate_trojan(string $url): bool {
    $url = sanitize_link($url);
    $p = @parse_url($url);
    if (!$p || strtolower($p['scheme'] ?? '') !== 'trojan') return false;

    if (empty($p['user']) || strlen((string)$p['user']) < 4) return false;
    if (empty($p['host']) || !is_host_like($p['host'])) return false;
    if (empty($p['port']) || !is_port($p['port'])) return false;

    parse_str($p['query'] ?? '', $q);
    if (!empty($q['sni']) && !is_host_like((string)$q['sni'])) return false;

    return true;
}

// ================= SS (FIXED) =================
function ss_parse(string $url): ?array {
    $url = sanitize_link($url);
    if (stripos($url, 'ss://') !== 0) return null;

    $raw = substr($url, 5);

    // Fragment
    $frag = '';
    if (strpos($raw, '#') !== false) {
        [$raw, $frag] = explode('#', $raw, 2);
    }

    // Query
    $query = '';
    if (strpos($raw, '?') !== false) {
        [$raw, $query] = explode('?', $raw, 2);
    }

    $raw = trim($raw);
    if ($raw === '') return null;

    // A) method:pass@host:port
    $raw_dec = safe_urldecode_once($raw);
    if (strpos($raw_dec, '@') !== false && strpos($raw_dec, ':') !== false) {
        $p = @parse_url('ss://' . $raw_dec);
        if ($p && !empty($p['user']) && isset($p['pass']) && !empty($p['host']) && !empty($p['port'])) {
            return [
                'method' => (string)$p['user'],
                'pass'   => (string)$p['pass'],
                'host'   => (string)$p['host'],
                'port'   => (string)$p['port'],
                'query'  => $query,
                'frag'   => $frag,
            ];
        }
    }

    // B) SIP002: b64(method:pass)@host:port  (✅ handles %3D)
    if (strpos($raw, '@') !== false) {
        [$b64, $hp] = explode('@', $raw, 2);

        // ✅ FIX: base64 may include %3D (encoded '=')
        $b64 = safe_urldecode_once($b64);

        $dec = base64_decode(base64url_to_base64($b64), true);
        if ($dec !== false && preg_match('/^([^:]+):(.+)$/', $dec, $mm)) {
            $hp = safe_urldecode_once($hp);
            if (preg_match('/^(\[[0-9a-f:]+\]|[^:]+):(\d{1,5})$/i', $hp, $mh)) {
                return [
                    'method' => (string)$mm[1],
                    'pass'   => (string)$mm[2],
                    'host'   => (string)$mh[1],
                    'port'   => (string)$mh[2],
                    'query'  => $query,
                    'frag'   => $frag,
                ];
            }
        }
    }

    // C) legacy: b64(method:pass@host:port) (✅ also decode %3D)
    $raw_b64 = safe_urldecode_once($raw);
    $dec = base64_decode(base64url_to_base64($raw_b64), true);
    if ($dec !== false) {
        if (preg_match('/^([^:]+):(.+)@(\[[0-9a-f:]+\]|[^:]+):(\d{1,5})$/i', $dec, $m)) {
            return [
                'method' => (string)$m[1],
                'pass'   => (string)$m[2],
                'host'   => (string)$m[3],
                'port'   => (string)$m[4],
                'query'  => $query,
                'frag'   => $frag,
            ];
        }
    }

    return null;
}

function ss_validate_parsed(?array $x): bool {
    if (!$x) return false;
    if (!is_host_like((string)($x['host'] ?? ''))) return false;
    if (!is_port((string)($x['port'] ?? ''))) return false;
    if (strlen((string)($x['method'] ?? '')) < 2) return false;
    if (strlen((string)($x['pass'] ?? '')) < 1) return false;
    return true;
}

function ss_build_sip002(string $method, string $pass, string $host, string $port, string $query = '', string $frag = ''): string {
    $userinfo_b64 = rtrim(strtr(base64_encode($method . ':' . $pass), '+/', '-_'), '=');
    $out = "ss://{$userinfo_b64}@{$host}:{$port}";
    if ($query !== '') $out .= '?' . $query;
    if ($frag !== '')  $out .= '#' . $frag;
    return $out;
}

function validate_ss(string $url): bool {
    return ss_validate_parsed(ss_parse($url));
}

// ---------- Hysteria 1 ----------
function validate_hysteria1(string $url): bool {
    $url = sanitize_link($url);
    $p = @parse_url($url);
    if (!$p || strtolower($p['scheme'] ?? '') !== 'hysteria') return false;
    if (empty($p['host']) || !is_host_like($p['host'])) return false;
    if (empty($p['port']) || !is_port($p['port'])) return false;
    parse_str($p['query'] ?? '', $q);
    if (!empty($q['sni']) && !is_host_like((string)$q['sni'])) return false;
    return true;
}

// ---------- Hy2 ----------
function validate_hy2(string $url): bool {
    $url = sanitize_link($url);
    $p = @parse_url($url);
    if (!$p || strtolower($p['scheme'] ?? '') !== 'hy2') return false;
    if (empty($p['host']) || !is_host_like($p['host'])) return false;
    if (empty($p['port']) || !is_port($p['port'])) return false;
    parse_str($p['query'] ?? '', $q);
    if (!empty($q['sni']) && !is_host_like((string)$q['sni'])) return false;
    return true;
}

// ---------- hysteria2:// alias ----------
function validate_hysteria2_alias(string $url): bool {
    $url = sanitize_link($url);
    $p = @parse_url($url);
    if (!$p || strtolower($p['scheme'] ?? '') !== 'hysteria2') return false;
    if (empty($p['host']) || !is_host_like($p['host'])) return false;
    if (empty($p['port']) || !is_port($p['port'])) return false;
    parse_str($p['query'] ?? '', $q);
    if (!empty($q['sni']) && !is_host_like((string)$q['sni'])) return false;
    return true;
}

function validate_by_scheme(string $url): bool {
    $url = sanitize_link($url);
    $scheme = strtolower(parse_url($url, PHP_URL_SCHEME) ?? '');
    return match ($scheme) {
        'vless'     => validate_vless($url),
        'vmess'     => validate_vmess($url),
        'trojan'    => validate_trojan($url),
        'ss'        => validate_ss($url),
        'hysteria'  => validate_hysteria1($url),
        'hy2'       => validate_hy2($url),
        'hysteria2' => validate_hysteria2_alias($url),
        default     => false,
    };
}

// ================= Extractors =================
function extractLinkTokensPrecise(string $text): array {
    $out = [];

    // 1) Line-by-line (best for cache files)
    $lines = preg_split("/\R/u", $text);
    foreach ($lines as $line) {
        $line = trim($line);
        if ($line === '') continue;

        if (preg_match('/^(vless|vmess|trojan|ss|hysteria|hy2|hysteria2):\/\/.+/i', $line)) {
            $out[] = sanitize_link($line);
        }
    }

    // 2) Fallback: regex in blobs
    $re = '/(?<![A-Za-z0-9_])(?P<scheme>vless|vmess|trojan|ss|hysteria|hy2|hysteria2):\/\/(?P<body>[^\s<>"\'`]+)/i';
    if (preg_match_all($re, $text, $m, PREG_SET_ORDER)) {
        foreach ($m as $one) {
            $scheme = strtolower($one['scheme']);
            $full = $scheme . '://' . $one['body'];
            $out[] = sanitize_link($full);
        }
    }

    return array_values(array_unique($out));
}

// ---- JSON detection (full configs) ----
function isV2rayNgFullConfig($decoded): bool {
    if (!is_array($decoded)) return false;
    $isAssoc = array_keys($decoded) !== range(0, count($decoded) - 1);
    if (!$isAssoc) return false;

    if (!isset($decoded['outbounds']) || !is_array($decoded['outbounds']) || count($decoded['outbounds']) < 1) return false;

    $hasValidOutbound = false;
    foreach ($decoded['outbounds'] as $ob) {
        if (!is_array($ob)) continue;
        $p = strtolower((string)($ob['protocol'] ?? ''));
        if ($p === '') continue;
        if (in_array($p, [
            'vless','vmess','trojan','shadowsocks','ss','hysteria','hy2','hysteria2',
            'socks','http','wireguard','freedom','blackhole','dns','loopback'
        ], true)) { $hasValidOutbound = true; break; }
    }
    if (!$hasValidOutbound) return false;

    $hasCore = (
        (isset($decoded['inbounds']) && is_array($decoded['inbounds']) && count($decoded['inbounds']) > 0) ||
        isset($decoded['routing']) || isset($decoded['dns']) || isset($decoded['log']) ||
        isset($decoded['policy']) || isset($decoded['api']) || isset($decoded['stats'])
    );

    return $hasCore;
}

function extractJsonConfigsRawFull(string $text): array {
    $results = [];
    $len = strlen($text);
    $i = 0;

    while ($i < $len) {
        $ch = $text[$i];
        if ($ch === '{' || $ch === '[') {
            $start = $i;
            $depth = 0;
            $inString = false;
            $escape = false;

            for (; $i < $len; $i++) {
                $c = $text[$i];

                if ($inString) {
                    if ($escape) $escape = false;
                    else {
                        if ($c === '\\') $escape = true;
                        elseif ($c === '"') $inString = false;
                    }
                } else {
                    if ($c === '"') $inString = true;
                    elseif ($c === '{' || $c === '[') $depth++;
                    elseif ($c === '}' || $c === ']') {
                        $depth--;
                        if ($depth === 0) {
                            $jsonStr = substr($text, $start, $i - $start + 1);
                            $decoded = json_decode($jsonStr, true);
                            if ($decoded !== null && json_last_error() === JSON_ERROR_NONE) {
                                $ok = false;
                                if (isV2rayNgFullConfig($decoded)) $ok = true;
                                else if (is_array($decoded) && array_keys($decoded) === range(0, count($decoded) - 1)) {
                                    foreach ($decoded as $item) {
                                        if (isV2rayNgFullConfig($item)) { $ok = true; break; }
                                    }
                                }
                                if ($ok) $results[] = $jsonStr;
                            }
                            break;
                        }
                    }
                }
            }
        }
        $i++;
    }

    return array_values(array_unique($results));
}

// ================= Collect + Dedupe =================
$configs = [];

// Link configs
foreach (extractLinkTokensPrecise($response) as $u) {
    $u = sanitize_link($u);
    if (!validate_by_scheme($u)) continue;
    $configs[] = renameLinkConfig($u);
}

// JSON configs
foreach (extractJsonConfigsRawFull($response) as $rawJson) {
    $configs[] = renameJsonConfig($rawJson);
}

if (empty($configs)) {
    header('Content-Type: text/plain; charset=UTF-8');
    die("هیچ کانفیگ معتبر پیدا نشد");
}

// Dedupe by SHA256
$unique = [];
foreach ($configs as $c) {
    $h = hash('sha256', $c);
    $unique[$h] = $c;
}
$configs = array_values($unique);

// ================= OUTPUT: ONLY ONE (equal probability) =================
$selected = $configs[random_int(0, count($configs) - 1)];

header('Content-Type: text/plain; charset=UTF-8');
echo $selected;