<?php
declare(strict_types=1);

// 下载入口：仅使用服务器确认的 IP、时间及文件白名单。
function udLogValue(string $value): string
{
    $value = preg_replace('/[\x00-\x1f\x7f|]+/u', ' ', $value) ?? '';
    preg_match('/^.{0,160}/us', trim($value), $match);
    return $match[0] ?? '';
}

function udClientIp(array $server): string
{
    $ip = $server['REMOTE_ADDR'] ?? '';
    if (!is_string($ip) || !filter_var($ip, FILTER_VALIDATE_IP)) return '未知';
    $packed = inet_pton($ip);
    if (strlen($packed) === 16 && substr($packed, 0, 12) === str_repeat("\0", 10) . "\xff\xff") {
        return inet_ntop(substr($packed, 12));
    }
    // X-Forwarded-For 不能由访客自行指定；CDN 的真实 IP 在 Nginx 配置可信代理后还原。
    return inet_ntop($packed);
}

function udLookupLocation(string $ip, ?string $databaseDirectory = null): string
{
    if ($ip === '未知') return '未知';
    if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
        return '内网 / 本机 / 保留地址';
    }
    $directory = $databaseDirectory ?? __DIR__ . '/ip2region';
    $isV4 = strlen(inet_pton($ip)) === 4;
    $database = $directory . ($isV4 ? '/ip2region_v4.xdb' : '/ip2region_v6.xdb');
    if (!is_readable($database)) return '归属地库不可用';
    $searcher = null;
    try {
        require_once __DIR__ . '/ip2region/Searcher.class.php';
        $version = $isV4 ? \ip2region\xdb\IPv4::default() : \ip2region\xdb\IPv6::default();
        // 按需读取本地索引，不联网，也不把整份数据库载入 PHP 内存。
        $searcher = \ip2region\xdb\Searcher::newWithFileOnly($version, $database);
        $region = $searcher->search($ip);
        $parts = [];
        // 官方数据字段：国家|省份|城市|运营商|国家代码。
        foreach (array_slice(explode('|', $region), 0, 3) as $field) {
            $value = udLogValue($field);
            if ($value !== '' && $value !== '0' && !in_array($value, $parts, true)) $parts[] = $value;
        }
        return $parts ? implode(' / ', $parts) : '归属地未知';
    } catch (Throwable $error) {
        error_log('UD IP lookup: ' . $error->getMessage());
        return '归属地查询失败';
    } finally {
        if ($searcher !== null) $searcher->close();
    }
}

function udAppendRecord(string $path, string $ip, string $location, string $package, string $kind, ?DateTimeImmutable $now = null): void
{
    $file = @fopen($path, 'c+b');
    if ($file === false) throw new RuntimeException('无法打开下载日志，请检查文件写入权限');
    try {
        if (!flock($file, LOCK_EX)) throw new RuntimeException('无法锁定下载日志');
        $size = fstat($file)['size'];
        // 只读尾部，历史记录再多也不必整份载入内存。
        fseek($file, max(0, $size - 4096));
        $tail = stream_get_contents($file);
        $now = ($now ?? new DateTimeImmutable())->setTimezone(new DateTimeZone('Asia/Shanghai'));
        $day = $now->format('Y-m-d');
        preg_match_all('/^\[(\d{4}-\d{2}-\d{2}) \d{2}:\d{2}:\d{2}\]/m', $tail, $dates);
        $lastDay = $dates[1] ? end($dates[1]) : null;
        $text = $size === 0
            ? "UD智数下载记录\n时间：北京时间（Asia/Shanghai，UTC+08:00）\n字段：下载时间 | IP地址 | IP归属地 | 文件名 | 请求类型\n说明：记录下载请求，不代表文件已完整保存；IP归属地为估算位置。\n"
            : '';
        if ($size > 0 && !str_ends_with($tail, "\n")) $text .= "\n";
        if ($lastDay !== $day) $text .= "\n================ {$day} ================\n";
        $text .= '[' . $now->format('Y-m-d H:i:s') . '] | ' . implode(' | ', array_map('udLogValue', [$ip, $location, $package, $kind])) . "\n";
        fseek($file, 0, SEEK_END);
        try {
            $offset = 0;
            while ($offset < strlen($text)) {
                $written = fwrite($file, substr($text, $offset));
                if ($written === false || $written === 0) throw new RuntimeException('下载日志写入失败');
                $offset += $written;
            }
            if (!fflush($file)) throw new RuntimeException('下载日志保存失败');
        } catch (Throwable $error) {
            ftruncate($file, $size);
            throw $error;
        }
    } finally {
        flock($file, LOCK_UN);
        fclose($file);
    }
}

function udByteRange(?string $header, int $size): ?array
{
    if ($header === null) return null;
    if (!preg_match('/^bytes=(\d*)-(\d*)$/D', $header, $match) || ($match[1] === '' && $match[2] === '')) {
        throw new InvalidArgumentException('Invalid range');
    }
    if ($match[1] === '') {
        $length = (int) $match[2];
        if ($length <= 0) throw new InvalidArgumentException('Invalid range');
        return [max(0, $size - $length), $size - 1];
    }
    $start = (int) $match[1];
    $end = $match[2] === '' ? $size - 1 : min((int) $match[2], $size - 1);
    if ($start >= $size || $end < $start) throw new InvalidArgumentException('Invalid range');
    return [$start, $end];
}

