<?php
// ============================================================
// worker.php — Background APK Protection Worker (With Points)
// ============================================================
// Args: chat_id | apk_path | caption_b64 | name_b64 | job_id_b64

if ($argc < 6) die("Usage: php worker.php <chat_id> <apk_path> <caption_b64> <name_b64> <job_id_b64>\n");

$chatId    = (int)$argv[1];
$localApk  = $argv[2];
$caption   = base64_decode($argv[3]);
$origName  = base64_decode($argv[4]);
$jobId     = (int)base64_decode($argv[5]);

require_once __DIR__ . '/config.php';
require_once __DIR__ . '/points.php';
require_once __DIR__ . '/fonts.php';

$BASE    = DEX_BASE;
$safeId  = preg_replace('/[^a-zA-Z0-9_\-]/', '', $chatId);
$cookieF = TEMP_DIR . '/cookie_' . md5($chatId . time()) . '.txt';
$flagF   = TEMP_DIR . '/hb_' . $safeId . '.lock';
$startTs = time();
$jobDone = false; // Track completion to avoid double refund in shutdown

// ── Job timeout watchdog ─────────────────────────────────────
// If script exits unexpectedly AND job not finished → refund
register_shutdown_function(function() use ($jobId, $localApk, $cookieF, $flagF, &$jobDone) {
    if (!$jobDone) {
        // Only refund if job was never marked done (unexpected exit)
        global $JOBS_FILE;
        $jobs = readJSON($JOBS_FILE ?? DATA_DIR . '/jobs.json');
        if (isset($jobs[$jobId]) && $jobs[$jobId]['status'] === 'processing') {
            refundPoints($jobId, 'unexpected_exit');
        }
    }
    cleanup($localApk, $cookieF, $flagF);
});

// ────────────────────────────────────────────────────────────
//  Font-aware APK renaming helper
// ────────────────────────────────────────────────────────────
function buildOutputName(string $origName): string {
    // Try to detect font keywords in the filename and match known fonts
    $fontFile = matchFont($origName);
    if ($fontFile) {
        $fontBase = pathinfo($fontFile, PATHINFO_FILENAME);
        $safeName = preg_replace('/[^a-zA-Z0-9._\-]/', '_', $origName);
        if (strtolower(substr($safeName, -4)) !== '.apk') $safeName .= '.apk';
        return $fontBase . '_' . $safeName;
    }
    $safeName = preg_replace('/[^a-zA-Z0-9._\-]/', '_', $origName);
    if (strtolower(substr($safeName, -4)) !== '.apk') $safeName .= '.apk';
    return $safeName;
}

sendMessage($chatId, "⚙️ Worker started! Connecting to protection server...");

// ─────────────────────────────────────────────────────────
// STEP 1: Get session
// ─────────────────────────────────────────────────────────
sendMessage($chatId, "🌐 Step 1/3: Getting session...");
$r1 = dexGet($BASE . '/', $cookieF);
if ($r1['http_code'] != 200) {
    sendMessage($chatId, "❌ Cannot reach protection server. Refunding points...");
    refundPoints($jobId, 'server_unreachable');
    cleanup($localApk, $cookieF, $flagF);
    exit;
}

// ─────────────────────────────────────────────────────────
// STEP 2: Login
// ─────────────────────────────────────────────────────────
sendMessage($chatId, "🔑 Step 2/3: Logging in...");
dexGet($BASE . '/dex/login', $cookieF);
$loginData = http_build_query(['user' => DEX_USER, 'pass' => DEX_PASS, 'language' => 'en_US']);
$r2 = dexPost($BASE . '/dex/login', $loginData, 'application/x-www-form-urlencoded', $BASE . '/dex/login', $cookieF);
$loginOk = (strpos($r2['final_url'], '/login') === false) && in_array($r2['http_code'], [200, 302]);
if (!$loginOk) {
    sendMessage($chatId, "❌ Login failed. Refunding points...");
    refundPoints($jobId, 'login_failed');
    cleanup($localApk, $cookieF, $flagF);
    exit;
}

