<?php
/**
 * 百度SEO站群系统 - MySQL数据库模块
 * 替代JSON文件存储，提升大规模泛目录的性能和SEO效果
 * * 表结构：
 * - pan_pages: 泛目录页面持久映射（路径→视频+TDK）
 * - video_cache: 视频数据本地缓存（减少API依赖）
 */

require_once dirname(__FILE__) . '/config.php';

/**
 * 获取MySQL数据库连接（单例）
 */
function getDB()
{
    static $db = null;
    if ($db !== null) {
        return $db;
    }

    try {
        $dsn = 'mysql:host=' . DB_HOST . ';port=' . DB_PORT . ';dbname=' . DB_NAME . ';charset=' . DB_CHARSET;
        $db = new PDO($dsn, DB_USER, DB_PASS, [
            PDO::ATTR_PERSISTENT => true,
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
            (defined('Pdo\Mysql::ATTR_INIT_COMMAND') ? constant('Pdo\Mysql::ATTR_INIT_COMMAND') : 1002) => 'SET NAMES ' . DB_CHARSET,
        ]);

        // 【致命效率瓶颈修复】：高并发下绝对不能每次查库都跑 DDL 建表验证。
        // 通过文件锁标识，实现“终身只执行一次”的判定，彻底拯救 MySQL Metadata CPU 飙升。
        $initLock = ROOT_PATH . 'cache/.db_init.lock';
        if (!file_exists($initLock)) {
            initDB($db);
            @file_put_contents($initLock, 'initialized');
        }
    } catch (PDOException $e) {
        error_log('MySQL连接失败: ' . $e->getMessage());
        return null;
    }

    return $db;
}

/**
 * 初始化数据库表（自动建表）
 */
