const os = require('os');
const axios = require('axios');
const cron = require('node-cron');
const cronIpToRedis = require('node-cron');
const fs = require('fs');
const path = require('path');
const ut = require('util');
const readFiles = ut.promisify(fs.readFile);
const writeFile = ut.promisify(fs.writeFile);
const {exec} = require('child_process');
const express = require('express')
const shell = require('shelljs');
const pm2 = require('pm2')
const execAsync  = ut.promisify(require('child_process').exec);
const app = express()
let Xray = require('./xray.js')
let xrayManager = new Xray()
const {spawn} = require('child_process');
const processName = `config.json`;
let uniqueIps = new Set();
let storeConfig=null
let cpuUser = 0;
let totalRAM = 0;
let usedRAM = 0;
let freeRAM = 0;
let networkUsage={rx:1,tx:1}

let uptime
const child_process = require('child_process');
const BASE_URL = "https://server.vpsl.xyz";

app.get('/', (req, res) => {
    res.send('Hello World!')
    //run()
})


app.get('/rst2', (req, res) => {

    return reboot()

})

app.get('/test', (req, res) => {
    // console.log(req.params.acc)
    res.sendfile('jetmtp.png')

})

app.get('/delall', (req, res) => {
    res.send('done')
    // forceCreateNew()

})

app.get('/resetProcc', (req, res) => {
    res.send('done')
    // restartXray()

})

