"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.logFlags = void 0;
const log4js_1 = __importDefault(require("log4js"));
const fs_1 = require("fs");
const utils = __importStar(require("../utils"));
const os_1 = __importDefault(require("os"));
const fs = require('fs');
const http = __importStar(require("../http"));
const nestedCounters_1 = require("../utils/nestedCounters");
const lib_types_1 = require("@shardeum-foundation/lib-types");
const log4jsExtend = require('log4js-extend');
const url_1 = require("url");
const debugMiddleware_1 = require("../network/debugMiddleware");
const network_1 = require("../network");
const Context_1 = require("../p2p/Context");
const path_1 = __importDefault(require("path"));
const customHttpFunctions_1 = require("../http/customHttpFunctions");
exports.logFlags = {
    debug: true,
    fatal: true,
    verbose: true,
    info: true,
    console: true,
    error: true,
    playback: false,
    playback_trace: false,
    playback_debug: false,
    net_trace: false,
    // main:true,
    // main_error:true,
    // main_debug:true,
    // main_trace:true,
    p2pNonFatal: true,
    newFilter: false,
    important_as_error: true,
    important_as_fatal: true,
    net_rust: false,
    net_verbose: false,
    net_stats: false,
    dapp_verbose: false,
    profiling_verbose: false,
    aalg: false,
    shardedCache: false,
    lost: false,
    rotation: false,
    seqdiagram: false,
    txCancel: false,
    getLocalOrRemote: false,
    verboseNestedCounters: false,
    node_rotation_debug: false,
    p2pSyncDebug: false,
    p2pExtraHeavyLogs: false,
};
const filePath1 = path_1.default.join(process.cwd(), 'data-logs', 'cycleRecords1.txt');
const filePath2 = path_1.default.join(process.cwd(), 'data-logs', 'cycleRecords2.txt');
class Logger {
    constructor(baseDir, config, dynamicLogMode) {
        this.baseDir = baseDir;
        this.config = config;
        this.logDir = null;
        this.log4Conf = null;
        this._setupLogs(dynamicLogMode);
    }
    // Checks if the configuration has the required components
    _checkValidConfig() {
        const config = this.config;
        if (!config.dir)
            throw Error('Fatal Error: Log directory not defined.');
        if (!config.files || typeof config.files !== 'object')
            throw Error('Fatal Error: Valid log file locations not provided.');
    }
    // Add filenames to each appender of type 'file'
    _addFileNamesToAppenders() {
        const conf = this.log4Conf;
        for (const key in conf.appenders) {
            const appender = conf.appenders[key];
            if (appender.type !== 'file')
                continue;
            appender.filename = `${this.logDir}/${key}.log`;
        }
    }
    _configureLogs() {
        return log4js_1.default.configure(this.log4Conf);
    }
    // Get the specified logger
    getLogger(logger) {
        return log4js_1.default.getLogger(logger);
    }
    // Setup the logs with the provided configuration using the base directory provided for relative paths
    _setupLogs(dynamicLogMode) {
        const baseDir = this.baseDir;
        const config = this.config;
        if (!baseDir)
            throw Error('Fatal Error: Base directory not defined.');
        if (!config)
            throw Error('Fatal Error: No configuration provided.');
        this._checkValidConfig();
        // Makes specified directory if it doesn't exist
        this.logDir = `${baseDir}/${config.dir}`;
        if (!(0, fs_1.existsSync)(this.logDir))
            (0, fs_1.mkdirSync)(this.logDir);
        // Read the log config from log config file
        this.log4Conf = config.options;
        log4jsExtend(log4js_1.default);
        this._addFileNamesToAppenders();
        this._configureLogs();
        this.getLogger('main').info('Logger initialized.');
        this._playbackLogger = this.getLogger('playback');
        this._mainLogger = this.getLogger('main');
        this.setupLogControlValues();
        if (dynamicLogMode.toLowerCase() === 'fatal' || dynamicLogMode.toLowerCase() === 'fatals') {
            console.log('startInFatalsLogMode=true!');
            this.setFatalFlags();
        }
        else if (dynamicLogMode.toLowerCase() === 'error' || dynamicLogMode.toLowerCase() === 'errors') {
            console.log('startInErrorLogMode=true!');
            this.setErrorFlags();
        }
        console.log(`logFlags: ` + lib_types_1.Utils.safeStringify(exports.logFlags));
        this._seenAddresses = {};
        this._shortStrings = {};
        this._playbackOwner_host = os_1.default.hostname();
        this._playbackOwner = 'temp_' + this._playbackOwner_host;
        this._playbackIPInfo = null;
        this._nodeInfos = {};
        http.setLogger(this);
    }
    // Tells this module that the server is shutting down, returns a Promise that resolves when all logs have been written to file, sockets are closed, etc.
    shutdown() {
        return new Promise((resolve) => {
            log4js_1.default.shutdown(() => {
                resolve('done');
            });
        });
    }
    setPlaybackIPInfo(ipInfo) {
        this._playbackIPInfo = ipInfo;
        let newName = 'temp_' + this._playbackOwner_host + ':' + this._playbackIPInfo.externalPort;
        this.playbackLogNote('logHostNameUpdate', '', { newName });
        this._playbackOwner = newName;
    }
    setPlaybackID(nodeID) {
        this._playbackNodeID = nodeID;
        let newName = utils.makeShortHash(this._playbackNodeID) + ':' + this._playbackIPInfo.externalPort;
        this.playbackLogNote('logHostNameUpdate', '', {
            newName,
            nodeID: nodeID + ' ',
        });
        this._playbackOwner = newName;
    }
    identifyNode(input) {
        if (utils.isString(input)) {
            if (input.length === 64) {
                let seenNode = this._nodeInfos[input];
                if (seenNode) {
                    return seenNode.out;
                }
                return utils.makeShortHash(input);
            }
            else {
                return input;
            }
        }
        if (utils.isObject(input)) {
            if (input.id) {
                let seenNode = this._nodeInfos[input.id];
                if (seenNode) {
                    return seenNode.out;
                }
                let shorthash = utils.makeShortHash(input.id);
                let out = shorthash + ':' + input.externalPort;
                this._nodeInfos[input.id] = { node: input, out, shorthash };
                return out;
            }
            return lib_types_1.Utils.safeStringify(input);
        }
    }
    processDesc(desc) {
        if (utils.isObject(desc)) {
            //desc = utils.stringifyReduce(desc)
            desc = utils.stringifyReduceLimit(desc, 1000);
        }
        return desc;
    }
    playbackLog(from, to, type, endpoint, id, desc) {
        if (!exports.logFlags.playback) {
            return;
        }
        nestedCounters_1.nestedCountersInstance.countEvent(type, endpoint);
        let ts = (0, network_1.shardusGetTime)();
        from = this.identifyNode(from);
        to = this.identifyNode(to);
        if (utils.isObject(id)) {
            id = lib_types_1.Utils.safeStringify(id);
        }
        else {
            id = utils.makeShortHash(id);
        }
        if (exports.logFlags.playback_trace) {
            desc = this.processDesc(desc);
            this._playbackLogger.trace(`\t${ts}\t${this._playbackOwner}\t${from}\t${to}\t${type}\t${endpoint}\t${id}\t${desc}`);
        }
        if (exports.logFlags.playback_debug) {
            this._playbackLogger.debug(`\t${ts}\t${this._playbackOwner}\t${from}\t${to}\t${type}\t${endpoint}\t${id}`);
        }
    }
    playbackLogState(newState, id, desc) {
        this.playbackLog('', '', 'StateChange', newState, id, desc);
    }
    playbackLogNote(noteCategory, id, desc = null) {
        this.playbackLog('', '', 'Note', noteCategory, id, desc);
    }
    setFatalFlags() {
        for (const [key, value] of Object.entries(exports.logFlags)) {
            exports.logFlags[key] = false;
        }
        exports.logFlags.fatal = true;
        exports.logFlags.important_as_fatal = true;
        exports.logFlags.playback = false;
    }
    setDisableAllFlags() {
        for (const [key, value] of Object.entries(exports.logFlags)) {
            exports.logFlags[key] = false;
        }
    }
    setErrorFlags() {
        for (const [key, value] of Object.entries(exports.logFlags)) {
            exports.logFlags[key] = false;
        }
        exports.logFlags.fatal = true;
        exports.logFlags.error = true;
        exports.logFlags.important_as_fatal = true;
        exports.logFlags.important_as_error = true;
        exports.logFlags.playback = false;
        //temp debug
        // logFlags.aalg = true
        // logFlags.shardedCache = true
        //logFlags.rotation = true
    }
    setDefaultFlags() {
        for (const [key, value] of Object.entries(exports.logFlags)) {
            exports.logFlags[key] = this.backupLogFlags[key];
        }
        if (exports.logFlags.playback_trace || exports.logFlags.playback_debug) {
            exports.logFlags.playback = true;
        }
        else {
            exports.logFlags.playback = false;
        }
        exports.logFlags.important_as_fatal = true;
        exports.logFlags.important_as_error = true;
        //logFlags.rotation = true
    }
    setFlagByName(name, value) {
        exports.logFlags[name] = value;
    }
    registerEndpoints(Context) {
        Context.network.registerExternalGet('log-fatal', debugMiddleware_1.isDebugModeMiddlewareMedium, (req, res) => {
            this.setFatalFlags();
            for (const [key, value] of Object.entries(exports.logFlags)) {
                res.write(`${key}: ${value}\n`);
            }
            res.end();
        });
        Context.network.registerExternalGet('log-disable', debugMiddleware_1.isDebugModeMiddlewareMedium, (req, res) => {
            this.setDisableAllFlags();
            for (const [key, value] of Object.entries(exports.logFlags)) {
                res.write(`${key}: ${value}\n`);
            }
            res.end();
        });
        Context.network.registerExternalGet('log-error', debugMiddleware_1.isDebugModeMiddlewareMedium, (req, res) => {
            this.setErrorFlags();
            for (const [key, value] of Object.entries(exports.logFlags)) {
                res.write(`${key}: ${value}\n`);
            }
            res.end();
        });
        Context.network.registerExternalGet('log-default', debugMiddleware_1.isDebugModeMiddlewareMedium, (req, res) => {
            this.setDefaultFlags();
            for (const [key, value] of Object.entries(exports.logFlags)) {
                res.write(`${key}: ${value}\n`);
            }
            res.end();
        });
        Context.network.registerExternalGet('log-flag', debugMiddleware_1.isDebugModeMiddlewareMedium, (req, res) => {
            //example of this endpont: http://localhost:9001/log-flag?name=verbose&value=true
            //check a query param for the flag name and value then call setFlagByName
            let flagName = req.query.name;
            let flagValue = req.query.value;
            if (flagName && flagValue) {
                this.setFlagByName(flagName, flagValue);
            }
            for (const [key, value] of Object.entries(exports.logFlags)) {
                res.write(`${key}: ${value}\n`);
            }
            res.end();
        });
        Context.network.registerExternalGet('log-getflags', debugMiddleware_1.isDebugModeMiddlewareLow, (req, res) => {
            for (const [key, value] of Object.entries(exports.logFlags)) {
                res.write(`${key}: ${value}\n`);
            }
            res.end();
        });
        Context.network.registerExternalGet('debug-cycle-recording-enable', debugMiddleware_1.isDebugModeMiddlewareMedium, (req, res) => {
            const enable = req.query.enable;
            if (enable === 'true') {
                Context_1.config.debug.localEnableCycleRecordDebugTool = true;
            }
            else if (enable === 'false') {
                Context_1.config.debug.localEnableCycleRecordDebugTool = false;
            }
            res.write(`localEnableCycleRecordDebugTool = ${Context_1.config.debug.localEnableCycleRecordDebugTool}`);
            res.end();
        });
        Context.network.registerExternalGet('debug-cycle-recording-clear', debugMiddleware_1.isDebugModeMiddlewareMedium, (req, res) => {
            fs.unlink(filePath1, (err) => {
                if (err) {
                    console.error(`Failed to delete ${filePath1}: ${err.message}`);
                }
                else {
                    console.log(`${filePath1} was deleted successfully.`);
                }
                // Attempt to delete the second file after the first completion
                fs.unlink(filePath2, (err) => {
                    if (err) {
                        console.error(`Failed to delete ${filePath2}: ${err.message}`);
                    }
                    else {
                        console.log(`${filePath2} was deleted successfully.`);
                    }
                    // End the response after attempting to delete both files
                    res.end('Cycle recording data cleared.');
                });
            });
        });
        Context.network.registerExternalGet('debug-cycle-recording-download', debugMiddleware_1.isDebugModeMiddlewareMedium, (req, res) => {
            // Use async read for non-blocking operation
            fs.readFile(filePath1, 'utf8', (err, data) => {
                if (err) {
                    // Handle error (e.g., file not found)
                    console.error('Error reading file:', err);
                    res.status(500).json({ error: 'Error reading file' });
                    return;
                }
                // Return data from filePath1 only
                res.setHeader('Content-Type', 'text/plain');
                res.json({ response: true, data: data });
            });
        });
        Context.network.registerExternalGet('debug-clearlog', debugMiddleware_1.isDebugModeMiddlewareMedium, async (req, res) => {
            const requestedFileName = req?.query?.file;
            let filesToClear = [];
            try {
                if (!requestedFileName) {
                    res.status(400).json({ error: 'No log file specified' });
                    return;
                }
                // Retrieve valid filenames from the logger configuration
                const validFileNames = [];
                for (const appender of Object.values(this.log4Conf.appenders)) {
                    if (appender.type === 'file') {
                        validFileNames.push(path_1.default.basename(appender.filename));
                    }
                }
                // explicitly add out.log since that is handled in saveConsoleOutput.ts
                if (this.config.saveConsoleOutput) {
                    validFileNames.push('out.log');
                }
                // If 'all' is requested, set to clear all files, otherwise sanitize the input file name.
                if (requestedFileName.toLowerCase() === 'all') {
                    filesToClear = validFileNames;
                }
                else {
                    const sanitizedFileName = path_1.default.basename(requestedFileName);
                    if (!validFileNames.includes(sanitizedFileName)) {
                        res.status(400).json({ error: 'Invalid log file specified' });
                        return;
                    }
                    filesToClear.push(sanitizedFileName);
                }
            }
            catch (error) {
                console.error('Error clearing log files 1:', error);
                res.status(500).json({ error: `Failed to clearing log files with input 1 ${requestedFileName}` });
            }
            try {
                // Retrieve and filter all log files and their rotated versions in the directory.
                // including rotated versions (e.g., out.log.1, out.log.2, etc.)
                const filesInDir = await fs.promises.readdir(this.logDir);
                const filesToDelete = filesInDir.filter((file) => filesToClear.some((f) => file.startsWith(f + '.') && file !== f));
                // Clear the original log files without deleting them by opening them with 'w+' to truncate and allow read/write
                const truncatePromises = filesToClear.map((file) => fs.promises.open(path_1.default.join(this.logDir, file), 'w+').then((fileHandle) => fileHandle.close()));
                await Promise.all(truncatePromises);
                // Delete rotated versions
                const deletePromises = filesToDelete.map((file) => fs.promises.unlink(path_1.default.join(this.logDir, file)));
                await Promise.all(deletePromises);
                res.status(200).json({ success: true });
            }
            catch (error) {
                console.error('Error clearing log files 2:', error);
                res.status(500).json({ error: `Failed to clearing log files with input 2 ${requestedFileName}` });
            }
        });
    }
    _containsProtocol(url) {
        if (!url.match('https?://*'))
            return false;
        return true;
    }
    _normalizeUrl(url) {
        let normalized = url;
        if (!this._containsProtocol(url))
            normalized = 'http://' + url;
        return normalized;
    }
    async _internalHackGet(url) {
        let normalized = this._normalizeUrl(url);
        let host = (0, url_1.parse)(normalized, true);
        try {
            await (0, customHttpFunctions_1.customGot)().get(host.href, {
                timeout: 1000,
                retry: 0,
                throwHttpErrors: false,
                //parseJson: (text:string)=>{},
                //json: false, // the whole reason for _internalHackGet was because we dont want the text response to mess things up
                //  and as a debug non shipping endpoint did not want to add optional parameters to http module
            });
        }
        catch (e) { }
    }
    async _internalHackGetWithResp(url) {
        let normalized = this._normalizeUrl(url);
        let host = (0, url_1.parse)(normalized, true);
        try {
            const res = await (0, customHttpFunctions_1.customGot)().get(host.href, {
                timeout: 7000,
                retry: 0,
                throwHttpErrors: false,
                //parseJson: (text:string)=>{},
                //json: false, // the whole reason for _internalHackGet was because we dont want the text response to mess things up
                //  and as a debug non shipping endpoint did not want to add optional parameters to http module
            });
            return res;
        }
        catch (e) {
            return null;
        }
    }
    setupLogControlValues() {
        exports.logFlags.fatal = true;
        let mainLogger = this.getLogger('main');
        // @ts-ignore
        if (mainLogger && ['TRACE', 'trace'].includes(mainLogger.level.levelStr)) {
            exports.logFlags.verbose = true;
            exports.logFlags.debug = true;
            exports.logFlags.info = true;
            exports.logFlags.error = true;
            // @ts-ignore
        }
        else if (mainLogger && ['DEBUG', 'debug'].includes(mainLogger.level.levelStr)) {
            exports.logFlags.verbose = false;
            exports.logFlags.debug = true;
            exports.logFlags.info = true;
            exports.logFlags.error = true;
            // @ts-ignore
        }
        else if (mainLogger && ['INFO', 'info'].includes(mainLogger.level.levelStr)) {
            exports.logFlags.verbose = false;
            exports.logFlags.debug = false;
            exports.logFlags.info = true;
            exports.logFlags.error = true;
            // @ts-ignore
        }
        else if (mainLogger && ['ERROR', 'error', 'WARN', 'warn'].includes(mainLogger.level.levelStr)) {
            exports.logFlags.verbose = false;
            exports.logFlags.debug = false;
            exports.logFlags.info = true;
            exports.logFlags.error = true;
        }
        else {
            exports.logFlags.verbose = false;
            exports.logFlags.debug = false;
            exports.logFlags.info = false;
            exports.logFlags.error = false;
            //would still get warn..
        }
        let playbackLogger = this.getLogger('playback');
        exports.logFlags.playback = false;
        if (playbackLogger) {
            // @ts-ignore
            exports.logFlags.playback_trace = ['TRACE'].includes(playbackLogger.level.levelStr);
            // @ts-ignore
            exports.logFlags.playback_debug = ['DEBUG'].includes(playbackLogger.level.levelStr);
            if (exports.logFlags.playback_trace || exports.logFlags.playback_debug) {
                exports.logFlags.playback = true;
            }
            else {
                exports.logFlags.playback = false;
            }
        }
        let netLogger = this.getLogger('net');
        // @ts-ignore
        if (netLogger && ['TRACE', 'trace'].includes(netLogger.level.levelStr)) {
            exports.logFlags.net_trace = true;
        }
        let p2pLogger = this.getLogger('p2p');
        // @ts-ignore
        if (p2pLogger && ['FATAL', 'fatal'].includes(p2pLogger.level.levelStr)) {
            exports.logFlags.p2pNonFatal = false;
        }
        else {
            exports.logFlags.p2pNonFatal = true;
        }
        this.backupLogFlags = utils.deepCopy(exports.logFlags);
        console.log(`base logFlags: ` + lib_types_1.Utils.safeStringify(exports.logFlags));
    }
    mainLog(level, key, message) {
        //initially this will just go to a main log but we could but this in
        //a json blob with the key and send it to a different logging service
        this._mainLogger[level](key + ' ' + message);
    }
    mainLog_debug(key, message) {
        //note will change the key to be an array later and remove the DBG prefix
        this.mainLog('debug', 'DBG_' + key, message);
    }
    combine(...args) {
        return args
            .map((arg) => {
            if (typeof arg === 'object') {
                return lib_types_1.Utils.safeStringify(arg);
            }
            else {
                return String(arg);
            }
        })
            .join(' ');
    }
}
exports.default = Logger;
//# sourceMappingURL=index.js.map