sendMessage($chatId, "✅ Login successful!\n\n📤 Step 3/3: Uploading APK...\n⏳ Please wait 2–5 minutes. I'll ping you every minute!");

// ── Start heartbeat ──────────────────────────────────────────
file_put_contents($flagF, time());
$hbScript = escapeshellarg(__DIR__ . '/heartbeat.php');
$php       = escapeshellarg(PHP_BINARY);
$hbArgs    = escapeshellarg($chatId) . ' ' . escapeshellarg(BOT_TOKEN) . ' ' . escapeshellarg($flagF);
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
    pclose(popen("start /B $php $hbScript $hbArgs", "r"));
} else {
    exec("$php $hbScript $hbArgs > /dev/null 2>&1 &");
}

// ─────────────────────────────────────────────────────────
// STEP 3: Upload APK
// ─────────────────────────────────────────────────────────
// ── dexshellx.com form fields (verified via live test June 2025) ──
// $caption holds the filter text (e.g. "!/\ncom/\n_COROUTINE/\nteacher/**")
// This is sent as stringFilters / hideFilters / classFilters to exclude
// specific packages from being obfuscated/encrypted.
$cFile = new CURLFile($localApk, 'application/vnd.android.package-archive', basename($localApk));
$postData = [
    // File
    'apkFile'            => $cFile,
    // Mode & Signature
    'mode'               => 'standard',
    'userModeValue'      => 'false',
    'signatureAlias'     => 'android',
    // ── FILTER EXCLUSIONS (from user caption / DEFAULT_FILTER_TEXT) ──
    'stringFilters'      => $caption,   // String Encryption exclusions
    'hideFilters'        => $caption,   // Hide Access exclusions
    'classFilters'       => $caption,   // Class Encryption exclusions
    // Code Protection
    'optimize'           => 'on',
    'stripLogging'       => 'on',
    'crashHandler'       => 'on',
    'webViewSupport'     => 'on',
    'manifestMangling'   => 'on',
    'assets'             => 'on',
    'res'                => 'on',
    'nameObf'            => 'on',
    'root'               => 'on',
    'strings'            => 'on',
    'annotationEnc'      => 'on',
    'stringEnc'          => 'on',
    'hideAccess'         => 'on',
    'classEnc'           => 'on',
    'jniObf'             => 'on',
    'nativeLib'          => 'on',
    // Anti-Tamper / Runtime Checks
    'antiAssinatura'     => 'on',
    'antiEmulator'       => 'on',
    'antiXposed'         => 'on',
    'antiManual'         => 'on',
    'antiDev'            => 'on',
    'uiProtection'       => 'on',
    'runtimeChecks'      => 'on',
];

$ch = curl_init($BASE . '/dex/protection/options');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $postData,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_MAXREDIRS      => 10,
    CURLOPT_TIMEOUT        => 300,
    CURLOPT_ENCODING       => '',
    CURLOPT_COOKIEFILE     => $cookieF,
    CURLOPT_COOKIEJAR      => $cookieF,
    CURLOPT_SSL_VERIFYPEER => false,
    CURLOPT_SSL_VERIFYHOST => false,
    CURLOPT_HTTPHEADER     => [
        'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
        'Accept: text/html,application/xhtml+xml,*/*;q=0.8',
        'Origin: ' . $BASE,
        'Referer: ' . $BASE . '/dex/protection/options?mode=standard',
    ],
]);
$uploadBody = curl_exec($ch);
$uploadCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$uploadUrl  = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
curl_close($ch);

// Extract process ID
preg_match('/process\/detail\?id=(\d+)/', $uploadUrl, $m);
if (!$m) preg_match('/data-process-id=["\'](\d+)["\']/', $uploadBody, $m);
if (!$m) preg_match('/<input[^>]+name=["\']processId["\'][^>]+value=["\'](\d+)["\']/', $uploadBody, $m);
if (!$m) {
    $jr = @json_decode($uploadBody, true);
    if ($jr && isset($jr['id']))        $m = [0, $jr['id']];
    elseif ($jr && isset($jr['processId'])) $m = [0, $jr['processId']];
}
$processId = $m[1] ?? null;