function udDownloadError(int $status, string $message): void
{
    http_response_code($status);
    header('Content-Type: text/plain; charset=utf-8');
    header('Cache-Control: no-store');
    header('X-Content-Type-Options: nosniff');
    if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'HEAD') echo $message;
}

function udHandleDownload(string $root, string $manifestPath): void
{
    $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
    if (!in_array($method, ['GET', 'HEAD'], true)) {
        header('Allow: GET, HEAD');
        udDownloadError(405, '不支持此请求方式。');
        return;
    }
    $packages = json_decode((string) @file_get_contents($manifestPath), true);
    $id = $_GET['package'] ?? '';
    if (!is_string($id) || !isset($packages[$id])) {
        udDownloadError(404, '未找到该下载文件。');
        return;
    }
    $package = $packages[$id];
    $installerRoot = realpath($root . '/Download_Installer');
    $filename = realpath($root . '/' . $package['path']);
    if ($installerRoot === false || $filename === false || !str_starts_with($filename, $installerRoot . DIRECTORY_SEPARATOR) || !is_file($filename)) {
        udDownloadError(404, '下载文件暂未提供，请稍后再试。');
        return;
    }
    $file = @fopen($filename, 'rb');
    if ($file === false) {
        udDownloadError(503, '下载文件暂时不可读取，请稍后重试。');
        return;
    }
    try {
        $info = fstat($file);
        $size = $info['size'];
        if ($size === 0) {
            udDownloadError(503, '下载文件尚未上传完成，请稍后再试。');
            return;
        }
        $etag = '"' . dechex($size) . '-' . dechex($info['mtime']) . '"';
        $modified = gmdate('D, d M Y H:i:s', $info['mtime']) . ' GMT';
        $rangeHeader = $_SERVER['HTTP_RANGE'] ?? null;
        $ifRange = $_SERVER['HTTP_IF_RANGE'] ?? null;
        if ($ifRange !== null && $ifRange !== $etag && $ifRange !== $modified) $rangeHeader = null;
        try {
            // HEAD 仅查询文件信息，不记下载；不支持多段 Range，避免重复或过量读取。
            $range = $method === 'GET' ? udByteRange($rangeHeader, $size) : null;
        } catch (InvalidArgumentException $error) {
            header('Content-Range: bytes */' . $size);
            udDownloadError(416, '下载范围无效，请重新开始下载。');
            return;
        }
        if ($method === 'GET') {
            $ip = udClientIp($_SERVER);
            $location = udLookupLocation($ip);
            try {
                udAppendRecord($root . '/download-records.txt', $ip, $location, $package['filename'], $range === null ? '下载请求' : '续传 / 分段请求');
            } catch (Throwable $error) {
                error_log('UD download log: ' . $error->getMessage());
                udDownloadError(503, '下载服务暂时不可用，请稍后重试或联系产品团队。');
                return;
            }
        }
        [$start, $end] = $range ?? [0, $size - 1];
        $remaining = $end - $start + 1;
        @ini_set('zlib.output_compression', '0');
        while (ob_get_level() > 0) ob_end_clean();
        if (function_exists('set_time_limit')) @set_time_limit(0);
        http_response_code($range === null ? 200 : 206);
        header('Content-Type: ' . ($package['contentType'] ?? 'application/octet-stream'));
        $fallbackFilename = $package['fallbackFilename'] ?? 'UD-installer.exe';
        header('Content-Disposition: attachment; filename="' . $fallbackFilename . '"; filename*=UTF-8\'\'' . rawurlencode($package['filename']));
        header('Content-Length: ' . $remaining);
        header('Accept-Ranges: bytes');
        header('ETag: ' . $etag);
        header('Last-Modified: ' . $modified);
        header('Cache-Control: no-store, max-age=0');
        header('X-Content-Type-Options: nosniff');
        if ($range !== null) header("Content-Range: bytes {$start}-{$end}/{$size}");
        if ($method === 'HEAD') return;
        fseek($file, $start);
        while ($remaining > 0 && !feof($file) && !connection_aborted()) {
            $chunk = fread($file, min(65536, $remaining));
            if ($chunk === false || $chunk === '') break;
            echo $chunk;
            $remaining -= strlen($chunk);
            flush();
        }
    } finally {
        fclose($file);
    }
}

if (!defined('UD_DOWNLOAD_LIBRARY_ONLY')) {
    // 启用 PHP 但尚未安装日志保护配置时，不在公开目录中产生访客记录。
    if (($_SERVER['UD_DOWNLOAD_LOG_PROTECTED'] ?? '') !== '1') {
        udDownloadError(503, '下载服务配置尚未完成，请联系网站管理员。');
    } else {
        udHandleDownload(getenv('UD_DOWNLOAD_ROOT') ?: __DIR__, __DIR__ . '/download-packages.json');
    }
}
