import cluster from "node:cluster";
import { EventEmitter } from "node:events";
import { resolveWorkerCount } from "./worker-count.js";
import { writePidFile, removePidFile } from "./pid-file.js";
// ── 默认配置 ────────────────────────────────────────────────
export const DEFAULT_CLUSTER_CONFIG = {
    workers: "auto",
    autoRestart: true,
    maxRestarts: 5,
    restartWindow: 60_000,
    restartBaseDelay: 1_000,
    restartMaxDelay: 30_000,
    healthCheck: {
        enabled: true,
        interval: 15_000,
        timeout: 30_000,
    },
    reload: {
        workerDelay: 2_000,
        readyTimeout: 30_000,
        shutdownTimeout: 10_000,
    },
    pidFile: ".vext.pid",
    titlePrefix: "vext",
    sticky: "none",
};
// ── 主类 ────────────────────────────────────────────────────
export class ClusterMaster extends EventEmitter {
    /** Worker 元数据表（key = cluster.Worker.id） */
    workers = new Map();
    nextWorkerId = 1;
    /** 窗口内的重启时间戳（用于频率保护） */
    restartTimestamps = [];
    /** 是否正在优雅关闭 */
    isShuttingDown = false;
    /** 是否正在执行 Rolling Restart */
    isReloading = false;
    /** 健康检查定时器 */
    healthCheckTimer = null;
    /** 已注册的信号处理器引用（用于 cleanup 时移除） */
    signalHandlers = [];
    /** 最新一次收到的 Worker 指标快照 */
    workerMetrics = new Map();
    /** 运行时配置（只读） */
    config;
    /** 实际计算出的 Worker 数量 */
    workerCount = 0;
    constructor(config = {}) {
        super();
        // 深合并嵌套对象（healthCheck / reload），其余字段浅合并
        const healthCheck = {
            ...DEFAULT_CLUSTER_CONFIG.healthCheck,
            ...config.healthCheck,
        };
        const reload = {
            ...DEFAULT_CLUSTER_CONFIG.reload,
            ...config.reload,
        };
        this.config = {
            ...DEFAULT_CLUSTER_CONFIG,
            ...config,
            healthCheck,
            reload,
        };
    }
    // ════════════════════════════════════════════════════════
    //  公共 API
    // ════════════════════════════════════════════════════════
    /**
     * start — 启动 Cluster Master
     *
     * 完整启动流程：
     *   1. 计算 Worker 数量
     *   2. 设置进程标题
     *   3. 写入 PID 文件
     *   4. 配置 cluster 调度策略
     *   5. 串行 fork Worker（首个失败 Fail Fast）
     *   6. 注册 Worker exit 事件
     *   7. 注册 OS 信号处理
     *   8. 启动健康检查（如果启用）
     *
     * @throws 首个 Worker 启动失败时抛出错误
     */
    async start() {
        this.workerCount = resolveWorkerCount(this.config.workers);
        // ── 设置进程标题 ──────────────────────────────────────
        process.title = `${this.config.titlePrefix}:master`;
        // ── 写入 PID 文件 ────────────────────────────────────
        const pidResult = writePidFile(this.config.pidFile);
        if (!pidResult.ok) {
            throw new Error(`[cluster] ${pidResult.error}`);
        }
        // ── 配置 cluster 调度策略 ─────────────────────────────
        //
        // Linux + 非 sticky: SCHED_NONE
        //   → 内核 SO_REUSEPORT，由内核分配连接，性能最优
        //
        // 其他情况: SCHED_RR (Round-Robin)
        //   → Node.js 内置负载均衡
        //
        if (process.platform === "linux" && this.config.sticky === "none") {
            cluster.schedulingPolicy = cluster.SCHED_NONE;
        }
        else {
            cluster.schedulingPolicy = cluster.SCHED_RR;
        }
        console.log(`[cluster] master ${process.pid} starting ${this.workerCount} workers`);
        // ── 串行 fork Worker ──────────────────────────────────
        //
        // 逐个启动，好处：
        //   - 避免启动时 DB 连接风暴
        //   - 首个 Worker 失败可立即中止
        //   - 首个 Worker 可执行一次性操作（如 DB 迁移）
        //
        for (let i = 0; i < this.workerCount; i++) {
            try {
                await this.forkWorker();
            }
            catch (err) {
                if (i === 0) {
                    // 首个 Worker 失败 → Fail Fast
                    console.error(`[cluster] ❌ first worker failed: ${err.message}`);
                    this.cleanup();
                    throw err;
                }
                // 后续 Worker 失败 → 警告但继续
                console.warn(`[cluster] worker ${i + 1} failed to start: ${err.message}`);
            }
        }
        // ── 注册 Worker exit 全局监听 ─────────────────────────
        cluster.on("exit", (worker, code, signal) => {
            this.handleWorkerExit(worker, code, signal);
        });
        // ── 注册 OS 信号处理 ──────────────────────────────────
        this.registerSignals();
        // ── 启动健康检查 ──────────────────────────────────────
        if (this.config.healthCheck.enabled) {
            this.startHealthCheck();
        }
        const readyCount = this.getReadyWorkerCount();
        console.log(`[cluster] ✅ ${readyCount}/${this.workerCount} workers ready`);
    }
    /**
     * gracefulShutdown — 优雅关闭所有 Worker
     *
     * 流程：
     *   1. 标记 isShuttingDown（阻止 auto-restart 和 rolling restart）
     *   2. 停止健康检查定时器
     *   3. 向所有存活 Worker 发送 shutdown 消息
     *   4. 等待所有 Worker 退出（超时则 SIGKILL）
     *   5. 清理 PID 文件
     *   6. Master 退出
     *
     * @param trigger 触发源（如 'SIGTERM'、'SIGINT'，用于日志输出）
     */
    async gracefulShutdown(trigger) {
        if (this.isShuttingDown)
            return;
        this.isShuttingDown = true;
        console.log(`[cluster] graceful shutdown triggered by ${trigger}`);
        // 停止健康检查
        this.stopHealthCheck();
        // 移除信号处理器
        this.removeSignalHandlers();
        // 通知所有 Worker 关闭
        const promises = [];
        for (const [id] of this.workers) {
            const worker = cluster.workers?.[id];
            if (worker && !worker.isDead()) {
                const meta = this.workers.get(id);
                if (meta)
                    meta.state = "draining";
                worker.send({
                    type: "shutdown",
                    timeout: this.config.reload.shutdownTimeout,
                });
                promises.push(this.waitForWorkerExit(worker, this.config.reload.shutdownTimeout));
            }
        }
        await Promise.allSettled(promises);
        this.cleanup();
        console.log("[cluster] all workers stopped, master exiting");
        process.exit(0);
    }
    /**
     * rollingRestart — 零停机滚动重启所有 Worker
     *
     * 流程（对每个旧 Worker 串行执行）：
     *   1. Fork 新 Worker（加载最新代码）
     *   2. 等待新 Worker ready（超时 → 杀新 Worker，跳过本轮）
     *   3. 新 Worker 就绪 → 通知旧 Worker shutdown
     *   4. 等待旧 Worker 退出（超时 → SIGKILL）
     *   5. 等待 workerDelay 再处理下一个
     *
     * 关键保证：
     *   - 在替换过程中始终保持至少 N-1 个 Worker 服务
     *   - 替换失败时旧 Worker 保持运行（不会减少可用 Worker）
     *   - 如果在重启过程中收到 SIGTERM，立即停止替换并转入 gracefulShutdown
     *
     * @param trigger 触发源（如 'SIGHUP'、'SIGUSR2'，用于日志输出）
     */
    async rollingRestart(trigger) {
        if (this.isReloading) {
            console.log("[cluster] rolling restart already in progress, skipping");
            return;
        }
        if (this.isShuttingDown)
            return;
        this.isReloading = true;
        const oldWorkerIds = [...this.workers.keys()];
        console.log(`[cluster] rolling restart triggered by ${trigger}, replacing ${oldWorkerIds.length} workers`);
        this.emit("reload-start", {
            trigger,
            workerCount: oldWorkerIds.length,
        });
        let replaced = 0;
        for (const oldId of oldWorkerIds) {
            if (this.isShuttingDown) {
                console.log("[cluster] shutdown requested during rolling restart, aborting");
                break;
            }
            try {
                // Fork 新 Worker
                const _newWorker = await this.forkWorker();
                // 新 Worker ready → 通知旧 Worker shutdown
                const oldWorker = cluster.workers?.[oldId];
                if (oldWorker && !oldWorker.isDead()) {
                    const oldMeta = this.workers.get(oldId);
                    if (oldMeta)
                        oldMeta.state = "draining";
                    oldWorker.send({
                        type: "shutdown",
                        timeout: this.config.reload.shutdownTimeout,
                    });
                    await this.waitForWorkerExit(oldWorker, this.config.reload.shutdownTimeout);
                }
                replaced++;
                // 替换间隔（防止连续 fork 压力过大）
                if (replaced < oldWorkerIds.length) {
                    await sleep(this.config.reload.workerDelay);
                }
            }
            catch (err) {
                console.error(`[cluster] failed to replace worker ${oldId}: ${err.message}`);
                // 替换失败 → 旧 Worker 保持运行，继续处理下一个
            }
        }
        this.isReloading = false;
        console.log(`[cluster] rolling restart complete, ${replaced}/${oldWorkerIds.length} replaced`);
        this.emit("reload-complete", {
            replaced,
            total: oldWorkerIds.length,
        });
    }
    /**
     * broadcast — 向所有存活 Worker 广播消息
     *
     * @param payload 广播内容（必须可 JSON 序列化）
     */
    broadcast(payload) {
        for (const [id] of this.workers) {
            const worker = cluster.workers?.[id];
            if (worker && !worker.isDead()) {
                worker.send({ type: "broadcast", payload });
            }
        }
    }
    /**
     * getWorkerCount — 获取当前 Worker 总数
     */
    getWorkerCount() {
        return this.workers.size;
    }
    /**
     * getReadyWorkerCount — 获取当前处于 ready 状态的 Worker 数量
     */
    getReadyWorkerCount() {
        let count = 0;
        for (const meta of this.workers.values()) {
            if (meta.state === "ready")
                count++;
        }
        return count;
    }
    /**
     * getWorkerMetas — 获取所有 Worker 的元数据快照（只读）
     */
    getWorkerMetas() {
        return this.workers;
    }
    /**
     * getWorkerMetrics — 获取最近一次上报的 Worker 指标
     */
    getLatestMetrics() {
        return this.workerMetrics;
    }
    /**
     * isRunning — Master 是否正在运行（未处于关闭/重载状态）
     */
    isRunning() {
        return !this.isShuttingDown;
    }
    /**
     * getTargetWorkerCount — 获取配置计算出的目标 Worker 数量
     */
    getTargetWorkerCount() {
        return this.workerCount;
    }
    // ════════════════════════════════════════════════════════
    //  内部方法
    // ════════════════════════════════════════════════════════
    /**
     * forkWorker — Fork 单个 Worker 并等待就绪
     *
     * 流程：
     *   1. cluster.fork() 创建新 Worker 进程
     *   2. 设置 WorkerMeta（state = 'starting'）
     *   3. 发送 set-title 消息
     *   4. 注册 IPC 消息监听
     *   5. 等待 Worker 发送 'ready' 消息（超时则拒绝）
     *
     * 环境变量传递：
     *   - VEXT_WORKER_ID: Worker 编号（从 1 开始递增）
     *   - VEXT_MODE: 'start'（触发 bootstrap 自执行入口）
     *
     * @returns fork 出的 cluster.Worker 实例
     * @throws Worker 在 readyTimeout 内未就绪或在就绪前退出
     */
    async forkWorker() {
        const nextId = this.nextWorkerId++;
        const worker = cluster.fork({
            VEXT_WORKER_ID: String(nextId),
            VEXT_MODE: "start",
        });
        const meta = {
            id: worker.id,
            startTime: Date.now(),
            restartCount: 0,
            lastHeartbeat: Date.now(),
            state: "starting",
        };
        this.workers.set(worker.id, meta);
        // 设置 Worker 进程标题
        try {
            worker.send({
                type: "set-title",
                title: `${this.config.titlePrefix}:worker:${nextId}`,
            });
        }
        catch {
            // Worker 可能在 send 之前就退出了
        }
        // 注册 IPC 消息监听
        worker.on("message", (msg) => this.handleWorkerMessage(worker, msg));
        // 等待 Worker 就绪
        await this.waitForWorkerReady(worker);
        this.emit("worker-ready", {
            workerId: worker.id,
            pid: worker.process.pid,
        });
        return worker;
    }
    /**
     * waitForWorkerReady — 等待 Worker 发送 'ready' 消息
     *
     * 使用 Promise 封装事件驱动的等待逻辑：
     *   - 收到 'ready' → resolve
     *   - 超时 → reject
     *   - Worker 提前退出 → reject
     *
     * @param worker 目标 Worker
     * @throws 超时或 Worker 提前退出
     */
    waitForWorkerReady(worker) {
        return new Promise((resolve, reject) => {
            let settled = false;
            const timeout = setTimeout(() => {
                if (settled)
                    return;
                settled = true;
                cleanup();
                reject(new Error(`worker ${worker.id} ready timeout (${this.config.reload.readyTimeout}ms)`));
            }, this.config.reload.readyTimeout);
            const onMessage = (msg) => {
                if (settled ||
                    typeof msg !== "object" ||
                    msg === null ||
                    msg.type !== "ready") {
                    return;
                }
                settled = true;
                cleanup();
                const meta = this.workers.get(worker.id);
                if (meta)
                    meta.state = "ready";
                resolve();
            };
            const onExit = () => {
                if (settled)
                    return;
                settled = true;
                cleanup();
                reject(new Error(`worker ${worker.id} exited before ready`));
            };
            const cleanup = () => {
                clearTimeout(timeout);
                worker.removeListener("message", onMessage);
                worker.removeListener("exit", onExit);
            };
            worker.on("message", onMessage);
            worker.once("exit", onExit);
        });
    }
    /**
     * handleWorkerMessage — 处理 Worker → Master 的 IPC 消息
     *
     * 消息类型：
     *   - ready:           Worker 就绪（由 waitForWorkerReady 处理）
     *   - heartbeat:       更新 lastHeartbeat 时间戳
     *   - metrics:         存储最新指标快照
     *   - request-restart: Worker 主动请求重启（如内存超阈值）
     */
    handleWorkerMessage(worker, msg) {
        if (typeof msg !== "object" || msg === null)
            return;
        const message = msg;
        switch (message.type) {
            case "heartbeat": {
                const meta = this.workers.get(worker.id);
                if (meta) {
                    meta.lastHeartbeat = Date.now();
                }
                break;
            }
            case "metrics": {
                this.workerMetrics.set(worker.id, message.data);
                break;
            }
            case "request-restart": {
                console.log(`[cluster] worker ${worker.id} requested restart: ${message.reason}`);
                // 异步替换：fork 新 Worker → 关闭旧 Worker
                this.replaceWorker(worker).catch((err) => {
                    console.error(`[cluster] failed to replace worker ${worker.id}: ${err.message}`);
                });
                break;
            }
            // 'ready' 在 waitForWorkerReady 中处理，此处忽略
            case "ready":
                break;
        }
    }
    /**
     * replaceWorker — 替换单个 Worker（用于 request-restart 场景）
     *
     * 流程：
     *   1. Fork 新 Worker
     *   2. 等待新 Worker ready
     *   3. 通知旧 Worker shutdown
     *   4. 等待旧 Worker 退出
     */
    async replaceWorker(oldWorker) {
        if (this.isShuttingDown || this.isReloading)
            return;
        try {
            await this.forkWorker();
            if (!oldWorker.isDead()) {
                const meta = this.workers.get(oldWorker.id);
                if (meta)
                    meta.state = "draining";
                oldWorker.send({
                    type: "shutdown",
                    timeout: this.config.reload.shutdownTimeout,
                });
                await this.waitForWorkerExit(oldWorker, this.config.reload.shutdownTimeout);
            }
        }
        catch (err) {
            console.error(`[cluster] worker replacement failed: ${err.message}`);
        }
    }
    /**
     * handleWorkerExit — Worker 退出事件处理
     *
     * 决策树：
     *   1. 正在关闭 → 跳过（gracefulShutdown 已处理）
     *   2. 状态为 draining → 跳过（正常替换流程）
     *   3. autoRestart=false → 检查是否所有 Worker 都已退出
     *   4. 频率保护命中 → 暂停自动重启 + 发射事件
     *   5. 正常场景 → 指数退避后重启
     */
    handleWorkerExit(worker, code, signal) {
        const meta = this.workers.get(worker.id);
        this.workers.delete(worker.id);
        this.workerMetrics.delete(worker.id);
        const exitInfo = signal ? `signal ${signal}` : `code ${code}`;
        console.log(`[cluster] worker ${worker.id} (pid: ${worker.process.pid}) exited: ${exitInfo}`);
        this.emit("worker-exit", {
            workerId: worker.id,
            code,
            signal,
        });
        // 正在关闭 → 不重启
        if (this.isShuttingDown)
            return;
        // 正常替换流程中的旧 Worker 退出 → 不重启
        if (meta?.state === "draining")
            return;
        // 自动重启禁用 → 检查是否所有 Worker 已退出
        if (!this.config.autoRestart) {
            this.checkAllDead();
            return;
        }
        // 频率保护
        if (this.isRestartThrottled()) {
            console.error(`[cluster] ❌ restart rate exceeded (${this.config.maxRestarts} in ${this.config.restartWindow}ms), pausing auto-restart`);
            this.emit("restart-throttled", {
                workerId: worker.id,
                code,
                signal,
            });
            this.checkAllDead();
            return;
        }
        // 指数退避重启
        const delay = this.calculateRestartDelay();
        console.log(`[cluster] restarting worker in ${delay}ms...`);
        setTimeout(async () => {
            if (this.isShuttingDown)
                return;
            try {
                await this.forkWorker();
            }
            catch (err) {
                console.error(`[cluster] failed to spawn replacement: ${err.message}`);
                this.checkAllDead();
            }
        }, delay);
    }
    // ── 频率保护 + 指数退避 ──────────────────────────────────
    /**
     * isRestartThrottled — 检测是否超过重启频率限制
     *
     * 在 restartWindow 毫秒窗口内，重启次数是否超过 maxRestarts。
     * 每次调用时记录当前时间戳并清理过期记录。
     */
    isRestartThrottled() {
        const now = Date.now();
        this.restartTimestamps.push(now);
        // 清理窗口外的记录
        this.restartTimestamps = this.restartTimestamps.filter((t) => t > now - this.config.restartWindow);
        return this.restartTimestamps.length > this.config.maxRestarts;
    }
    /**
     * calculateRestartDelay — 计算指数退避延迟
     *
     * delay = baseDelay × 2^(consecutiveRestarts - 1)
     * 上限 = maxDelay（默认 30s）
     *
     * 避免崩溃循环时频繁 fork 消耗系统资源。
     */
    calculateRestartDelay() {
        const exponent = Math.max(0, this.restartTimestamps.length - 1);
        return Math.min(this.config.restartBaseDelay * 2 ** exponent, this.config.restartMaxDelay);
    }
    // ── 信号处理 ──────────────────────────────────────────────
    /**
     * registerSignals — 注册 OS 信号处理器
     *
     * | 信号     | 行为           |
     * |---------|---------------|
     * | SIGTERM | 优雅关闭       |
     * | SIGINT  | 同 SIGTERM     |
     * | SIGHUP  | Rolling Restart |
     * | SIGUSR2 | 同 SIGHUP      |
     *
     * Windows 不支持 SIGHUP / SIGUSR2，仅注册 SIGTERM / SIGINT。
     */
    registerSignals() {
        const registerHandler = (signal, handler) => {
            process.on(signal, handler);
            this.signalHandlers.push({ signal, handler });
        };
        registerHandler("SIGTERM", () => {
            this.gracefulShutdown("SIGTERM").catch((err) => {
                console.error(`[cluster] shutdown error: ${err.message}`);
                process.exit(1);
            });
        });
        registerHandler("SIGINT", () => {
            this.gracefulShutdown("SIGINT").catch((err) => {
                console.error(`[cluster] shutdown error: ${err.message}`);
                process.exit(1);
            });
        });
        // Windows 不支持 SIGHUP / SIGUSR2
        if (process.platform !== "win32") {
            registerHandler("SIGHUP", () => {
                this.rollingRestart("SIGHUP").catch((err) => {
                    console.error(`[cluster] rolling restart error: ${err.message}`);
                });
            });
            registerHandler("SIGUSR2", () => {
                this.rollingRestart("SIGUSR2").catch((err) => {
                    console.error(`[cluster] rolling restart error: ${err.message}`);
                });
            });
        }
    }
    /**
     * removeSignalHandlers — 移除所有已注册的信号处理器
     */
    removeSignalHandlers() {
        for (const { signal, handler } of this.signalHandlers) {
            process.removeListener(signal, handler);
        }
        this.signalHandlers = [];
    }
    // ── 健康检查 ──────────────────────────────────────────────
    /**
     * startHealthCheck — 启动定期健康检查
     *
     * 检查逻辑（每 interval 毫秒执行一次）：
     *   1. 遍历所有 ready 状态的 Worker
     *   2. 检查 lastHeartbeat 是否超过 timeout
     *   3. 超时 → 标记 Worker 为死亡，SIGKILL 终止
     *      （handleWorkerExit 会触发自动重启）
     */
    startHealthCheck() {
        this.healthCheckTimer = setInterval(() => {
            if (this.isShuttingDown)
                return;
            const now = Date.now();
            for (const [id, meta] of this.workers) {
                // 只检查已就绪的 Worker（starting 状态的由 readyTimeout 覆盖）
                if (meta.state !== "ready")
                    continue;
                const elapsed = now - meta.lastHeartbeat;
                if (elapsed > this.config.healthCheck.timeout) {
                    console.warn(`[cluster] worker ${id} heartbeat timeout (${Math.round(elapsed / 1000)}s > ${Math.round(this.config.healthCheck.timeout / 1000)}s), killing`);
                    this.emit("heartbeat-timeout", {
                        workerId: id,
                        lastHeartbeat: meta.lastHeartbeat,
                    });
                    const worker = cluster.workers?.[id];
                    if (worker && !worker.isDead()) {
                        meta.state = "dead";
                        worker.process.kill("SIGKILL");
                    }
                }
            }
        }, this.config.healthCheck.interval);
        // 不阻止 Master 进程退出
        this.healthCheckTimer.unref();
    }
    /**
     * stopHealthCheck — 停止健康检查定时器
     */
    stopHealthCheck() {
        if (this.healthCheckTimer) {
            clearInterval(this.healthCheckTimer);
            this.healthCheckTimer = null;
        }
    }
    // ── Worker 退出等待 ──────────────────────────────────────
    /**
     * waitForWorkerExit — 等待 Worker 退出（带超时保护）
     *
     * 超时后对 Worker 发送 SIGKILL 强制终止。
     */
    waitForWorkerExit(worker, timeout) {
        return new Promise((resolve) => {
            let resolved = false;
            const done = () => {
                if (!resolved) {
                    resolved = true;
                    resolve();
                }
            };
            worker.once("exit", () => {
                clearTimeout(timer);
                done();
            });
            const timer = setTimeout(() => {
                if (!worker.isDead()) {
                    console.warn(`[cluster] worker ${worker.id} shutdown timeout (${timeout}ms), SIGKILL`);
                    try {
                        worker.process.kill("SIGKILL");
                    }
                    catch {
                        // 进程可能已经退出
                    }
                }
                done();
            }, timeout);
            // 不阻止进程退出
            timer.unref();
        });
    }
    // ── 清理 ──────────────────────────────────────────────────
    /**
     * checkAllDead — 检查是否所有 Worker 已退出
     *
     * 当所有 Worker 都已退出且不在关闭/重载流程中时，
     * 发射 'all-workers-dead' 事件。
     */
    checkAllDead() {
        if (this.workers.size === 0) {
            this.emit("all-workers-dead");
        }
    }
    /**
     * cleanup — 清理资源
     *
     * 关闭定时器 + 移除信号处理器 + 删除 PID 文件。
     */
    cleanup() {
        this.stopHealthCheck();
        this.removeSignalHandlers();
        const pidResult = removePidFile(this.config.pidFile);
        if (!pidResult.ok && pidResult.error) {
            console.warn(`[cluster] ${pidResult.error}`);
        }
    }
}
// ── 辅助函数 ────────────────────────────────────────────────
/**
 * sleep — 延迟指定毫秒
 */
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