if (!$processId) {
    if (file_exists($flagF)) unlink($flagF);
    sendMessage($chatId, "❌ Upload failed — could not get process ID.\n⚠️ Points will be refunded.");
    refundPoints($jobId, 'upload_failed');
    cleanup($localApk, $cookieF, $flagF);
    exit;
}

// Update job with process ID
global $JOBS_FILE;
$_jobs = readJSON(DATA_DIR . '/jobs.json');
if (isset($_jobs[$jobId])) {
    $_jobs[$jobId]['process_id'] = $processId;
    writeJSON(DATA_DIR . '/jobs.json', $_jobs);
}

sendMessage($chatId, "📋 APK uploaded! Process ID: #$processId\n⚙️ Protection running on server...");

// ─────────────────────────────────────────────────────────
// STEP 4: Poll status (max JOB_TIMEOUT_MIN minutes)
// ─────────────────────────────────────────────────────────
$pollUrl   = $BASE . '/dex/process/detail/data?id=' . $processId;
$detailUrl = $BASE . '/dex/process/detail?id='      . $processId;
$maxWait   = JOB_TIMEOUT_MIN * 60;
$interval  = 15;
$waited    = 0;
$status    = 'RUNNING';

while ($waited < $maxWait) {
    sleep($interval);
    $waited += $interval;

    $pr   = dexGet($pollUrl, $cookieF, $detailUrl, true);
    $json = @json_decode($pr['body'], true);
    if ($json && isset($json['status'])) {
        $status = strtoupper($json['status']);
    } else {
        preg_match('/data-status=["\']([^"\']+)["\']/', $pr['body'], $sm);
        if ($sm) $status = strtoupper($sm[1]);
    }

    if (in_array($status, ['DONE', 'SUCCESS', 'FINISHED', 'COMPLETED'])) break;
    if (in_array($status, ['ERROR', 'FAILED', 'FAILURE'])) {
        if (file_exists($flagF)) unlink($flagF);
        sendMessage($chatId, "❌ Protection failed on server.\n↩️ Points refunded.");
        refundPoints($jobId, 'protection_failed');
        cleanup($localApk, $cookieF, $flagF);
        exit;
    }
}

if (file_exists($flagF)) unlink($flagF);

if (!in_array($status, ['DONE', 'SUCCESS', 'FINISHED', 'COMPLETED'])) {
    sendMessage($chatId, "⏰ Timeout after " . JOB_TIMEOUT_MIN . " min.\n↩️ Points refunded automatically.");
    refundPoints($jobId, 'timeout');
    cleanup($localApk, $cookieF, $flagF);
    exit;
}

// SUCCESS - Deduct points immediately as compilation is completed
$jobDone = true;
deductPoints($jobId);

sendMessage($chatId, "🛡️ Protection complete in {$waited}s!\n⬇️ Downloading protected APK...");

