"use strict";
/**
 * archive-messagerecords.ts
 *
 * 将 messagerecords 热表中超过 HOT_DAYS 天的数据按月归档到
 * messagerecords_archive_YYYYMM 集合。
 *
 * 用法：
 *   npx ts-node src/scripts/archive-messagerecords.ts            # dry-run（仅统计，不写库）
 *   npx ts-node src/scripts/archive-messagerecords.ts --execute  # 真正执行归档
 *   npx ts-node src/scripts/archive-messagerecords.ts --execute --month 2026-02  # 仅处理指定月份
 *
 * 归档规则：
 *   - 归档截止日：今天往前 HOT_DAYS 天（默认 180）的完整月末
 *   - 幂等：重跑不重复插，使用 replaceOne+upsert by _id
 *   - 每批 BATCH_SIZE 条，防止 OOM
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const mongoose_1 = __importDefault(require("mongoose"));
const MONGO_URI = process.env.MONGO_URI || "mongodb://oms:oms123.@localhost:27717/omsdb?authSource=admin";
const HOT_DAYS = 180;
const BATCH_SIZE = 3000;
// ---------- CLI 参数 ----------
const isDryRun = !process.argv.includes("--execute");
const monthArg = (() => {
    const idx = process.argv.indexOf("--month");
    return idx !== -1 ? process.argv[idx + 1] : null;
})();
// ---------- 工具 ----------
function monthStr(year, month) {
    return `${year}${String(month).padStart(2, "0")}`;
}
/** 计算需要归档的月份列表（完整月，不包含当前热数据月） */
function calcArchiveMonths(hotDays) {
    const now = new Date();
    // 归档截止：当前日期 - hotDays，取当月月初为边界（保守归档完整月）
    const cutoff = new Date(now);
    cutoff.setDate(cutoff.getDate() - hotDays);
    // 归档到 cutoff 所在月份的上一个月末（避免截断当月数据）
    const cutoffMonth = new Date(cutoff.getFullYear(), cutoff.getMonth(), 1);
    const months = [];
    // 找出 messagerecords 中最早记录的月份上限：不超过 cutoffMonth
    // 实际起点由 findEarliestMonth 确定，这里先返回最大范围
    // 从 2024-01 到 cutoffMonth（exclusive）
    const start = new Date(2024, 5, 1); // 2024-06 数据起始月
    const cursor = new Date(start);
    while (cursor < cutoffMonth) {
        const y = cursor.getFullYear();
        const m = cursor.getMonth(); // 0-indexed
        const monthStart = new Date(y, m, 1);
        const monthEnd = new Date(y, m + 1, 1);
        const yyyymm = monthStr(y, m + 1);
        months.push({ yyyymm, start: monthStart, end: monthEnd });
        cursor.setMonth(cursor.getMonth() + 1);
    }
    return months;
}
/** 获取归档集合（动态 model，复用 connection） */
function getArchiveCollection(conn, yyyymm) {
    const collectionName = `messagerecords_archive_${yyyymm}`;
    // 直接操作 native collection，不走 Mongoose model，保持 schema 灵活
    return conn.collection(collectionName);
}
// ---------- 主流程 ----------
async function run() {
    console.log(`[Archive] mode=${isDryRun ? "DRY-RUN" : "EXECUTE"} hotDays=${HOT_DAYS} batchSize=${BATCH_SIZE}`);
    if (monthArg) {
        console.log(`[Archive] scope=single month=${monthArg}`);
    }
    const conn = await mongoose_1.default.createConnection(MONGO_URI).asPromise();
    console.log("[Archive] Connected to MongoDB.");
    const hotCol = conn.collection("messagerecords");
    // 确定要处理的月份列表
    let months = calcArchiveMonths(HOT_DAYS);
    if (monthArg) {
        const [y, m] = monthArg.split("-").map(Number);
        months = months.filter((mo) => mo.yyyymm === monthStr(y, m));
        if (months.length === 0) {
            console.error(`[Archive] ERROR: month ${monthArg} is not in archivable range (must be >= ${HOT_DAYS} days ago).`);
            await conn.close();
            process.exit(1);
        }
    }
    let totalArchived = 0;
    let totalSkipped = 0;
    for (const { yyyymm, start, end } of months) {
        const hotCount = await hotCol.countDocuments({ createdAt: { $gte: start, $lt: end } });
        if (hotCount === 0) {
            console.log(`[Archive] ${yyyymm}: hotCount=0, skip`);
            continue;
        }
        const archiveCol = getArchiveCollection(conn, yyyymm);
        const archiveCount = await archiveCol.countDocuments({ createdAt: { $gte: start, $lt: end } });
        if (archiveCount >= hotCount) {
            console.log(`[Archive] ${yyyymm}: already complete (hot=${hotCount}, archive=${archiveCount}), skip`);
            totalSkipped += hotCount;
            continue;
        }
        console.log(`[Archive] ${yyyymm}: hot=${hotCount} archive=${archiveCount} pending=${hotCount - archiveCount}`);
        if (isDryRun) {
            totalArchived += hotCount - archiveCount;
            continue;
        }
        // 确保归档集合有 createdAt 索引（与热表一致）
        await archiveCol.createIndex({ createdAt: 1 }, { background: true });
        await archiveCol.createIndex({ uid: 1 }, { background: true });
        await archiveCol.createIndex({ strategyName: 1, createdAt: 1 }, { background: true });
        // 游标分批迁移
        let batchInserted = 0;
        const cursor = hotCol.find({ createdAt: { $gte: start, $lt: end } }).batchSize(BATCH_SIZE);
        let batch = [];
        for await (const doc of cursor) {
            batch.push(doc);
            if (batch.length >= BATCH_SIZE) {
                const ops = batch.map((d) => ({
                    replaceOne: {
                        filter: { _id: d._id },
                        replacement: d,
                        upsert: true,
                    },
                }));
                const result = await archiveCol.bulkWrite(ops, { ordered: false });
                batchInserted += result.upsertedCount + result.modifiedCount;
                batch = [];
                process.stdout.write(`\r[Archive] ${yyyymm}: archived ${batchInserted}/${hotCount - archiveCount} ...`);
            }
        }
        // 处理最后一批
        if (batch.length > 0) {
            const ops = batch.map((d) => ({
                replaceOne: {
                    filter: { _id: d._id },
                    replacement: d,
                    upsert: true,
                },
            }));
            const result = await archiveCol.bulkWrite(ops, { ordered: false });
            batchInserted += result.upsertedCount + result.modifiedCount;
        }
        console.log(`\n[Archive] ${yyyymm}: done inserted/updated=${batchInserted}`);
        totalArchived += batchInserted;
    }
    console.log(`\n[Archive] SUMMARY mode=${isDryRun ? "DRY-RUN" : "EXECUTE"} totalArchived=${totalArchived} totalSkipped=${totalSkipped}`);
    if (isDryRun) {
        console.log("[Archive] DRY-RUN: no data was written. Re-run with --execute to apply.");
    }
    await conn.close();
}
run().catch((err) => {
    console.error("[Archive] FATAL:", err);
    process.exit(1);
});