function initDB($db)
{
    // 泛目录页面映射表
    $db->exec('CREATE TABLE IF NOT EXISTS pan_pages (
        id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        path_hash VARCHAR(32) NOT NULL,
        path VARCHAR(500) NOT NULL DEFAULT "",
        keywords TEXT,
        title VARCHAR(500) NOT NULL DEFAULT "",
        description TEXT,
        meta_keywords TEXT,
        video_id INT UNSIGNED NOT NULL DEFAULT 0,
        video_name VARCHAR(500) NOT NULL DEFAULT "",
        video_pic VARCHAR(500) NOT NULL DEFAULT "",
        player_url VARCHAR(500) NOT NULL DEFAULT "",
        hits INT UNSIGNED NOT NULL DEFAULT 0,
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        UNIQUE KEY uk_path_hash (path_hash),
        KEY idx_video_id (video_id),
        KEY idx_hits (hits DESC, id DESC)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');

    // 视频数据缓存表
    $db->exec('CREATE TABLE IF NOT EXISTS video_cache (
        vod_id INT UNSIGNED PRIMARY KEY,
        vod_name VARCHAR(500) NOT NULL DEFAULT "",
        vod_pic VARCHAR(500) NOT NULL DEFAULT "",
        vod_blurb TEXT,
        vod_content TEXT,
        vod_play_url TEXT,
        vod_remarks VARCHAR(200) NOT NULL DEFAULT "",
        type_id INT UNSIGNED NOT NULL DEFAULT 0,
        type_name VARCHAR(100) NOT NULL DEFAULT "",
        hits_day INT UNSIGNED NOT NULL DEFAULT 0,
        hits_week INT UNSIGNED NOT NULL DEFAULT 0,
        hits_month INT UNSIGNED NOT NULL DEFAULT 0,
        cached_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
        KEY idx_type_hits (type_id, hits_day DESC)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');

    // 站点配置表（存储首页TDK等固定配置）
    $db->exec('CREATE TABLE IF NOT EXISTS site_config (
        config_key VARCHAR(100) PRIMARY KEY,
        config_value TEXT,
        updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');

    // 泛目录HTML静态缓存表
    $db->exec('CREATE TABLE IF NOT EXISTS pan_html_cache (
        path_hash VARCHAR(32) PRIMARY KEY,
        html_content MEDIUMTEXT NOT NULL,
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        KEY idx_created_at (created_at)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4');
}

// ==============================================================
// ==================== 视频缓存相关 ============================
// ==============================================================

/**
 * 批量同步视频数据到MySQL
 * @param array $videos API返回的视频列表
 */
function syncVideosToDB($videos)
{
    if (empty($videos))
        return;

    $db = getDB();
    if (!$db)
        return;

    $sql = 'INSERT INTO video_cache 
            (vod_id, vod_name, vod_pic, vod_blurb, vod_content, vod_play_url, vod_remarks, 
             type_id, type_name, hits_day, hits_week, hits_month)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ON DUPLICATE KEY UPDATE 
                vod_pic=VALUES(vod_pic), vod_play_url=VALUES(vod_play_url), 
                vod_remarks=VALUES(vod_remarks), type_id=VALUES(type_id), type_name=VALUES(type_name),
                hits_day=VALUES(hits_day), hits_week=VALUES(hits_week), hits_month=VALUES(hits_month)';

    $stmt = $db->prepare($sql);
    // 用于检测是否存在重复播放地址的防乱采探针
    $checkDupStmt = $db->prepare("SELECT vod_id FROM video_cache WHERE vod_play_url = ? LIMIT 1");

    $db->beginTransaction();
    try {
        foreach ($videos as $v) {
            $rawName = isset($v['vod_name']) ? $v['vod_name'] : '';
            $rawContent = isset($v['vod_content']) ? $v['vod_content'] : '';
            $playUrl = isset($v['vod_play_url']) ? trim($v['vod_play_url']) : '';
            $currentVodId = isset($v['vod_id']) ? intval($v['vod_id']) : 0;

            // 【严格采集去重机制】：防冗余/防 API 洗稿数据
            // 如果接口传来了实际播放地址，我们要确保本地绝对没有出现过这个一模一样的 M3U8 链接。
            // 只要出现了相同的 M3U8，就算片名和 ID 不一样，也直接判定为采集站发来的重复“垃圾片”，予以抛弃过滤！
            if ($playUrl !== '') {
                $checkDupStmt->execute([$playUrl]);
                $existingVodId = $checkDupStmt->fetchColumn();
                // 如果发现库里有相同的播放源，并且原数据的ID不同（证明是冗余视频），则跳过入库操作。
                // 若等于当前ID，则放行（属于正常资料更新）。
                if ($existingVodId !== false && (int)$existingVodId !== $currentVodId) {
                    continue; // 触发去重拦截，抛弃此条垃圾数据
                }
            }

            // 【百度飓风算法 3.0 破局：采集源伪原创拦截注入】
            // 在入库那一瞬间直接把 2.txt 的泛目录词锁死在标题和内容里。
            // 由于上面 ON DUPLICATE KEY 移除了 vod_name 的更新，这个词将被永久固化，蜘蛛每次来看到的都是绝对稳定的长尾标题。
            // 【百度飓风算法 3.0 / 文心大模型破局：高级语境伪原创】
            // 抛弃机械的“【关键词】+关键词堆砌”，采用多套自然语义模板洗稿，完美融词骗过百度 AI 内容指纹检测
            $injectKeywords = getRandomKeywords(DIR_KEYWORDS_FILE, 4);
            if (!empty($injectKeywords)) {
                $kw1 = $injectKeywords[0];
                $kw2 = isset($injectKeywords[1]) ? $injectKeywords[1] : '热门首发';
                $kw3 = isset($injectKeywords[2]) ? $injectKeywords[2] : '精彩看点';
                $kw4 = isset($injectKeywords[3]) ? $injectKeywords[3] : '高清视频';

                // 片名优化：百变自然组词，不露人工痕迹
                if ($rawName && strpos($rawName, $kw1) === false) {
                    $nameTemplates = [
                        $kw1 . '专属：' . $rawName,
                        $rawName . '（' . $kw1 . '无删减版）',
                        $rawName . ' - ' . $kw1 . '特供',
                        '精选' . $kw1 . '：' . $rawName
                    ];
                    $rawName = $nameTemplates[abs(crc32($rawName)) % count($nameTemplates)];
                }

                // 内容优化：百变自然语句池，高度契合百度 NLP 语义识别，绝不留机器拼接痕迹
                $contentTemplates = [
                    '<p>' . SITE_NAME . '重点推荐《' . $rawName . '》，本作不仅画面精良，更是' . $kw2 . '领域的代表作之一。如果您对' . $kw3 . '感兴趣，这部影片绝对能满足您的期待。</p>',
                    '<p>欢迎在' . SITE_NAME . '在线观看《' . $rawName . '》。本片精心提炼了' . $kw2 . '的经典元素，同时穿插了惊艳的' . $kw3 . '看点，特别推荐给喜欢' . $kw4 . '的观众欣赏。</p>',
                    '<p>《' . $rawName . '》是' . SITE_NAME . '近期收录的一部高分佳作。全片围绕着' . $kw2 . '展开极致演绎，在细节处充分展露了关于' . $kw3 . '的绝佳魅力，极具观赏性。</p>',
                    '<p>寻找优质的' . $kw2 . '资源？' . SITE_NAME . '为您极速放送《' . $rawName . '》。在这里您可以探索' . $kw3 . '与' . $kw4 . '的双重境界，感受无与伦比的高清流畅视听。</p>'
                ];
                $seoParagraph = $contentTemplates[abs(crc32($rawName . 'content')) % count($contentTemplates)];
                $rawContent = $seoParagraph . ($rawContent ? '<br>' . $rawContent : '');
            }

            // 【百度垂直领域细分算法（细粒度内容识别）破局】
            // 解决“挂羊头卖狗肉”问题：各大资源站传来的乱七八糟分类（如“综艺”、“纪录片”）如果挂着您的泛目录主词，会被百度算法秒降权。
            // 这里利用正则NLP，将杂乱的采集分类强制清洗并归入我们站点特定的核心分类中（保持全站高度垂直）。
            $rawTypeName = isset($v['type_name']) ? $v['type_name'] : '';
            $detectText = $rawName . $rawContent . $rawTypeName;
            
            $finalTypeId = 1;
            $finalTypeName = '热点';
            
            // 语义探针识别优先级：日韩 > 欧美 > 动漫 > 国产 > 热点
            if (preg_match('/(日韩|日本|韩国|中文字幕|无码|有码|熟女|AV|三级|东京|乱伦)/iu', $detectText)) {
                $finalTypeId = 2;
                $finalTypeName = '日韩一区二区';
            } elseif (preg_match('/(欧美|黑人|白妞|法兰西|金发|俄罗斯|欧美极品|欧美剧)/iu', $detectText)) {
                $finalTypeId = 4;
                $finalTypeName = '欧美一区二区';
            } elseif (preg_match('/(动漫|动画|全彩|里番|二次元|H漫|3D)/iu', $detectText)) {
                $finalTypeId = 5;
                $finalTypeName = '动漫';
            } elseif (preg_match('/(国产|大陆|探花|流出|自拍|偷拍|吃瓜|网红|福利姬|国产剧)/iu', $detectText)) {
                $finalTypeId = 3;
                $finalTypeName = '国产一区二区';
            } else {
                // 如果毫无明显特征，根据哈希值随机归入安全的泛领域
                $fallbackTypes = [
                    ['id' => 1, 'name' => '热点'],
                    ['id' => 3, 'name' => '国产一区二区'],
                ];
                $pick = $fallbackTypes[abs(crc32($detectText)) % count($fallbackTypes)];
                $finalTypeId = $pick['id'];
                $finalTypeName = $pick['name'];
            }

            $stmt->execute([
                isset($v['vod_id']) ? intval($v['vod_id']) : 0,
                $rawName,
                isset($v['vod_pic']) ? $v['vod_pic'] : '',
                isset($v['vod_blurb']) ? $v['vod_blurb'] : '',
                $rawContent,
                isset($v['vod_play_url']) ? $v['vod_play_url'] : '',
                isset($v['vod_remarks']) ? $v['vod_remarks'] : '',
                $finalTypeId,
                $finalTypeName,
                isset($v['vod_hits_day']) ? intval($v['vod_hits_day']) : 0,
                isset($v['vod_hits_week']) ? intval($v['vod_hits_week']) : 0,
                isset($v['vod_hits_month']) ? intval($v['vod_hits_month']) : 0,
            ]);
        }
        $db->commit();
    } catch (PDOException $e) {
        $db->rollBack();
        error_log('syncVideosToDB失败: ' . $e->getMessage());
    }
}

// ==============================================================
// ==================== 视频查询相关 ============================
// ==============================================================

/**
 * 获取视频缓存总数
 */
function getDBVideoCount()
{
    $db = getDB();
    if (!$db)
        return 0;
    try {
        $stmt = $db->query('SELECT COUNT(*) FROM video_cache');
        return intval($stmt->fetchColumn());
    } catch (PDOException $e) {
        return 0;
    }
}

/**
 * 根据ID从本地缓存获取视频详情
 */
function getVideoFromDB($vodId)
{
    $db = getDB();
    if (!$db)
        return null;
    try {
        $stmt = $db->prepare('SELECT * FROM video_cache WHERE vod_id = ? LIMIT 1');
        $stmt->execute([intval($vodId)]);
        $row = $stmt->fetch();
        return $row ?: null;
    } catch (PDOException $e) {
        return null;
    }
}

/**
 * 随机获取视频（可按分类过滤）
 */
function getRandomVideosFromDB($count = 12, $type = 0)
{
    $db = getDB();
    if (!$db)
        return [];
    try {
        // 先获取全站视频主键 ID 的首尾极值
        $stmtMinMax = $db->query('SELECT MIN(vod_id), MAX(vod_id) FROM video_cache');
        $bounds = $stmtMinMax->fetch(PDO::FETCH_NUM);
        if (!$bounds || empty($bounds[0])) return [];
        $minId = intval($bounds[0]);
        $maxId = intval($bounds[1]);

        $results = [];
        $attempts = 0;
        $maxAttempts = intval($count) * 3;
        
        // 采用跳表游标随机探针（拒绝 ORDER BY RAND() 引起的 CPU 烧毁灾难）
        while (count($results) < $count && $attempts < $maxAttempts) {
            $attempts++;
            $randId = mt_rand($minId, $maxId);
            
            if ($type > 0) {
                // 如果有分类限制，可能需要多次探针，所以加宽搜索面
                $stmt = $db->prepare('SELECT * FROM video_cache WHERE vod_id >= ? AND type_id = ? LIMIT 1');
                $stmt->execute([$randId, intval($type)]);
            } else {
                $stmt = $db->prepare('SELECT * FROM video_cache WHERE vod_id >= ? LIMIT 1');
                $stmt->execute([$randId]);
            }
            
            $row = $stmt->fetch();
            if ($row) {
                // 确保结果池内不出现重复数据
                $results[$row['vod_id']] = $row;
            } else {
                // 如果命中空白区，重新获取并把指针移回开头重试
                if ($type > 0) {
                    $stmt = $db->prepare('SELECT * FROM video_cache WHERE type_id = ? LIMIT 1');
                    $stmt->execute([intval($type)]);
                } else {
                    $stmt = $db->query('SELECT * FROM video_cache LIMIT 1');
                }
                $row = $stmt->fetch();
                if ($row) $results[$row['vod_id']] = $row;
            }
        }
        
        // 如果实在是空库或分类极度冷门导致未装满，也直接打乱返回
        $finalArr = array_values($results);
        shuffle($finalArr);
        return $finalArr;
    } catch (PDOException $e) {
        return [];
    }
}

/**
 * 获取最新视频（按缓存时间倒序）
 */
function getLatestVideosFromDB($count = 24)
{
    $db = getDB();
    if (!$db)
        return [];
    try {
        $stmt = $db->prepare('SELECT * FROM video_cache ORDER BY cached_at DESC, vod_id DESC LIMIT ?');
        $stmt->execute([intval($count)]);
        return $stmt->fetchAll();
    } catch (PDOException $e) {
        return [];
    }
}

/**
 * 获取分类热门视频（按日点击量排序）
 */
function getHotVideosByType($typeId = 0, $count = 10)
{
    $db = getDB();
    if (!$db)
        return [];
    try {
        if ($typeId > 0) {
            $stmt = $db->prepare('SELECT * FROM video_cache WHERE type_id = ? ORDER BY hits_day DESC, hits_week DESC LIMIT ?');
            $stmt->execute([intval($typeId), intval($count)]);
        } else {
            $stmt = $db->prepare('SELECT * FROM video_cache ORDER BY hits_day DESC, hits_week DESC LIMIT ?');
            $stmt->execute([intval($count)]);
        }
        return $stmt->fetchAll();
    } catch (PDOException $e) {
        return [];
    }
}

/**
 * 分页获取视频（无限滚动用）
 */
function getVideosPage($offset = 0, $count = 12)
{
    $db = getDB();
    if (!$db)
        return [];
    try {
        $stmt = $db->prepare('SELECT * FROM video_cache ORDER BY cached_at DESC, vod_id DESC LIMIT ? OFFSET ?');
        $stmt->execute([intval($count), intval($offset)]);
        return $stmt->fetchAll();
    } catch (PDOException $e) {
        return [];
    }
}

/**
 * 获取有内容的前N个分类
 */
function getTopCategories($count = 3)
{
    $db = getDB();
    if (!$db)
        return [];
    try {
        $stmt = $db->prepare('SELECT type_id, type_name, COUNT(*) as cnt FROM video_cache WHERE type_id > 0 AND type_name != "" GROUP BY type_id, type_name ORDER BY cnt DESC, type_id DESC LIMIT ?');
        $stmt->execute([intval($count)]);
        return $stmt->fetchAll();
    } catch (PDOException $e) {
        return [];
    }
}

// ==============================================================
// ==================== 泛目录页面相关 ==========================
// ==============================================================

/**
 * 从数据库查询泛目录页面映射
 */
function getPanPageFromDB($pathHash)
{
    $db = getDB();
    if (!$db)
        return null;
    try {
        $stmt = $db->prepare('SELECT * FROM pan_pages WHERE path_hash = ? LIMIT 1');
        $stmt->execute([$pathHash]);
        $row = $stmt->fetch();
        if ($row) {
            // keywords字段存储为JSON字符串，需要解码
            if (!empty($row['keywords'])) {
                $decoded = @json_decode($row['keywords'], true);
                $row['keywords'] = is_array($decoded) ? $decoded : [$row['keywords']];
            } else {
                $row['keywords'] = [];
            }
        }
        return $row ?: null;
    } catch (PDOException $e) {
        return null;
    }
}

/**
 * 保存泛目录页面映射到数据库
 */
function savePanPageToDB($data)
{
    $db = getDB();
    if (!$db)
        return false;

    $pathHash = md5($data['path']);
    $keywords = isset($data['keywords']) ? json_encode($data['keywords'], JSON_UNESCAPED_UNICODE) : '[]';

    try {
        $sql = 'INSERT INTO pan_pages (path_hash, path, keywords, title, description, meta_keywords, video_id, video_name, video_pic, player_url)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                ON DUPLICATE KEY UPDATE
                    title=VALUES(title), description=VALUES(description), meta_keywords=VALUES(meta_keywords),
                    video_id=VALUES(video_id), video_name=VALUES(video_name), video_pic=VALUES(video_pic), player_url=VALUES(player_url)';
        $stmt = $db->prepare($sql);
        $stmt->execute([
            $pathHash,
            isset($data['path']) ? $data['path'] : '',
            $keywords,
            isset($data['title']) ? $data['title'] : '',
            isset($data['description']) ? $data['description'] : '',
            isset($data['meta_keywords']) ? $data['meta_keywords'] : '',
            isset($data['video_id']) ? intval($data['video_id']) : 0,
            isset($data['video_name']) ? $data['video_name'] : '',
            isset($data['video_pic']) ? $data['video_pic'] : '',
            isset($data['player_url']) ? $data['player_url'] : '',
        ]);
        return true;
    } catch (PDOException $e) {
        error_log('savePanPageToDB失败: ' . $e->getMessage());
        return false;
    }
}

/**
 * 获取已分配的视频ID列表（防重复分配）
 */
function getUsedVideoIds($limit = 500)
{
    $db = getDB();
    if (!$db)
        return [];
    try {
        $stmt = $db->prepare('SELECT video_id FROM pan_pages WHERE video_id > 0 ORDER BY id DESC LIMIT ?');
        $stmt->execute([intval($limit)]);
        return $stmt->fetchAll(PDO::FETCH_COLUMN);
    } catch (PDOException $e) {
        return [];
    }
}

/**
 * 随机获取泛目录页面（交叉内链用）
 */
function getRandomPanPages($count = 8, $excludePath = '')
{
    $db = getDB();
    if (!$db)
        return [];
    try {
        $reqPathOnly = strtok(isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '', '?');
        $seedStr = (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost') . $reqPathOnly;
        
        $stmtMax = $db->query('SELECT MAX(id) FROM pan_pages');
        $maxId = intval($stmtMax->fetchColumn());
        if ($maxId <= 0) return [];

        $results = [];
        $fetchedHashes = [];
        if (!empty($excludePath)) {
            $fetchedHashes[md5($excludePath)] = true;
        }

        $attempts = 0;
        $maxAttempts = intval($count) * 3;

        for ($i = 0; count($results) < $count && $attempts < $maxAttempts; $i++, $attempts++) {
            $seedInt = abs(crc32($seedStr . '_rnd_' . $i));
            $startId = $seedInt % max(1, $maxId);

            $query = 'SELECT path, title, video_name, path_hash FROM pan_pages WHERE id >= ? AND video_name != "" ORDER BY id ASC LIMIT 1';
            $stmt = $db->prepare($query);
            $stmt->execute([$startId]);
            $row = $stmt->fetch();

            if (!$row) {
                // If id was too high and no rows were found, wrap back around from beginning
                $query = 'SELECT path, title, video_name, path_hash FROM pan_pages WHERE id >= 1 AND video_name != "" ORDER BY id ASC LIMIT 1';
                $stmt = $db->query($query);
                $row = $stmt->fetch();
            }

            if ($row) {
                if (!isset($fetchedHashes[$row['path_hash']])) {
                    $fetchedHashes[$row['path_hash']] = true;
                    $results[] = [
                        'path' => $row['path'],
                        'title' => $row['title'],
                        'video_name' => $row['video_name']
                    ];
                }
            } else {
                break;
            }
        }
        return $results;
    } catch (PDOException $e) {
        return [];
    }
}

// ==============================================================
// ==================== 站点配置相关 ============================
// ==============================================================

/**
 * 获取首页TDK配置
 */
function getHomeTDK($domain)
{
    $db = getDB();
    if (!$db)
        return null;
    try {
        $stmt = $db->prepare('SELECT config_value FROM site_config WHERE config_key = ? LIMIT 1');
        $stmt->execute(['home_tdk_' . $domain]);
        $row = $stmt->fetch();
        if ($row && !empty($row['config_value'])) {
            return @json_decode($row['config_value'], true);
        }
        return null;
    } catch (PDOException $e) {
        return null;
    }
}

/**
 * 保存首页TDK配置
 */
function saveHomeTDK($domain, $data)
{
    $db = getDB();
    if (!$db)
        return false;
    try {
        $sql = 'INSERT INTO site_config (config_key, config_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE config_value=VALUES(config_value)';
        $stmt = $db->prepare($sql);
        $stmt->execute([
            'home_tdk_' . $domain,
            json_encode($data, JSON_UNESCAPED_UNICODE),
        ]);
        return true;
    } catch (PDOException $e) {
        return false;
    }
}

// ==============================================================
// ==================== 数据迁移 ================================
// ==============================================================

/**
 * 从pan_map.json迁移到MySQL
 */
function migrateFromJSON()
{
    $jsonFile = ROOT_PATH . 'pan_map.json';
    if (!file_exists($jsonFile))
        return false;

    $json = @file_get_contents($jsonFile);
    $map = @json_decode($json, true);
    if (empty($map))
        return false;

    $db = getDB();
    if (!$db)
        return false;

    $count = 0;
    $db->beginTransaction();
    try {
        foreach ($map as $path => $entry) {
            $data = [
                'path' => $path,
                'keywords' => isset($entry['keywords']) ? $entry['keywords'] : [],
                'title' => isset($entry['title']) ? $entry['title'] : '',
                'description' => isset($entry['description']) ? $entry['description'] : '',
                'meta_keywords' => isset($entry['meta_keywords']) ? $entry['meta_keywords'] : '',
                'video_id' => isset($entry['video_id']) ? intval($entry['video_id']) : 0,
                'video_name' => isset($entry['video_name']) ? $entry['video_name'] : '',
                'video_pic' => isset($entry['video_pic']) ? $entry['video_pic'] : '',
                'player_url' => isset($entry['player_url']) ? $entry['player_url'] : '',
            ];

            $pathHash = md5($path);
            $keywords = json_encode($data['keywords'], JSON_UNESCAPED_UNICODE);

            $stmt = $db->prepare('INSERT IGNORE INTO pan_pages (path_hash, path, keywords, title, description, meta_keywords, video_id, video_name, video_pic, player_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
            $stmt->execute([
                $pathHash,
                $data['path'],
                $keywords,
                $data['title'],
                $data['description'],
                $data['meta_keywords'],
                $data['video_id'],
                $data['video_name'],
                $data['video_pic'],
                $data['player_url'],
            ]);
            $count++;
        }
        $db->commit();
        // 迁移成功后可选择备份json（不删除，防止回滚需要）
        @rename($jsonFile, $jsonFile . '.migrated');
        error_log("migrateFromJSON: 迁移了 {$count} 条泛目录记录到MySQL");
        return true;
    } catch (PDOException $e) {
        $db->rollBack();
        error_log('migrateFromJSON失败: ' . $e->getMessage());
        return false;
    }
}

// ==============================================================
// ==================== HTML静态缓存相关 ==========================
// ==============================================================

/**
 * 获取泛目录页面的静态HTML缓存
 */
function getPanHtmlFromDB($pathHash)
{
    $db = getDB();
    if (!$db) return null;
    try {
        $stmt = $db->prepare('SELECT html_content FROM pan_html_cache WHERE path_hash = ? LIMIT 1');
        $stmt->execute([$pathHash]);
        return $stmt->fetchColumn() ?: null;
    } catch (PDOException $e) {
        return null;
    }
}

/**
 * 保存泛目录页面的静态HTML缓存
 */
function savePanHtmlToDB($pathHash, $htmlContent)
{
    $db = getDB();
    if (!$db) return false;
    try {
        // 使用 INSERT... ON DUPLICATE KEY UPDATE 以完美兼容 MySQL 进行强缓存写入
        $stmt = $db->prepare('INSERT INTO pan_html_cache (path_hash, html_content) VALUES (?, ?) ON DUPLICATE KEY UPDATE html_content = html_content');
        $stmt->execute([$pathHash, $htmlContent]);
        
        // 【防灾备逻辑：自动真空回收机制】
        // 应对百万收录级别站群：每次写入有 1% 概率触发清理。
        // 删除 7 天前（即早就过了极光算法考察期）的旧泛目录静态快照。
        // 防止数据库瞬间膨胀至几十GB导致磁盘写满出现 502/500 服务器崩溃被百度强迫拔毛！
        if (mt_rand(1, 100) === 1) {
            $db->exec("DELETE FROM pan_html_cache WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 DAY)");
        }
        
        return true;
    } catch (PDOException $e) {
        error_log('savePanHtmlToDB 失败: ' . $e->getMessage());
        return false;
    }
}