// ============================================================
// build_aggregates_sql.ts — TimescaleDB 连续聚合逐月刷新脚本
// ============================================================
// 用途：
//   按月份粒度逐月刷新 klines 分层连续聚合物化视图链：
//     基表 1m → 3m / 5m → 15m → 30m → 1h → 2h / 4h / 6h → 8h / 1d → 1w / 1mon
//   严格按依赖顺序从低到高刷新。
//
// 使用方式：
//   # 仅生成 SQL 不执行（dry-run，默认）
//   bun run data/run/build_aggregates_sql.ts
//
//   # 实际执行（连接数据库刷新）
//   bun run data/run/build_aggregates_sql.ts --execute
//
//   # 指定时间范围
//   bun run data/run/build_aggregates_sql.ts --start 2019-08 --end 2020-03 --execute
//
// 配置：
//   数据库连接信息从项目根目录 env.yaml 读取（通过 ../config 模块）。
//   确保 env.yaml 中 db 段配置正确。
//
// 风险提示：
//   - refresh_continuous_aggregate 会获取表级锁，建议在低流量时段执行
//   - 逐月刷新避免单次锁定时间过长，每层每个月的刷新是独立事务
//   - 如果某层某月刷新失败，脚本会记录错误并继续处理后续月份
// ============================================================

import { logger } from "../utils/logger";
// AppDataSource 通过动态导入延迟加载，避免 dry-run 模式下触发数据库连接

// ============================================================
// 1. 常量定义
// ============================================================

/**
 * 分层聚合视图链（按依赖顺序：低层级 → 高层级）
 *
 * 实际依赖关系（来自于 03-continuous-aggregates.sql）：
 *   基表 1m
 *     ├── 3m  （直接聚合 1m）
 *     └── 5m  （直接聚合 1m）→ 15m → 30m → 1h ──→ 2h
 *                                              ├── 4h ──→ 8h
 *                                              │        └── 1d ──→ 1w
 *                                              │               └── 1mon
 *                                              └── 6h
 *
 * 刷新时必须严格按此顺序，因为：
 *   - klines_3m / klines_5m 无聚合依赖，可最先刷新
 *   - klines_15m 依赖 klines_5m
 *   - klines_30m 依赖 klines_15m
 *   - klines_1h  依赖 klines_30m
 *   - klines_2h / klines_4h / klines_6h 依赖 klines_1h
 *   - klines_8h / klines_1d 依赖 klines_4h
 *   - klines_1w / klines_1mon 依赖 klines_1d
 */
const AGGREGATE_VIEWS = [
    "klines_3m",
    "klines_5m",
    "klines_15m",
    "klines_30m",
    "klines_1h",
    "klines_2h",
    "klines_4h",
    "klines_6h",
    "klines_8h",
    "klines_1d",
    "klines_1w",
    "klines_1mon",
] as const;

/** 默认起始年月 */
const DEFAULT_START = { year: 2017, month: 5 }; // 2018-09
/** 默认结束年月 */
const DEFAULT_END = { year: 2026, month: 6 }; // 2019-01

// ============================================================
// 2. 工具函数
// ============================================================

/** 解析命令行参数为 key-value 映射 */
function parseArgs(args: string[]): Map<string, string> {
    const map = new Map<string, string>();
    for (let i = 0; i < args.length; i++) {
        const arg = args[i]!; // 循环条件保证 i < args.length
        if (arg.startsWith("--")) {
            const key = arg.slice(2);
            // 下一个参数如果不是 -- 开头，则为 value；否则是 boolean flag
            const next = args[i + 1];
            if (next && !next.startsWith("--")) {
                map.set(key, next);
                i++;
            } else {
                map.set(key, "true");
            }
        }
    }
    return map;
}

/**
 * 解析 "YYYY-MM" 格式字符串为 { year, month }
 * 月份使用 1-based（与 JS Date 不同，便于人类阅读）
 */
function parseYearMonth(input: string): { year: number; month: number } | null {
    const match = input.match(/^(\d{4})-(\d{2})$/);
    if (!match) {
        return null;
    }
    // match[1] / match[2] 在正则匹配成功时一定存在，使用 ! 断言
    const year = parseInt(match[1]!, 10);
    const month = parseInt(match[2]!, 10);
    if (month < 1 || month > 12) return null;
    return { year, month };
}

/**
 * 生成从 start 到 end（含）的所有年月序列
 *
 * 例如 start=2018-09, end=2019-01 → [2018-09, 2018-10, 2018-11, 2018-12, 2019-01]
 *
 * @param start - 起始年月（1-based month）
 * @param end   - 结束年月（1-based month），包含该月
 * @returns 按时间升序排列的年月数组
 */
function generateMonthRange(
    start: { year: number; month: number },
    end: { year: number; month: number }
): Array<{ year: number; month: number }> {
    const result: Array<{ year: number; month: number }> = [];

    let y = start.year;
    let m = start.month; // 1-based

    // 将 end 转为总月份数，便于比较
    const endTotal = end.year * 12 + (end.month - 1);

    while (true) {
        const currentTotal = y * 12 + (m - 1);
        if (currentTotal > endTotal) break;

        result.push({ year: y, month: m });

        // 递增月份
        m++;
        if (m > 12) {
            m = 1;
            y++;
        }
    }

    return result;
}

