#!/usr/bin/env php
<?php
/**
 * 超级外链 CLI Runner — 供宝塔计划任务调用
 * 每次执行处理一批外链，自动轮转所有已发布站点域名
 * 用法: php /path/to/admin/cli/seo-backlinks-runner-cli.php
 */

if (php_sapi_name() !== 'cli') {
    echo "此脚本仅限 CLI 模式运行。\n";
    exit(1);
}

$_SERVER['REQUEST_METHOD'] = 'CLI';
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
$_SERVER['HTTP_HOST'] = 'localhost';
$_SERVER['REQUEST_URI'] = '/admin/cli/seo-backlinks-runner-cli.php';
if (!isset($_SERVER['SERVER_NAME'])) {
    $_SERVER['SERVER_NAME'] = 'localhost';
}

umask(0002);

require __DIR__ . '/../../app/bootstrap.php';
require __DIR__ . '/../../app/database.php';

session_write_close();
ignore_user_abort(true);
@set_time_limit(0);

$runtimeDir = __DIR__ . '/../../runtime';
if (!is_dir($runtimeDir)) {
    @mkdir($runtimeDir, 0755, true);
}
$stateFile = $runtimeDir . '/seo-backlinks-state.json';
$logFile = $runtimeDir . '/seo-backlinks.log';
$lockFile = $runtimeDir . '/seo-backlinks.lock';

function bl_log($msg) {
    global $logFile;
    $line = '[' . date('Y-m-d H:i:s') . '] ' . $msg . "\n";
    @file_put_contents($logFile, $line, FILE_APPEND | LOCK_EX);
    echo $line;
}

function bl_read_state() {
    global $stateFile;
    if (!is_file($stateFile)) {
        return null;
    }
    $json = @file_get_contents($stateFile);
    if (!$json) return null;
    $data = json_decode($json, true);
    return is_array($data) ? $data : null;
}

function bl_save_state($state) {
    global $stateFile;
    @file_put_contents($stateFile, json_encode($state, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX);
}

// 文件锁防并发
$lockFp = @fopen($lockFile, 'c');
if ($lockFp && !flock($lockFp, LOCK_EX | LOCK_NB)) {
    bl_log('--- Backlinks Runner skipped (another instance running).');
    fclose($lockFp);
    exit(0);
}

bl_log('--- Backlinks Runner called.');

// 读取任务状态
$state = bl_read_state();

if (!$state || !isset($state['status']) || $state['status'] !== 'running') {
    bl_log('  Task not running (status=' . ($state['status'] ?? 'none') . '). Exiting.');
    if ($lockFp) { flock($lockFp, LOCK_UN); fclose($lockFp); }
    exit(0);
}

// 读取外链库
$linksFile = BASE_PATH . '/data/seo-links.txt';
if (!is_file($linksFile)) {
    bl_log('  ERROR: seo-links.txt not found.');
    if ($lockFp) { flock($lockFp, LOCK_UN); fclose($lockFp); }
    exit(1);
}

$allUrls = file($linksFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$totalLinks = count($allUrls);

if ($totalLinks === 0) {
    bl_log('  ERROR: seo-links.txt is empty.');
    if ($lockFp) { flock($lockFp, LOCK_UN); fclose($lockFp); }
    exit(1);
}

// 获取域名列表
$domains = isset($state['domains']) && is_array($state['domains']) ? $state['domains'] : array();
if (empty($domains)) {
    bl_log('  ERROR: No domains in task state.');
    $state['status'] = 'completed';
    $state['finished_at'] = date('Y-m-d H:i:s');
    bl_save_state($state);
    if ($lockFp) { flock($lockFp, LOCK_UN); fclose($lockFp); }
    exit(0);
}

$batchSize = isset($state['batch_size']) ? max(1, min(30, (int)$state['batch_size'])) : 15;
$currentDomainIdx = isset($state['current_domain_idx']) ? (int)$state['current_domain_idx'] : 0;
$currentPage = isset($state['current_page']) ? (int)$state['current_page'] : 0;
$totalSent = isset($state['total_sent']) ? (int)$state['total_sent'] : 0;

$maxPage = (int)ceil($totalLinks / $batchSize);

// 检查是否全部完成
if ($currentDomainIdx >= count($domains)) {
    bl_log('  All domains completed! Total sent: ' . $totalSent);
    $state['status'] = 'completed';
    $state['finished_at'] = date('Y-m-d H:i:s');
    bl_save_state($state);
    if ($lockFp) { flock($lockFp, LOCK_UN); fclose($lockFp); }
    exit(0);
}

$domain = $domains[$currentDomainIdx];
bl_log('  Processing domain [' . ($currentDomainIdx + 1) . '/' . count($domains) . ']: ' . $domain . ' page=' . $currentPage . '/' . $maxPage);

// 获取当前批次 URL
$offset = $currentPage * $batchSize;
$batch = array_slice($allUrls, $offset, $batchSize);

$sentThisBatch = 0;
$successCount = 0;
$failCount = 0;

foreach ($batch as $urlTemplate) {
    $urlTemplate = trim($urlTemplate);
    if ($urlTemplate === '') continue;

    $targetUrl = str_replace('***', $domain, $urlTemplate);
    $sentThisBatch++;
    $totalSent++;

    // 发送 HTTP GET 请求
    $ch = curl_init();
    curl_setopt_array($ch, array(
        CURLOPT_URL => $targetUrl,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 3,
        CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
        CURLOPT_SSL_VERIFYPEER => false,
        CURLOPT_SSL_VERIFYHOST => 0,
    ));
    curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode >= 200 && $httpCode < 400) {
        $successCount++;
    } else {
        $failCount++;
    }
}

bl_log('  Batch done: sent=' . $sentThisBatch . ' success=' . $successCount . ' fail=' . $failCount);

// 更新状态
$currentPage++;
if ($currentPage >= $maxPage) {
    // 当前域名完成，移到下一个
    bl_log('  Domain "' . $domain . '" completed. Moving to next.');
    $currentDomainIdx++;
    $currentPage = 0;
}

$state['current_domain_idx'] = $currentDomainIdx;
$state['current_page'] = $currentPage;
$state['total_sent'] = $totalSent;
$state['last_run_at'] = date('Y-m-d H:i:s');

// 记录执行日志
if (!isset($state['logs']) || !is_array($state['logs'])) {
    $state['logs'] = array();
}
$state['logs'][] = array(
    'time' => date('Y-m-d H:i:s'),
    'domain' => $domain,
    'page' => $currentPage,
    'sent' => $sentThisBatch,
    'success' => $successCount,
    'fail' => $failCount
);
// 只保留最近 100 条日志
if (count($state['logs']) > 100) {
    $state['logs'] = array_slice($state['logs'], -100);
}

// 检查是否全部完成
if ($currentDomainIdx >= count($domains)) {
    $state['status'] = 'completed';
    $state['finished_at'] = date('Y-m-d H:i:s');
    bl_log('  All domains completed! Total sent: ' . $totalSent);
}

bl_save_state($state);

if ($lockFp) {
    flock($lockFp, LOCK_UN);
    fclose($lockFp);
}
bl_log('--- Backlinks Runner finished.');
