import * as fs from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname } from "node:path";
import * as os from "node:os";
import cluster from "node:cluster";
import net from "node:net";
import repl from "node:repl";
import chalk from "chalk";
import chalkTemplate from "chalk-template";
import semver from "semver";
import Logger from "../services/logger.js";
import loadConfig from "../config/load.js";
import { envOption } from "../env.js";
import { showMachineInfo } from "../misc/show-machine-info.js";
import { db, initDb } from "../db/postgre.js";
const _filename = fileURLToPath(import.meta.url);
const _dirname = dirname(_filename);
const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, "utf-8"));
const logger = new Logger("core", "cyan");
const bootLogger = logger.createSubLogger("boot", "magenta", false);
const themeColor = chalk.hex("#31748f");
function greet() {
    if (!envOption.quiet) {
        console.log(chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`);
    }
    bootLogger.info(`Iceshrimp v${meta.version}, initializing...`, null, true);
}
/**
 * Init master process
 */ export async function masterMain() {
    let config;
    // initialize app
    try {
        greet();
        showEnvironment();
        await showMachineInfo(bootLogger);
        showNodejsVersion();
        config = loadConfigBoot();
        await connectDb();
        if (config.shellAddress !== undefined) spawnShell(config.shellAddress);
    } catch (e1) {
        bootLogger.error("Fatal error occurred during initialization", null, true);
        bootLogger.error(e1);
        process.exit(1);
    }
    bootLogger.succ("Iceshrimp initialized");
    if (!envOption.disableClustering) {
        await spawnWorkers(config.clusterLimit);
    }
    bootLogger.succ(`Now listening on port ${config.port} on ${config.url} - using ${config.domain}`, null, true);
    if (!envOption.noDaemons && !config.onlyQueueProcessor) {
        import("../daemons/server-stats.js").then((x)=>x.default());
        import("../daemons/queue-stats.js").then((x)=>x.default());
        import("../daemons/janitor.js").then((x)=>x.default());
    }
}
function showEnvironment() {
    const env = process.env.NODE_ENV;
    const logger = bootLogger.createSubLogger("env");
    logger.info(typeof env === "undefined" ? "NODE_ENV is not set" : `NODE_ENV: ${env}`);
    if (env !== "production") {
        logger.warn("The environment is not in production mode.");
        logger.warn("DO NOT USE FOR PRODUCTION PURPOSE!", null, true);
    }
}
function showNodejsVersion() {
    const nodejsLogger = bootLogger.createSubLogger("nodejs");
    nodejsLogger.info(`Version ${process.version} detected.`);
    const minVersion = fs.readFileSync(`${_dirname}/../../../../.node-version`, "utf-8").trim();
    if (semver.lt(process.version, minVersion)) {
        nodejsLogger.error(`At least Node.js ${minVersion} required!`);
        process.exit(1);
    }
}
function loadConfigBoot() {
    const configLogger = bootLogger.createSubLogger("config");
    let config;
    try {
        config = loadConfig();
    } catch (exception) {
        if (exception.code === "ENOENT") {
            configLogger.error("Configuration file not found", null, true);
            process.exit(1);
        } else if (e instanceof Error) {
            configLogger.error(e.message);
            process.exit(1);
        }
        throw exception;
    }
    configLogger.succ("Loaded");
    return config;
}
async function connectDb() {
    const dbLogger = bootLogger.createSubLogger("db");
    // Try to connect to DB
    try {
        dbLogger.info("Connecting...");
        await initDb();
        const v = await db.query("SHOW server_version").then((x)=>x[0].server_version);
        dbLogger.succ(`Connected: v${v}`);
    } catch (e1) {
        dbLogger.error("Cannot connect", null, true);
        dbLogger.error(e1);
        process.exit(1);
    }
}
async function spawnWorkers(limit = 1) {
    const workers = Math.min(limit, os.cpus().length);
    bootLogger.info(`Starting ${workers} worker${workers === 1 ? "" : "s"}...`);
    await Promise.all([
        ...Array(workers)
    ].map(spawnWorker));
    bootLogger.succ("All workers started");
}
function spawnWorker() {
    return new Promise((res)=>{
        const worker = cluster.fork();
        worker.on("message", (message)=>{
            if (message === "listenFailed") {
                bootLogger.error("The server Listen failed due to the previous error.");
                process.exit(1);
            }
            if (message !== "ready") return;
            res();
        });
    });
}
function spawnShell(address) {
    logger.info(`Spawning debug shell on ${address}`);
    if (typeof address == "string") {
        try {
            fs.unlinkSync(address);
        } catch  {}
    }
    net.createServer().unref().listen(address).on("connection", (socket)=>{
        const r = repl.start({
            input: socket,
            output: socket
        });
        socket.on("close", ()=>r.close());
        socket.on("error", ()=>r.close());
        r.on("close", ()=>socket.destroy());
        if (!envOption.disableClustering) {
            r.defineCommand("worker", (iid)=>{
                r.close();
                const id = parseInt(iid.trim());
                cluster.workers[id].send("debug-shell", socket);
            });
        }
    }).on("error", (error)=>logger.error(error));
}
