"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const bot_js_1 = require("./app/bot.js");
const PaymentMonitor_js_1 = require("./application/subscriptions/PaymentMonitor.js");
const SubscriptionService_js_1 = require("./application/subscriptions/SubscriptionService.js");
const env_js_1 = require("./config/env.js");
const connection_js_1 = require("./infrastructure/database/connection.js");
const BalanceService_js_1 = require("./infrastructure/payments/BalanceService.js");
const Crypto_js_1 = require("./infrastructure/payments/Crypto.js");
const WalletService_js_1 = require("./infrastructure/payments/WalletService.js");
const server_js_1 = require("./admin-api/server.js");
const KeepAliveService_js_1 = require("./infrastructure/keepalive/KeepAliveService.js");
const Logger_js_1 = require("./shared/logger/Logger.js");
const SupportModule_js_1 = require("./support/SupportModule.js");
function serializeError(error) {
    if (error instanceof Error) {
        return {
            name: error.name,
            message: error.message,
            stack: error.stack,
            code: "code" in error ? error.code : undefined
        };
    }
    return { error };
}
const config = (0, env_js_1.loadEnv)();
const logger = new Logger_js_1.Logger(config.logLevel, {
    logDir: config.logDir,
    fileLogging: config.logToFile
});
let signalsBot;
let paymentMonitor;
let supportModule;
let adminApiServer;
let keepAliveService;
let isShuttingDown = false;
async function main() {
    const databaseConnected = await (0, connection_js_1.connectDatabase)(config.mongoUri, logger);
    const cryptoBox = config.paymentEncryptionSecret ? new Crypto_js_1.CryptoBox(config.paymentEncryptionSecret) : undefined;
    const walletService = cryptoBox ? new WalletService_js_1.WalletService(cryptoBox) : undefined;
    const balanceService = new BalanceService_js_1.BalanceService(config, logger);
    const subscriptionService = new SubscriptionService_js_1.SubscriptionService(config, databaseConnected ? walletService : undefined, databaseConnected && cryptoBox ? balanceService : undefined, logger);
    supportModule = databaseConnected ? (0, SupportModule_js_1.createSupportModule)(config, logger) : undefined;
    signalsBot = (0, bot_js_1.createSignalsBot)(config, logger, subscriptionService, supportModule);
    paymentMonitor =
        subscriptionService.isEnabled() && balanceService
            ? new PaymentMonitor_js_1.PaymentMonitor(signalsBot.bot, subscriptionService, balanceService, config.adminTelegramIds, config.paymentPollIntervalSeconds, config.paymentConfirmations, logger)
            : undefined;
    await signalsBot.bootstrap();
    signalsBot.arbitrageCleanupWorker.start(60_000);
    paymentMonitor?.start();
    if (supportModule) {
        startSupportBot();
    }
    if (!cryptoBox) {
        logger.warn("Signals admin API disabled: PAYMENT_ENCRYPTION_SECRET not set.");
        if (config.keepAliveUrl) {
            logger.warn("Keep-alive URL is configured but the admin API is disabled. The keep-alive service will not start because there is no HTTP server to ping.");
        }
    }
    else {
        adminApiServer = (0, server_js_1.createAdminApiServer)(config, cryptoBox, logger, signalsBot.bot, supportModule);
        if (adminApiServer) {
            const { httpServer, port, host } = adminApiServer;
            httpServer.listen(port, host, () => {
                logger.info(`Signals admin API listening on ${host}:${port}.`);
                logger.info(`Health check: http://${host}:${port}/health`);
                if (config.keepAliveUrl) {
                    keepAliveService = new KeepAliveService_js_1.KeepAliveService({
                        url: config.keepAliveUrl,
                        intervalMs: config.keepAliveIntervalMs,
                        requestTimeoutMs: config.keepAliveRequestTimeoutMs,
                        logger
                    });
                    keepAliveService.start();
                }
            });
        }
    }
    if (!config.keepAliveUrl) {
        logger.info("Keep-alive service disabled: KEEP_ALIVE_URL not set. Consider setting it (or using an external uptime monitor) to prevent the hosting platform from sleeping the service.");
    }
    startProcessHealthLogger(logger);
    logger.info("Starting Quant Whisper Telegram bot polling.");
    void signalsBot.bot.launch({ dropPendingUpdates: true }, () => {
        const username = signalsBot?.bot.botInfo?.username;
        logger.info(`Quant Whisper Telegram bot started${username ? ` as @${username}` : ""}.`);
    }).catch((error) => {
        logger.error("Could not start Telegram bot. Verify TELEGRAM_BOT_TOKEN and network access to api.telegram.org.", serializeError(error));
        process.exitCode = 1;
    });
}
function startSupportBot() {
    if (!supportModule)
        return;
    supportModule.bot
        .start()
        .then(() => {
        logger.info("Support bot started successfully.");
    })
        .catch((error) => {
        logger.error("Failed to start support bot. The main signal bot will continue running.", serializeError(error));
    });
}
const FORCE_EXIT_MS = 10_000;
const shutdown = async (signal) => {
    if (isShuttingDown)
        return;
    isShuttingDown = true;
    logger.info(`Received ${signal}; stopping bot and admin API.`);
    const forceExitTimer = setTimeout(() => {
        logger.error(`Shutdown did not complete within ${FORCE_EXIT_MS}ms; forcing exit.`);
        process.exit(1);
    }, FORCE_EXIT_MS);
    forceExitTimer.unref();
    try {
        signalsBot?.arbitrageCleanupWorker.stop();
    }
    catch (error) {
        logger.error("Error stopping arbitrage cleanup worker.", serializeError(error));
    }
    try {
        paymentMonitor?.stop();
    }
    catch (error) {
        logger.error("Error stopping payment monitor.", serializeError(error));
    }
    try {
        signalsBot?.bot.stop(signal);
    }
    catch (error) {
        logger.error("Error stopping signal bot.", serializeError(error));
    }
    try {
        await supportModule?.bot.stop();
    }
    catch (error) {
        logger.error("Error stopping support bot.", serializeError(error));
    }
    try {
        await adminApiServer?.close();
    }
    catch (error) {
        logger.error("Error closing admin API server.", serializeError(error));
    }
    try {
        keepAliveService?.stop();
    }
    catch (error) {
        logger.error("Error stopping keep-alive service.", serializeError(error));
    }
    try {
        await (0, connection_js_1.disconnectDatabase)();
    }
    catch (error) {
        logger.error("Error disconnecting database.", serializeError(error));
    }
    clearTimeout(forceExitTimer);
    logProcessHealth(logger, "final");
    logger.info("Shutdown complete.");
    if (signal !== "uncaughtException") {
        process.exit(0);
    }
};
function logProcessHealth(logger, tag) {
    const mem = process.memoryUsage();
    logger.info("Process health.", {
        tag,
        uptimeSec: Math.floor(process.uptime()),
        rssMb: Math.round(mem.rss / 1024 / 1024),
        heapTotalMb: Math.round(mem.heapTotal / 1024 / 1024),
        heapUsedMb: Math.round(mem.heapUsed / 1024 / 1024),
        externalMb: Math.round(mem.external / 1024 / 1024)
    });
}
function startProcessHealthLogger(logger) {
    logProcessHealth(logger, "startup");
    const intervalMs = 5 * 60 * 1000; // 5 minutes
    const timer = setInterval(() => logProcessHealth(logger), intervalMs);
    timer.unref?.();
}
process.once("SIGINT", () => void shutdown("SIGINT"));
process.once("SIGTERM", () => void shutdown("SIGTERM"));
process.on("uncaughtException", (error) => {
    logger.error("Uncaught exception; exiting to avoid undefined state.", serializeError(error));
    void shutdown("uncaughtException").finally(() => {
        process.exit(1);
    });
});
process.on("unhandledRejection", (reason, promise) => {
    logger.error("Unhandled promise rejection.", {
        reason: reason instanceof Error ? serializeError(reason) : String(reason),
        promise: String(promise)
    });
    // Keep the process alive because Telegraf/Mongoose transient errors can surface here.
    // If the rejection is fatal, a subsequent tick will log further errors and the process
    // manager should restart the service.
});
main().catch((error) => {
    logger.error("Unexpected error during startup.", serializeError(error));
    process.exit(1);
});