/**
 * 为指定年月构建 refresh_continuous_aggregate SQL
 *
 * TimescaleDB 中 refresh_continuous_aggregate 的 window 是左闭右开：
 *   [window_start, window_end)
 * 因此 window_start = 当月1日 00:00:00+00
 *      window_end   = 下月1日 00:00:00+00
 *
 * @param viewName    - 聚合视图名称，如 "klines_5m"
 * @param year        - 年份
 * @param month       - 月份（1-based）
 * @returns 可执行的 SQL 字符串
 */
function buildRefreshSQL(
    viewName: string,
    year: number,
    month: number
): string {
    // 计算下个月的年月
    let nextMonth = month + 1;
    let nextYear = year;
    if (nextMonth > 12) {
        nextMonth = 1;
        nextYear = year + 1;
    }

    const pad = (n: number) => String(n).padStart(2, "0");
    const windowStart = `${year}-${pad(month)}-01 00:00:00+00`;
    const windowEnd = `${nextYear}-${pad(nextMonth)}-01 00:00:00+00`;

    return `CALL refresh_continuous_aggregate('${viewName}', '${windowStart}', '${windowEnd}');`;
}

/**
 * 为所有聚合视图生成某个指定月份的 SQL 语句列表
 *
 * @returns 按依赖顺序排列的 SQL 语句数组
 */
function buildMonthSQLBatch(
    year: number,
    month: number
): Array<{ view: string; sql: string }> {
    return AGGREGATE_VIEWS.map((view) => ({
        view,
        sql: buildRefreshSQL(view, year, month),
    }));
}

// ============================================================
// 3. 核心执行逻辑
// ============================================================

/** 单次刷新操作的统计 */
interface RefreshStats {
    /** 尝试执行的视图数 */
    totalViews: number;
    /** 成功数 */
    success: number;
    /** 失败数 */
    failed: number;
    /** 失败的详情列表 */
    errors: Array<{ view: string; month: string; error: string }>;
}

/**
 * 执行单个月份所有聚合视图的刷新
 *
 * 按依赖顺序依次执行，每个视图的刷新是独立事务。
 * 某个视图失败不会阻止后续视图的执行。
 *
 * @param ds    - TypeORM DataSource 实例
 * @param year  - 年份
 * @param month - 月份（1-based）
 * @returns 该月份的刷新统计
 */
async function refreshSingleMonth(
    ds: import("typeorm").DataSource,
    year: number,
    month: number
): Promise<RefreshStats> {
    const pad = (n: number) => String(n).padStart(2, "0");
    const monthLabel = `${year}-${pad(month)}`;
    const batch = buildMonthSQLBatch(year, month);

    const stats: RefreshStats = {
        totalViews: batch.length,
        success: 0,
        failed: 0,
        errors: [],
    };

    for (const { view, sql } of batch) {
        try {
            logger.info(`[refresh] ${monthLabel} | ${view} 开始刷新...`);
            const startMs = Date.now();
            await ds.query(sql);
            const elapsed = ((Date.now() - startMs) / 1000).toFixed(1);
            logger.info(`[refresh] ${monthLabel} | ${view} 完成 (${elapsed}s)`);
            stats.success++;
        } catch (err: unknown) {
            const errMsg = err instanceof Error ? err.message : String(err);
            logger.error(`[refresh] ${monthLabel} | ${view} 失败: ${errMsg}`);
            stats.failed++;
            stats.errors.push({
                view,
                month: monthLabel,
                error: errMsg,
            });
        }
    }

    return stats;
}

/**
 * 汇总统计信息
 */
function summarizeStats(
    allStats: RefreshStats[],
    totalMonths: number,
    totalElapsedMs: number
): void {
    const totalViews = allStats.reduce((sum, s) => sum + s.totalViews, 0);
    const totalSuccess = allStats.reduce((sum, s) => sum + s.success, 0);
    const totalFailed = allStats.reduce((sum, s) => sum + s.failed, 0);
    const allErrors = allStats.flatMap((s) => s.errors);

    logger.info("========================================");
    logger.info("[refresh] 聚合刷新完成");
    logger.info(`  总月份数:  ${totalMonths}`);
    logger.info(`  总视图数:  ${totalViews} (${AGGREGATE_VIEWS.length} 视图 × ${totalMonths} 月)`);
    logger.info(`  成功:      ${totalSuccess}`);
    logger.info(`  失败:      ${totalFailed}`);
    logger.info(`  总耗时:    ${(totalElapsedMs / 1000).toFixed(1)}s`);
    logger.info("========================================");

    if (allErrors.length > 0) {
        logger.warn(`以下 ${allErrors.length} 次刷新失败，请检查:`);
        for (const e of allErrors) {
            logger.warn(`  - ${e.month} / ${e.view}: ${e.error}`);
        }
    }
}

// ============================================================
// 4. Dry-Run 模式（仅生成 SQL，不连接数据库）
// ============================================================