app.get('/system-info', async (req, res) => {
    try {
        res.json({
            cpuUser,
            totalRAM,
            usedRAM,
            freeRAM,
            networkUsage,
            uptime,
            users:{ count: uniqueIps.size, ips: Array.from(uniqueIps) }
        });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

// New routes to get system resource information
app.get('/cpu-usage', async (req, res) => {
    try {
        res.json({ cpuUser });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

app.get('/ram-usage', async (req, res) => {
    try {
        res.json({ ramUsage:freeRAM });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

app.get('/total-ram', async (req, res) => {
    try {
        res.json({ totalRAM });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

app.get('/used-ram', async (req, res) => {
    try {
        res.json({ usedRAM });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});



app.get('/network-usage', async (req, res) => {
    try {
        res.json(networkUsage);
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});

app.get('/uptime', async (req, res) => {
    try {
        res.json({ uptime });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});


app.get('/users', async (req, res) => {
    try {
        res.json({ count: uniqueIps.size, ips: Array.from(uniqueIps) });
    } catch (error) {
        res.status(500).json({ error: error.message });
    }
});
app.listen(3000, () => console.log(`Example app listening on port 3000!`))

async function main() {

    let newConfig=await getConfig()
    newConfig=JSON.stringify(newConfig)
    if (storeConfig === null || newConfig !==storeConfig)
    {
        console.log('config has been changed')
        applyChanges(newConfig)
        storeConfig=newConfig
    }
    else
    {
        console.log('config not been changed')
    }

}

async function applyChanges(newConfig) {


    const configFilePath = path.join('/usr/local/x-ui/', `config.json`);
    await writeFile(configFilePath, newConfig);
    const processExists = await xrayManager.isProcessRunning(processName);
    if (!processExists) {
        await startProcess();
    }
    // Sort clients by id


}


async function startProcess() {
    return new Promise((resolve, reject) => {
        const configFilePath = path.join('/usr/local/x-ui/', `config.json`);
//console.log(configFilePath)
        pm2.connect((error) => {
            if (error) {
                console.error(`PM2 connection error: ${error.message}`);
                reject(error);
            } else {
                pm2.start(
                    {
                        name: `runConfig`,
                        script: './xray',
                        args: `run -c config.json`,
                        cwd: '/usr/local/x-ui',
                        watch: [configFilePath],
                        ignore_watch: ['node_modules'],

                        watch_options: {
                            followSymlinks: false,
                        }
                    },
                    (error) => {
                        if (error) {
                            console.error(`Error starting PM2 process: ${error.message}`);
                            reject(error);
                        } else {
                            console.log(`PM2 process for config.json started successfully.`);
                            resolve();
                        }
                        pm2.disconnect();
                    }
                );
            }
        });
    });
}

async function sleep(millis) {
    return new Promise(resolve => setTimeout(resolve, millis));
}




async function runGetUsers() {
    uniqueIps.clear();
    const filePath = '/usr/local/x-ui/access.log';

    getSystemUsage()

    const data = await readFiles(filePath, 'utf8');
    const regex = /(\d+\.\d+\.\d+\.\d+):\d+ accepted/g;
    let match;

    while ((match = regex.exec(data)) !== null) {
        uniqueIps.add(match[1]);
    }

    await writeFile(filePath, ''); // Clear the file after reading

    try {
        sendUsageData(cpuUser, freeRAM, uptime, networkUsage.rx, networkUsage.tx);
    }
    catch (e)
    {
console.log(e)
    }


    // Log the unique IPs or handle them as needed
}

async function updateFirewall() {
    let blockedIps = await getAllBlockIpRanges();


    // Add rules for each blocked IP to DROP packets
    for (const ip of blockedIps) {
        child_process.execSync(`sudo iptables -A INPUT -s ${ip.ip} -j DROP`);
        child_process.execSync(`sudo iptables -A OUTPUT -d ${ip.ip} -j DROP`);
    }
}



async function getSystemUsage() {

    try {

        await getSystemInfo()
        networkUsage = await getNetworkUsage();

        uptime = getUptime()

        // sendUsageData(cpuUsage, ramUsage, uptime, networkUsage.rx, networkUsage.tx);
    } catch (error) {
        // console.log(error)
        console.log(`Error: ${error}`);
    }
}

async function getSystemInfo() {
    const command = `
  top -bn1 | awk '
  /Cpu\\(s\\)/ { cpu = $2 }
  /MiB Mem/ {
    total = $4
    free = $8
    used = total - free
  }
  END {
    printf "{ \\"cpu_user\\": %.1f, \\"total_mem\\": %.1f, \\"used_mem\\": %.1f, \\"free_mem\\": %.1f }", cpu, total, used, free
  }'`;

    try {
        const { stdout, stderr } = await execAsync(command);

        if (stderr) {
            throw new Error(stderr);
        }

        // Parse the JSON output
        const systemInfo = JSON.parse(stdout);

        // Assign to variables
        cpuUser = systemInfo.cpu_user;
        totalRAM = systemInfo.total_mem;
        usedRAM = systemInfo.used_mem;
        freeRAM = systemInfo.free_mem;



    } catch (error) {
        console.error('Error:', error.message);
    }
}

async function reboot() {
    exec('sudo reboot', (error, stdout, stderr) => {
        if (error) {
            console.error(`exec error: ${error}`);
            return;
        }
        console.log(`stdout: ${stdout}`);
        //  console.error(`stderr: ${stderr}`);
    });

}


function getNetworkUsage() {
    return new Promise((resolve, reject) => {
        shell.exec('vnstat -tr 5', {silent: true}, (code, stdout, stderr) => {
            if (code !== 0) {
                reject(stderr);
            } else {
                const lines = stdout.trim().split('\n');

                const rxLine = lines.find(line => line.trim().startsWith('rx'));
                const txLine = lines.find(line => line.trim().startsWith('tx'));

                let rx = parseFloat(rxLine.split(/\s+/)[2]);
                let tx = parseFloat(txLine.split(/\s+/)[2]);

                const rxUnit = rxLine.split(/\s+/)[3];
                const txUnit = txLine.split(/\s+/)[3];

                // Convert to MB if unit is KB
                if (rxUnit.toLowerCase() === 'kbit/s') {
                    rx /= 1024;
                }

                if (txUnit.toLowerCase() === 'kbit/s') {
                    tx /= 1024;
                }

                // If rx or tx is less than 1, set it to 1
                rx = rx < 1 ? 1 : rx;
                tx = tx < 1 ? 1 : tx;

                if (txUnit.toLowerCase() === 'bit/s') {
                    tx = 1;
                    rx = 1;
                }

                resolve({rx: rx.toFixed(2), tx: tx.toFixed(2)});
            }
        });
    });
}

function getUptime() {
    const uptime = os.uptime();
    return (uptime / 86400).toFixed(2); // Convert to days
}



async function getConfig() {
    try {
        const response = await axios.get(`${BASE_URL}/getXrayConfig`, {
            params: {

            },
            timeout: 30000
        });
        return response.data;
    } catch (error) {
        throw error;
    }
}




async function getAccByIp() {
    try {
        const response = await axios.get(`${BASE_URL}/getAccByIP`, {timeout: 30000});
        return response.data;
    } catch (error) {
        throw  error
    }
}


async function sendUsageData(cpu, ram, uptime, rx, tx) {
    try {
        const response = await axios.get(`${BASE_URL}/vUsage`, {
            params: {
                cpu: cpu,
                ram: ram,
                uptime: uptime,
                rx: rx,
                tx: tx
            },
            timeout: 3000
        });

        console.log(response)

        return
    } catch (error) {
        console.log('Error sending usage data:', error);
        return null;
    }
}

async function getAllBlockIpRanges() {
    try {
        const response = await axios.get(`${BASE_URL}/getAllBlockIpRanges`, {timeout: 30000});
        return response.data;
    } catch (error) {
        throw  error
    }
}

main()
updateFirewall()
cron.schedule(' */1 * * * *', main);
cronIpToRedis.schedule('*/10 * * * * *', runGetUsers);
//cronServiceCheck.schedule('*/5 * * * * *', firewallUpdater);


