#!/usr/bin/env php
<?php
declare(strict_types=1);

/**
 * SERP background worker.
 * Invoked by SerpApiController::startSerpJob() via exec/popen.
 * Usage: php serp-worker.php <job_id>
 */

require_once dirname(__DIR__) . '/bootstrap.php';

use Crawl\Lib\Database;

set_time_limit(0);

$jobId = $argv[1] ?? '';
if (!$jobId) {
    fwrite(STDERR, "Usage: php serp-worker.php <job_id>\n");
    exit(1);
}

$db = Database::getInstance();

$job = $db->fetch('SELECT * FROM serp_jobs WHERE job_id = ?', [$jobId]);
if (!$job) {
    fwrite(STDERR, "Job not found: $jobId\n");
    exit(1);
}

$setStatus = function (string $status, int $pct, string $msg) use ($db, $jobId): void {
    $db->update(
        'serp_jobs',
        ['status' => $status, 'percent' => $pct, 'message' => $msg],
        'job_id = :jid',
        ['jid' => $jobId]
    );
};

$setProgress = function (int $pct, string $msg) use ($db, $jobId): void {
    $db->update(
        'serp_jobs',
        ['percent' => $pct, 'message' => $msg],
        'job_id = :jid',
        ['jid' => $jobId]
    );
};

$setStatus('processing', 2, 'Starting...');

$submission = $db->fetch(
    'SELECT * FROM serp_submissions WHERE TRIM(submission_id) = ?',
    [trim($job['submission_id'])]
);
if (!$submission) {
    $setStatus('failed', 100, 'Submission not found');
    exit(1);
}

$sbApiKey = $_ENV['SCRAPINGBEE_API_KEY'] ?? '';
if (!$sbApiKey) {
    $setStatus('failed', 100, 'SCRAPINGBEE_API_KEY not configured in .env');
    exit(1);
}

$deviceType  = $job['device'];
$cityName    = $job['city_name'];
$countryCode = $job['country_code'];
$searchType  = $job['search_type'];
$weburl      = $submission['weburl'];

$keywordsList  = array_values(array_filter(array_map('trim', explode("\n", $submission['keywords']))));
$totalKeywords = count($keywordsList);

if ($totalKeywords === 0) {
    $setStatus('failed', 100, 'No keywords configured for this submission');
    exit(1);
}

$setProgress(2, "Starting SERP check for $totalKeywords keyword(s)...");

$results         = [];
$processed       = 0;
$maxConcurrency  = $totalKeywords <= 20 ? 2 : ($totalKeywords <= 100 ? 4 : 6);
$retryLimit      = 3;
$retriesTracker  = [];
$pageTracker     = array_fill_keys($keywordsList, 1);
$pendingKeywords = array_values($keywordsList);
$stopAll         = false;
$errorMessage    = '';
$lastDbUpdate    = 0;

$mh            = curl_multi_init();
$activeHandles = [];
$handleMap     = [];

