<?php

namespace App\Support\Integration;

/**
 * 防 SSRF：对主站钱包基地址做严格归一化校验。
 *
 * 规则（保守）：
 * - 仅允许 https
 * - 不允许 user/pass、query、fragment
 * - 不允许除 / 以外的 path（即仅允许根地址）
 * - 拒绝 localhost 与私网/保留网段（IP 字面量层面）
 *
 * 说明：对 hostname 不做 DNS 解析（避免引入不确定性），但会拦截 localhost 及明显内网标识。
 */
final class WalletApiUrlSanitizer
{
    public static function normalizeAndValidate(?string $raw): ?string
    {
        if (! is_string($raw)) {
            return null;
        }

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

        // E2E：允许本地 mock 主站钱包（http://127.0.0.1:port），生产 LOTTERY_E2E 默认 false。
        if ((bool) env('LOTTERY_E2E', false) && app()->environment(['local', 'testing'])) {
            if (preg_match('#^https?://127\.0\.0\.1:\d{1,5}$#', rtrim($raw, '/')) === 1) {
                return rtrim($raw, '/');
            }
        }

        // 允许尾部 /，归一化后移除
        $raw = rtrim($raw, " \t\n\r\0\x0B/");

        $parts = parse_url($raw);
        if (! is_array($parts)) {
            return null;
        }

        $scheme = strtolower((string) ($parts['scheme'] ?? ''));
        if ($scheme !== 'https') {
            return null;
        }

        if (isset($parts['user']) || isset($parts['pass'])) {
            return null;
        }

        if (! isset($parts['host']) || ! is_string($parts['host']) || $parts['host'] === '') {
            return null;
        }

        if (isset($parts['query']) || isset($parts['fragment'])) {
            return null;
        }

        $path = (string) ($parts['path'] ?? '');
        if ($path !== '' && $path !== '/') {
            return null;
        }

        $host = strtolower(trim((string) $parts['host']));
        if ($host === '' || str_contains($host, ' ') || str_contains($host, "\t") || str_contains($host, "\n")) {
            return null;
        }

        // 明确拦截 localhost / 本地常见名
        if ($host === 'localhost' || $host === 'local' || $host === 'localdomain') {
            return null;
        }

        // 拦截 IP 字面量私网
        $isIp = filter_var($host, FILTER_VALIDATE_IP) !== false;
        if ($isIp) {
            if (self::ipIsPrivateOrReserved($host)) {
                return null;
            }
        }

        // 端口校验（允许不写 port）
        if (isset($parts['port'])) {
            $port = (int) $parts['port'];
            if ($port < 1 || $port > 65535) {
                return null;
            }
        }

        $normalized = 'https://'.$host;
        if (isset($parts['port'])) {
            $normalized .= ':'.(string) (int) $parts['port'];
        }

        return $normalized;
    }

    private static function ipIsPrivateOrReserved(string $ip): bool
    {
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            $v = ip2long($ip);
            if ($v === false) {
                return true;
            }

            // PHP 在 macOS 上 ip2long 可能为有符号，强转为 int64 统一处理
            $v = (int) $v;

            return self::ipInRangesV4((int) $v, [
                // 0.0.0.0/8
                ['base' => ip2long('0.0.0.0'), 'mask' => 0xFF000000],
                // 10.0.0.0/8
                ['base' => ip2long('10.0.0.0'), 'mask' => 0xFF000000],
                // 127.0.0.0/8
                ['base' => ip2long('127.0.0.0'), 'mask' => 0xFF000000],
                // 169.254.0.0/16
                ['base' => ip2long('169.254.0.0'), 'mask' => 0xFFFF0000],
                // 172.16.0.0/12
                ['base' => ip2long('172.16.0.0'), 'mask' => 0xFFF00000],
                // 192.168.0.0/16
                ['base' => ip2long('192.168.0.0'), 'mask' => 0xFFFF0000],
                // 100.64.0.0/10 (CGNAT)
                ['base' => ip2long('100.64.0.0'), 'mask' => 0xFFC00000],
                // 192.0.0.0/24 (IETF Protocol Assignments)
                ['base' => ip2long('192.0.0.0'), 'mask' => 0xFFFFFF00],
                // 198.18.0.0/15 (benchmarking)
                ['base' => ip2long('198.18.0.0'), 'mask' => 0xFFFE0000],
                // 224.0.0.0/4 (multicast)
                ['base' => ip2long('224.0.0.0'), 'mask' => 0xF0000000],
                // 240.0.0.0/4 (reserved for future use)
                ['base' => ip2long('240.0.0.0'), 'mask' => 0xF0000000],
            ]);
        }

        // IPv6：仅做关键保守段拦截（避免复杂数值比较引入 bug）
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            $bin = inet_pton($ip);
            if ($bin === false) {
                return true;
            }

            // IPv6 ::1 (loopback)
            if (substr($bin, 0, 15) === str_repeat("\0", 15) && $bin[15] === "\1") {
                return true;
            }

            // IPv6 unspecified ::
            if ($bin === str_repeat("\0", 16)) {
                return true;
            }

            $b0 = ord($bin[0]);
            $b1 = ord($bin[1]);

            // ff00::/8 multicast
            if ($b0 === 0xFF) {
                return true;
            }

            // fc00::/7 unique local => fc or fd
            if ($b0 === 0xFC || $b0 === 0xFD) {
                return true;
            }

            // fe80::/10 link-local => fe + (second byte & 0xC0) == 0x80
            if ($b0 === 0xFE && (($b1 & 0xC0) === 0x80)) {
                return true;
            }

            // IPv4-mapped ::ffff:0:0/96 => 检查最后 4 字节映射的 IPv4 是否为私网
            if (substr($bin, 0, 10) === str_repeat("\0", 10) && substr($bin, 10, 2) === "\xFF\xFF") {
                $v4bin = substr($bin, 12, 4);
                $v4 = inet_ntop($v4bin);
                // inet_ntop 对 v4bin 有时返回 false，这里保守返回 true
                if ($v4 === false) {
                    return true;
                }

                return self::ipIsPrivateOrReserved($v4);
            }
        }

        // 非法 IP：保守拒绝
        return true;
    }

    private static function ipInRangesV4(int $v, array $ranges): bool
    {
        foreach ($ranges as $r) {
            $base = (int) $r['base'];
            $mask = (int) $r['mask'];
            if (($v & $mask) === ($base & $mask)) {
                return true;
            }
        }

        return false;
    }
}