/**
 * 仅输出 SQL 语句，不连接数据库也不执行。
 * 适用于审查 SQL 或手动拷贝到 psql 执行。
 */
function dryRun(
    start: { year: number; month: number },
    end: { year: number; month: number }
): void {
    const months = generateMonthRange(start, end);
    const pad = (n: number) => String(n).padStart(2, "0");

    console.log("-- ============================================================");
    console.log(
        `-- TimescaleDB 连续聚合刷新 SQL（Dry-Run）`
    );
    console.log(
        `-- 时间范围: ${start.year}-${pad(start.month)} → ${end.year}-${pad(end.month)}`
    );
    console.log(`-- 月份数:   ${months.length}`);
    console.log(
        `-- 视图链:   ${AGGREGATE_VIEWS.join(" → ")}`
    );
    console.log("-- ============================================================\n");

    for (const { year, month } of months) {
        const monthLabel = `${year}-${pad(month)}`;
        console.log(`-- [${monthLabel}] ----------------------------------------`);
        for (const { view, sql } of buildMonthSQLBatch(year, month)) {
            console.log(`-- ${view}`);
            console.log(sql);
        }
        console.log();
    }

    console.log(
        `-- 共生成 ${months.length * AGGREGATE_VIEWS.length} 条 SQL 语句`
    );
    console.log(
        "-- 使用 --execute 参数实际执行上述 SQL"
    );
}

// ============================================================
// 5. 主入口
// ============================================================

async function main(): Promise<void> {
    const args = parseArgs(Bun.argv.slice(2)); // Bun.argv[0] = bun, [1] = script path

    // 解析时间范围参数
    const startRaw = args.get("start") ?? `${DEFAULT_START.year}-${String(DEFAULT_START.month).padStart(2, "0")}`;
    const endRaw = args.get("end") ?? `${DEFAULT_END.year}-${String(DEFAULT_END.month).padStart(2, "0")}`;

    const start = parseYearMonth(startRaw);
    const end = parseYearMonth(endRaw);

    if (!start) {
        logger.error(`无效的起始年月: "${startRaw}"，格式应为 YYYY-MM（如 2018-09）`);
        process.exit(1);
    }
    if (!end) {
        logger.error(`无效的结束年月: "${endRaw}"，格式应为 YYYY-MM（如 2019-01）`);
        process.exit(1);
    }

    // 验证 start <= end
    const startTotal = start.year * 12 + start.month;
    const endTotal = end.year * 12 + end.month;
    if (startTotal > endTotal) {
        logger.error(
            `起始时间 (${startRaw}) 不能晚于结束时间 (${endRaw})`
        );
        process.exit(1);
    }

    const months = generateMonthRange(start, end);
    const pad = (n: number) => String(n).padStart(2, "0");

    logger.info(
        `[refresh] 时间范围: ${start.year}-${pad(start.month)} → ${end.year}-${pad(end.month)}，共 ${months.length} 个月`
    );

    // Dry-run 模式：仅输出 SQL
    const shouldExecute = args.has("execute");
    if (!shouldExecute) {
        logger.info("[refresh] Dry-Run 模式：仅生成 SQL，不执行。添加 --execute 参数实际执行。");
        dryRun(start, end);
        process.exit(0);
    }

    // 执行模式：连接数据库逐月刷新
    logger.info("[refresh] 执行模式：连接数据库并逐月刷新聚合...");

    // 动态导入 DataSource（仅在执行模式下连接数据库）
    const { AppDataSource } = await import("../db/data-source");

    // 确保 DataSource 已初始化
    if (!AppDataSource.isInitialized) {
        await AppDataSource.initialize();
        logger.info("[refresh] 数据库连接已建立");
    }

    const allStats: RefreshStats[] = [];
    const totalStartMs = Date.now();

    for (let i = 0; i < months.length; i++) {
        const { year, month } = months[i]!; // 循环条件保证 i < months.length
        const monthLabel = `${year}-${pad(month)}`;
        const progress = `[${i + 1}/${months.length}]`;

        logger.info(`\n${progress} 开始处理月份: ${monthLabel}`);

        const stats = await refreshSingleMonth(AppDataSource, year, month);
        allStats.push(stats);

        // 单月汇总
        logger.info(
            `${progress} ${monthLabel} 完成: ${stats.success}/${stats.totalViews} 成功` +
            (stats.failed > 0 ? `, ${stats.failed} 失败` : "")
        );
    }

    const totalElapsedMs = Date.now() - totalStartMs;
    summarizeStats(allStats, months.length, totalElapsedMs);

    // 如果有失败，以非零退出码退出
    const totalFailed = allStats.reduce((sum, s) => sum + s.failed, 0);
    if (totalFailed > 0) {
        process.exit(1);
    }

    // 优雅关闭数据库连接（仅在执行模式下）
    if (AppDataSource.isInitialized) {
        await AppDataSource.destroy();
        logger.info("[refresh] 数据库连接已关闭");
    }
}

// ============================================================
// 6. 启动
// ============================================================

main().catch((err) => {
    logger.error({ err }, "[refresh] 脚本执行异常");
    process.exit(1);
});
