<?php
/**
 * 封面图本地缓存 / 代理
 * 用法：<img src="img.php?u=<urlencode(原始封面URL)>">
 *
 * 作用：
 *  1) 首次请求回源下载原图，缩成缩略图后落到 data/imgcache/，之后本地直出，不再依赖第三方 CDN；
 *  2) 第三方 CDN 再次挂掉/被墙时也能从本地缓存读出，不裂图；
 *  3) 抓取失败时返回内置占位图，并记录静默期避免重复回源。
 *
 * 安全：域名白名单 + 仅 http(s) + 仅默认端口，避免被当成任意 URL 代理（SSRF）。
 *
 * 注意：本文件只做「入口 + 输出」。
 * 抓取/缩略/落盘等实际逻辑全部在 lib.php 里（img_fetch / img_thumb / img_cache_warm_one），
 * 这样 pic_warm.php（缓存预热）能复用完全相同的逻辑，保证预热的缓存 img.php 能直接命中。
 */

require_once __DIR__ . '/lib.php';

// 防止被重复引入：本文件既定义函数又在末尾直接输出图片
if (defined('IMG_PROXY_ACTIVE')) return;
define('IMG_PROXY_ACTIVE', true);

/** 输出内置占位图（SVG） */
function img_send_placeholder(): void {
    header('Content-Type: image/svg+xml; charset=utf-8');
    header('Cache-Control: public, max-age=86400');
    echo '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="120">'
        . '<rect width="100%" height="100%" fill="#232946"/>'
        . '<text x="50%" y="55%" fill="#56607a" font-size="13" text-anchor="middle" '
        . 'font-family="sans-serif">暂无封面</text></svg>';
}

/** 清理过期缓存与失败标记（按概率触发，避免每次请求都扫目录） */
function img_gc(): void {
    if (mt_rand(1, 200) !== 1) return;
    $deadline = time() - IMG_CACHE_DAYS * 86400;
    foreach (glob(IMG_CACHE_DIR . '/*') ?: [] as $f) {
        if (@filemtime($f) < $deadline) @unlink($f);
    }
    // 失败标记只保留 IMG_FAIL_TTL，过期即清，让后续请求可以重试
    $failDeadline = time() - IMG_FAIL_TTL;
    foreach (glob(IMG_CACHE_DIR . '/*.fail') ?: [] as $f) {
        if (@filemtime($f) < $failDeadline) @unlink($f);
    }
}

/* ---------------- 主流程 ---------------- */
$raw = isset($_GET['u']) ? (string)$_GET['u'] : '';
$url = trim($raw);
// 兼容传入未 urlencode 的情况
if ($url !== '' && strpos($url, 'http') !== 0 && strpos(urldecode($raw), 'http') === 0) {
    $url = urldecode($raw);
}

if ($url === '' || stripos($url, 'http') !== 0) {
    img_send_placeholder();
    exit;
}

// 白名单校验（scheme 必须 http/https；host 必须精确匹配；端口仅允许默认）
if (!img_url_allowed($url)) {
    img_send_placeholder();
    exit;
}

ensure_dir(IMG_CACHE_DIR);
$cache = img_cache_file($url);
$meta  = img_cache_meta($url);

/**
 * 输出一张已缓存的图：带强缓存 + ETag，让浏览器重复访问直接 304 命中，
 * 连那 30KB 的传输都省掉（移动端流量与速度都受益）。
 */
function img_send_cached(string $file, string $mime, bool $withGc = false): void {
    $size = (int)@filesize($file);
    $mtime = (int)@filemtime($file);
    $etag = '"' . dechex($size) . '-' . dechex($mtime) . '"';

    header('Content-Type: ' . $mime);
    header('Cache-Control: public, max-age=604800');   // 7 天
    header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $mtime) . ' GMT');
    header('ETag: ' . $etag);

    // 浏览器已有最新版本 → 304，不传图片数据
    $ifNoneMatch = $_SERVER['HTTP_IF_NONE_MATCH'] ?? '';
    $ifModSince  = (int)($_SERVER['HTTP_IF_MODIFIED_SINCE'] ?? 0);
    if ($ifNoneMatch === $etag
        || ($ifNoneMatch === '' && $ifModSince > 0 && $ifModSince >= $mtime)) {
        header('HTTP/1.1 304 Not Modified');
        if ($withGc) img_gc();
        exit;
    }

    header('Content-Length: ' . $size);
    @readfile($file);
    if ($withGc) img_gc();
    exit;
}

// 1) 命中原图缓存
if (file_exists($cache) && file_exists($meta)) {
    $m = load_json($meta, []);
    $mime = isset($m['mime']) ? (string)$m['mime'] : 'image/jpeg';
    img_send_cached($cache, $mime);
}

// 1b) 命中"失败标记"：短时间内不重复回源，避免每次访问都白等超时
$failFlag = img_cache_fail($url);
if (file_exists($failFlag) && time() - (int)@filemtime($failFlag) < IMG_FAIL_TTL) {
    header('X-Img-Status: recently-failed');
    img_send_placeholder();
    exit;
}

// 2) 抓取（共用 lib.php 的落盘逻辑，与 pic_warm.php 完全一致）
$result = img_cache_warm_one($url);
if ($result === 'fail') {
    header('X-Img-Status: fetch-failed');
    img_send_placeholder();
    exit;
}

// 3) 输出（warm_one 已把结果写进缓存）
if (!file_exists($cache)) {
    header('X-Img-Status: cache-missing');
    img_send_placeholder();
    exit;
}
$m = load_json($meta, []);
$mime = isset($m['mime']) ? (string)$m['mime'] : 'image/jpeg';
img_send_cached($cache, $mime, true);