try {
    while ($processed < $totalKeywords && !$stopAll) {
        while (count($activeHandles) < $maxConcurrency && count($pendingKeywords) > 0) {
            $kw   = array_shift($pendingKeywords);
            $page = $pageTracker[$kw] ?? 1;

            if ($searchType === 'fast') {
                $apiUrl = 'https://app.scrapingbee.com/api/v1/fast_search?' . http_build_query([
                    'api_key'      => $sbApiKey,
                    'search'       => $kw,
                    'country_code' => strtolower($countryCode ?: 'in'),
                    'page'         => $page,
                ]);
            } else {
                $apiUrl = 'https://app.scrapingbee.com/api/v1/google?' . http_build_query([
                    'api_key'       => $sbApiKey,
                    'search'        => $kw,
                    'language'      => 'en',
                    'device'        => $deviceType,
                    'nb_results'    => 100,
                    'light_request' => 'false',
                    'country_code'  => strtolower($countryCode ?: 'us'),
                ]);
            }

            $ch = curl_init();
            curl_setopt_array($ch, [
                CURLOPT_URL            => $apiUrl,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT        => 60,
                CURLOPT_FOLLOWLOCATION => true,
            ]);
            curl_multi_add_handle($mh, $ch);

            $chId                 = (int)$ch;
            $activeHandles[$chId] = $ch;
            $handleMap[$chId]     = ['kw' => $kw, 'page' => $page];
            $retriesTracker[$kw]  = ($retriesTracker[$kw] ?? 0) + ($page === 1 ? 1 : 0);
        }

        do {
            $status = curl_multi_exec($mh, $active);
            if ($active) curl_multi_select($mh, 0.1);
        } while ($active && ($status === CURLM_CALL_MULTI_PERFORM || $status === CURLM_OK));

        while ($info = curl_multi_info_read($mh)) {
            $ch       = $info['handle'];
            $chId     = (int)$ch;
            $kwMeta   = $handleMap[$chId];
            $kw       = $kwMeta['kw'];
            $page     = $kwMeta['page'];
            $respBody = curl_multi_getcontent($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

            $found   = false;
            $kwRank  = '-';
            $rankUrl = '';

            if ($httpCode === 200 && $respBody) {
                $resData = json_decode($respBody, true);
                $organic = $resData['organic_results'] ?? [];

                if (count($organic) > 0) {
                    $parsedBase = parse_url((str_starts_with($weburl, 'http') ? '' : 'https://') . $weburl);
                    $cleanBase  = str_ireplace('www.', '', strtolower($parsedBase['host'] ?? $weburl));

                    foreach ($organic as $idx => $item) {
                        $itemUrl    = $item['url'] ?? $item['link'] ?? '';
                        if (!$itemUrl) continue;
                        $parsedItem = parse_url((str_starts_with($itemUrl, 'http') ? '' : 'https://') . $itemUrl);
                        $compareUrl = str_ireplace('www.', '', strtolower($parsedItem['host'] ?? $itemUrl));
                        if (str_contains($compareUrl, $cleanBase) || str_contains($cleanBase, $compareUrl)) {
                            $kwRank  = $item['position'] ?? (($page - 1) * count($organic) + $idx + 1);
                            $rankUrl = $itemUrl;
                            $found   = true;
                            break;
                        }
                    }
                }
            } elseif (in_array($httpCode, [401, 402, 403])) {
                $stopAll      = true;
                $errorMessage = $httpCode === 402
                    ? 'ScrapingBee credits exhausted'
                    : "ScrapingBee API error (HTTP $httpCode — check API key)";
            } elseif ($httpCode === 429) {
                usleep(3_000_000);
                $maxConcurrency = max(1, $maxConcurrency - 1);
            }

            $maxRetriesHit = $searchType === 'normal' && ($retriesTracker[$kw] ?? 0) >= $retryLimit;

            if ($found || $stopAll || $maxRetriesHit) {
                $results[$kw] = ['rank' => $kwRank, 'url' => $rankUrl];
                $processed++;
                $pct = (int)round($processed / $totalKeywords * 95);
                $now = time();
                if ($now - $lastDbUpdate >= 2) {
                    $setProgress($pct, "Processed $processed / $totalKeywords keywords...");
                    $lastDbUpdate = $now;
                }
            } elseif ($searchType === 'fast') {
                if ($page < 10) {
                    $pageTracker[$kw] = $page + 1;
                    array_unshift($pendingKeywords, $kw);
                } else {
                    $results[$kw] = ['rank' => '-', 'url' => ''];
                    $processed++;
                    $pct = (int)round($processed / $totalKeywords * 95);
                    $now = time();
                    if ($now - $lastDbUpdate >= 2) {
                        $setProgress($pct, "Processed $processed / $totalKeywords keywords...");
                        $lastDbUpdate = $now;
                    }
                }
            } else {
                $pendingKeywords[] = $kw;
            }

            curl_multi_remove_handle($mh, $ch);
            curl_close($ch);
            unset($activeHandles[$chId], $handleMap[$chId]);
        }

        if (count($activeHandles) === 0 && count($pendingKeywords) === 0 && $processed < $totalKeywords) {
            break;
        }
    }
} catch (\Exception $e) {
    curl_multi_close($mh);
    $setStatus('failed', 100, 'Error: ' . $e->getMessage());
    exit(1);
}

curl_multi_close($mh);

if ($stopAll) {
    $setStatus('failed', 100, "Error: $errorMessage");
    exit(1);
}

$setProgress(97, 'Saving results to database...');

try {
    $kArr = [];
    $rArr = [];
    $uArr = [];
    foreach ($keywordsList as $kw) {
        $res    = $results[$kw] ?? ['rank' => '-', 'url' => ''];
        $kArr[] = $kw;
        $rArr[] = (string)$res['rank'];
        $uArr[] = (string)$res['url'];
    }

    $db->insert('serp_keyword_ranks', [
        'submission_id' => $job['submission_id'],
        'keyword'       => implode("\n", $kArr),
        'keyword_rank'  => implode("\n", $rArr),
        'url'           => implode("\n", $uArr),
        'device'        => $deviceType,
        'location_name' => $cityName,
        'country_code'  => $countryCode,
    ]);
} catch (\Exception $e) {
    $setStatus('failed', 100, 'Error saving results: ' . $e->getMessage());
    exit(1);
}

$setStatus('done', 100, 'Rankings saved successfully!');
exit(0);
