/** @param {import(".").NS } ns */
import { EXCLUDE_SERVERS } from "./constants.js";

export async function main(ns) {
    ns.disableLog('ALL');

    async function writeLog(type, obj) {
        let timeNow = new Date();
        let year = timeNow.getFullYear().toString().substring(2);
        let month = (timeNow.getMonth() < 9 ? '0' : '') + (timeNow.getMonth() + 1);
        let day = (timeNow.getDate() < 10 ? '0' : '') + timeNow.getDate();
        let hour = (timeNow.getHours() < 10 ? '0' : '') + timeNow.getHours();
        let minute = (timeNow.getMinutes() < 10 ? '0' : '') + timeNow.getMinutes();
        let second = (timeNow.getSeconds() < 10 ? '0' : '') + timeNow.getSeconds();
        let timestamp = `${year}${month}${day}_${hour}${minute}${second}`;
        await ns.tryWritePort(2, `t${timestamp}_${type} = ${obj};\n`);

        if (['ERROR', 'TERMINAL'].indexOf(type) != -1) ns.tprint(`${timestamp} ${type} = ${obj}`);
    }

    /**
     * @constant
     * @default
     */
    const EXPLOITS = [
        'BruteSSH.exe',
        'FTPCrack.exe',
        'relaySMTP.exe',
        'HTTPWorm.exe',
        'SQLInject.exe'
    ];

    /** @function maxPortsToHack
     * Calculate how many ports you're able to open
     * @returns {number} number of ports you're able to open
     */
    function maxPortsToHack() {
        let maxPorts = 0;
        EXPLOITS.forEach(exploit => {
            if (ns.fileExists(exploit)) ++maxPorts;
        });
        // ns.tryWritePort(2, `Max ports for hacking ${maxPorts}`);
        return maxPorts;
    }

    /** @function analyzeServer
     * Analyze server requirements
     * @param {string} server 
     * @returns {object} object containing server information
     */
    function analyzeServer(server) {
        let requiredHacking = ns.getServerRequiredHackingLevel(server);
        let requiredPorts = ns.getServerNumPortsRequired(server);
        let currentHacking = ns.getHackingLevel();

        return {
            "rootAccess": ns.hasRootAccess(server),
            "requiredHacking": requiredHacking,
            "requiredPorts": requiredPorts,
            "nukeAvailable": (currentHacking >= requiredHacking && maxPortsToHack() >= requiredPorts)
        };
    }

    /** @function runHack
     * Recursively scan, try to nuke and hack servers in the network
     * @param {string} server server to nuke and hack
     * @returns {Promise<number>} exit code; if negative error, if positive hack script PID, if 0 already running
     */
    async function runNuke(server) {
        let serverStatus = analyzeServer(server);
        ns.print('INFO ', `{server: "${server}", status: ${JSON.stringify(serverStatus)}}`);

        if (!serverStatus.rootAccess && serverStatus.nukeAvailable) {
            ns.print(`"Nuking ${server}!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"`);
            for (let i = 0; i < maxPortsToHack(); ++i) {
                switch (EXPLOITS[i]) {
                    case 'BruteSSH.exe':
                        ns.brutessh(server);
                        break;

                    case 'FTPCrack.exe':
                        ns.ftpcrack(server);
                        break;

                    case 'relaySMTP.exe':
                        ns.relaysmtp(server);
                        break;

                    case 'HTTPWorm.exe':
                        ns.httpworm(server);
                        break;

                    case 'SQLInject.exe':
                        ns.sqlinject(server);
                        break;

                }
            }
            ns.nuke(server);
        }
    }

    async function runHack(server) {
        let script = 'simple-hack.js';

        let serverStatus = analyzeServer(server);
        ns.print('INFO ', `{server: "${server}", status: ${JSON.stringify(serverStatus)}}`);

        if (serverStatus.rootAccess && serverStatus.nukeAvailable) {
            if (!ns.fileExists(script, 'home')) {
                ns.print(`ERROR Script '${script}' doesn't exist!`);
                // return -3;
            }
            ns.scp(script, server, 'home');

            let serverMaxRam = ns.getServerMaxRam(server);
            let serverUsedRam = ns.getServerUsedRam(server);
            let serverFreeRam = serverMaxRam - serverUsedRam;
            let scriptRam = ns.getScriptRam(script, server);
            let maxThreads = Math.floor(serverFreeRam / scriptRam);

            let a = ns.scriptRunning(script, server);
            if (!a) {
                if (maxThreads == 0) {
                    // await writeLog('ERROR', `"Not enough ram to run '${script}' on '${server}', ${scriptRam}/${serverFreeRam}!"`);
                    return -4;
                }

                let execExitCode = ns.exec(script, server, maxThreads, server);

                if (!execExitCode) {
                    // await writeLog('ERROR', `"Exec error running script '${script} on '${server}!"`);
                    return -5;
                }
                else {
                    return execExitCode;
                }
            } else {
                // await writeLog('ERROR', `"Hack already running on ${server}"`);
                return 0;
            }
        }

        return -6;
    }

    /**
     * Recursively kill simple-hack.js running on servers in the network
     * @async
     * @param {string} currentServer server to scan
     */
    async function scanServer(currentServer, previousServer = currentServer) {
        let availableServers = ns.scan(currentServer);

        for (let i = 0; i < availableServers.length; ++i) {
            let nextServer = availableServers[i];

            if (nextServer != currentServer && nextServer != previousServer) {
                await runNuke(nextServer);
                if (EXCLUDE_SERVERS.indexOf(nextServer) == -1) {
                    let hackExitCode = await runHack(nextServer);

                    // if (hackExitCode == 0) {
                    //     ns.print('INFO ' + `"Hack already running on ${nextServer}"`);
                    // } else if (hackExitCode == -6) {
                    //     ns.print('ERROR ' + `"No root access to ${nextServer}!"`);
                    // } else if (hackExitCode == -4) {
                    //     ns.print('WARN ' + `"Not enough RAM to run script on ${nextServer}!"`);
                    // } else {
                    //     if (hackExitCode < 0) {
                    //         ns.print('ERROR ' + `"Hack exec ERROR on '${nextServer}': ${hackExitCode}!"`);
                    //     } else {
                    //         ns.print('INFO ' + `"Hack started on '${nextServer}'"`);
                    //     }
                    // }
                }
                await scanServer(nextServer, currentServer);
            }
        }
        await ns.sleep(10);
    }

    ns.write('nuke-log.js', '', 'w');
    if (!ns.scriptRunning('simple-hack.js', 'n00dles')) ns.write('hack-log.js', '', 'w');
    ns.tprint(`TERMINAL = "Running scan"`);

    while (true) {
        ns.print(`INFO Running scan`);
        await scanServer('home');
        await ns.sleep(60000);
    }
}