try {
    $detailR = dexGet($detailUrl, $cookieF);
    preg_match('/href=["\']([^"\']*process\/download\?id=' . $processId . '[^"\']*type=apk[^"\']*)["\']/', $detailR['body'], $dlM);
    if (!$dlM) {
        throw new Exception("Download link not found on detail page.");
    }

    $downloadUrl = $BASE . html_entity_decode($dlM[1]);
    $ch = curl_init($downloadUrl);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_TIMEOUT        => 300,
        CURLOPT_ENCODING       => '',
        CURLOPT_COOKIEFILE     => $cookieF,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_HTTPHEADER     => [
            'User-Agent: Mozilla/5.0',
            'Accept: application/octet-stream,*/*',
            'Referer: ' . $detailUrl,
        ],
    ]);
    $apkData = curl_exec($ch);
    $dlCode  = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $dlType  = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    curl_close($ch);

    $isApk = (
        strpos($dlType, 'package-archive') !== false ||
        strpos($dlType, 'octet-stream')    !== false ||
        strpos($dlType, 'application/zip')    !== false
    );

    if ($dlCode != 200 || !$isApk || strlen($apkData) < 10000) {
        throw new Exception("Invalid file download (HTTP Code: $dlCode, Content-Type: $dlType, Size: " . strlen($apkData) . " bytes)");
    }

    $outName = buildOutputName($origName);
    $outFile = TEMP_DIR . '/' . $outName;
    file_put_contents($outFile, $apkData);

    // Save to secure history cache
    saveToProtectedCache($outFile, $jobId);

    $sizeKb = round(strlen($apkData) / 1024);
    sendMessage($chatId, "🛡️ Sending protected APK ({$sizeKb} KB)...");
    
    // Send to Telegram
    sendDocument($chatId, $outFile);
    @unlink($outFile);

    $remaining = getAvailablePoints($chatId); // $chatId == user's telegram_id in worker
    sendMessage($chatId, "✅ Done! Protected APK sent.\n\n💰 *" . POINTS_PER_JOB . " points permanently deducted from hold.*\n📉 Available balance: *$remaining pts*");

} catch (Exception $e) {
    // Log the error
    file_put_contents(TEMP_DIR . '/bot_error.log', sanitizeText(date('Y-m-d H:i:s') . " - Job #$jobId Download/Send Error: " . $e->getMessage() . "\n"), FILE_APPEND);
    
    // Notify the user about the history download fallback
    sendMessage($chatId,
        "⚠️ *Telegram Delivery Failed*\n\n" .
        "Your APK was successfully protected, but the download/delivery to Telegram failed due to connection issues.\n\n" .
        "💰 *" . POINTS_PER_JOB . " points deducted.*\n\n" .
        "📲 You can download your protected APK directly from your App History using Job ID: `#$jobId`."
    );
}

cleanup($localApk, $cookieF, $flagF);

// ─────────────────────────────────────────────────────────
// HELPER FUNCTIONS
// ─────────────────────────────────────────────────────────

function dexGet(string $url, string $cookieF, string $referer = '', bool $json = false): array {
    $headers = [
        'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
        'Accept-Language: en-US,en;q=0.9',
        'Connection: keep-alive',
    ];
    $headers[] = $json ? 'Accept: application/json, */*' : 'Accept: text/html,*/*';
    if ($referer) $headers[] = 'Referer: ' . $referer;
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 10,
        CURLOPT_TIMEOUT        => 60,
        CURLOPT_ENCODING       => '',
        CURLOPT_COOKIEFILE     => $cookieF,
        CURLOPT_COOKIEJAR      => $cookieF,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_HTTPHEADER     => $headers,
    ]);
    $body      = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $final_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
    curl_close($ch);
    return ['body' => $body, 'http_code' => $http_code, 'final_url' => $final_url];
}

function dexPost(string $url, string $postData, string $contentType, string $referer, string $cookieF): array {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $postData,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS      => 10,
        CURLOPT_TIMEOUT        => 60,
        CURLOPT_ENCODING       => '',
        CURLOPT_COOKIEFILE     => $cookieF,
        CURLOPT_COOKIEJAR      => $cookieF,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_HTTPHEADER     => [
            'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
            'Accept: text/html,*/*',
            'Content-Type: ' . $contentType,
            'Origin: ' . DEX_BASE,
            'Referer: ' . $referer,
        ],
    ]);
    $body      = curl_exec($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $final_url = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
    curl_close($ch);
    return ['body' => $body, 'http_code' => $http_code, 'final_url' => $final_url];
}

function sendMessage(int $chatId, string $text): void {
    $text = sanitizeText($text);
    $ch = curl_init(BOT_API . "/sendMessage");
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => ['chat_id' => $chatId, 'text' => $text, 'parse_mode' => 'Markdown'],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_TIMEOUT        => 30,
    ]);
    curl_exec($ch);
    curl_close($ch);
}

function sendDocument(int $chatId, string $filePath): void {
    $ch = curl_init(BOT_API . "/sendDocument");
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => ['chat_id' => $chatId, 'document' => new CURLFile($filePath)],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_TIMEOUT        => 300,
    ]);
    curl_exec($ch);
    curl_close($ch);
}

function cleanup(string $apk, string $cookie, string $flag = ''): void {
    if (file_exists($apk))              @unlink($apk);
    if (file_exists($cookie))           @unlink($cookie);
    if ($flag && file_exists($flag))    @unlink($flag);
}
