<?php

declare(strict_types=1);

namespace app\common\service;

/**
 * 短链 HTTP 重定向预解析（与 puppeteer-api/url-resolve.js 行为对齐）
 *
 * 用于 antiBot landingUrlHint；CF 拦截时可能失败，浏览器仍会 follow。
 */
final class SplitPageUrlResolver
{
    private const REDIRECT_CODES = [301, 302, 303, 307, 308];

    /**
     * 跟随 HTTP 重定向，返回最终 URL；失败时返回原始 URL
     */
    public static function resolveFinalUrl(string $pageUrl, int $maxRedirects = 10, int $timeoutSec = 12): string
    {
        $current = trim($pageUrl);
        if ($current === '') {
            return '';
        }

        for ($i = 0; $i < $maxRedirects; $i++) {
            $response = self::fetchHeadOrGet($current, $timeoutSec);
            if ($response === null) {
                return $current;
            }

            $code = $response['code'];
            $location = $response['location'];
            if (!in_array($code, self::REDIRECT_CODES, true) || $location === '') {
                return $current;
            }

            $current = self::resolveRelativeUrl($current, $location);
        }

        return $current;
    }

    /**
     * @return array{code: int, location: string}|null
     */
    private static function fetchHeadOrGet(string $url, int $timeoutSec): ?array
    {
        $result = self::curlOnce($url, true, $timeoutSec);
        if ($result !== null) {
            return $result;
        }

        return self::curlOnce($url, false, $timeoutSec);
    }

    /**
     * @return array{code: int, location: string}|null
     */
    private static function curlOnce(string $url, bool $headOnly, int $timeoutSec): ?array
    {
        if (!function_exists('curl_init')) {
            return null;
        }

        $ch = curl_init($url);
        if ($ch === false) {
            return null;
        }

        curl_setopt_array($ch, [
            CURLOPT_NOBODY         => $headOnly,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => false,
            CURLOPT_TIMEOUT        => $timeoutSec,
            CURLOPT_HEADER         => true,
            CURLOPT_USERAGENT      => 'Mozilla/5.0 (compatible; SplitPageUrlResolver/1.0)',
        ]);

        $body = curl_exec($ch);
        if ($body === false) {
            curl_close($ch);

            return null;
        }

        $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $redirectUrl = (string) curl_getinfo($ch, CURLINFO_REDIRECT_URL);
        curl_close($ch);

        $location = $redirectUrl;
        if ($location === '' && is_string($body)) {
            $location = self::parseLocationHeader($body);
        }

        return ['code' => $code, 'location' => trim($location)];
    }

    private static function parseLocationHeader(string $rawHeaders): string
    {
        foreach (preg_split('/\r\n|\n|\r/', $rawHeaders) as $line) {
            if (stripos($line, 'Location:') === 0) {
                return trim(substr($line, 9));
            }
        }

        return '';
    }

    private static function resolveRelativeUrl(string $baseUrl, string $location): string
    {
        if (preg_match('#^https?://#i', $location)) {
            return $location;
        }

        $base = parse_url($baseUrl);
        if (!is_array($base)) {
            return $location;
        }

        $scheme = (string) ($base['scheme'] ?? 'https');
        $host = (string) ($base['host'] ?? '');
        if ($host === '') {
            return $location;
        }

        if (strpos($location, '/') === 0) {
            return $scheme . '://' . $host . $location;
        }

        $path = (string) ($base['path'] ?? '/');
        $dir = rtrim(str_replace(basename($path), '', $path), '/');

        return $scheme . '://' . $host . ($dir !== '' ? $dir . '/' : '/') . $location;
    }
}
