// Library imports
const express = require('express');
const router = express.Router();
const fs = require('fs');
const { SerialPort } = require('serialport')
const path = require('path');
const https = require('https');

// File Imports
const { parseAudioDevice } = require('./stream/parser');
const { configName, serverConfig, configUpdate, configSave, configExists, configPath } = require('./server_config');
const helpers = require('./helpers');
const storage = require('./storage');
const { logInfo, logDebug, logWarn, logError, logFfmpeg, logs } = require('./console');
const dataHandler = require('./datahandler');
const fmdxList = require('./fmdx_list');
const { allPluginConfigs } = require('./plugins');

// Endpoints
router.get('/', (req, res) => {
    let requestIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

    const normalizedIp = requestIp?.replace(/^::ffff:/, '');
    const ipList = normalizedIp.split(',').map(ip => ip.trim()); // in case there are multiple IPs (proxy), we need to check all of them
    
    const isBanned = ipList.some(ip => serverConfig.webserver.banlist.some(banEntry => banEntry[0] === ip));
    
    if (isBanned) {
        res.render('403');
        logInfo(`Web client (${normalizedIp}) is banned`);
        return;
    }

    const noPlugins = req.query.noPlugins === 'true';

    if (configExists() === false) {
        let serialPorts;
        
        SerialPort.list()
        .then((deviceList) => {
            serialPorts = deviceList.map(port => ({
                path: port.path,
                friendlyName: port.friendlyName,
            }));
            
            parseAudioDevice((result) => {
                res.render('wizard', {
                    isAdminAuthenticated: true,
                    videoDevices: result.audioDevices,
                    audioDevices: result.videoDevices,
                    serialPorts: serialPorts
                });
            });
        });
    } else {
        res.render('index', {
            isAdminAuthenticated: req.session.isAdminAuthenticated,
            isTuneAuthenticated: req.session.isTuneAuthenticated,
            tunerName: serverConfig.identification.tunerName,
            tunerDesc: helpers.parseMarkdown(serverConfig.identification.tunerDesc),
            tunerDescMeta: helpers.removeMarkdown(serverConfig.identification.tunerDesc),
            tunerLock: serverConfig.lockToAdmin,
            publicTuner: serverConfig.publicTuner,
            ownerContact: serverConfig.identification.contact,
            antennas: serverConfig.antennas ? serverConfig.antennas : {},
            tuningLimit: serverConfig.webserver.tuningLimit,
            tuningLowerLimit: serverConfig.webserver.tuningLowerLimit,
            tuningUpperLimit: serverConfig.webserver.tuningUpperLimit,
            chatEnabled: serverConfig.webserver.chatEnabled,
            device: serverConfig.device,
            noPlugins,
            plugins: serverConfig.plugins,
            fmlist_integration: typeof(serverConfig.extras?.fmlistIntegration) !== undefined ? serverConfig.extras?.fmlistIntegration : true,
            bwSwitch: serverConfig.bwSwitch ? serverConfig.bwSwitch : false
        });
    }
});

router.get('/403', (req, res) => {
    res.render('403');
})

router.get('/wizard', (req, res) => {
    let serialPorts;
    
    if(!req.session.isAdminAuthenticated) {
        res.render('login');
        return;
    }

    SerialPort.list()
    .then((deviceList) => {
        serialPorts = deviceList.map(port => ({
            path: port.path,
            friendlyName: port.friendlyName,
        }));
        
        parseAudioDevice((result) => {
            res.render('wizard', {
                isAdminAuthenticated: req.session.isAdminAuthenticated,
                videoDevices: result.audioDevices,
                audioDevices: result.videoDevices,
                serialPorts: serialPorts
            });
        });
    })
})
  
  router.get('/setup', (req, res) => {
      let serialPorts; 
      function loadConfig() {
        if (fs.existsSync(configPath)) {
          const configFileContents = fs.readFileSync(configPath, 'utf8');
          return JSON.parse(configFileContents);
        }
        return serverConfig;
      }
  
      if(!req.session.isAdminAuthenticated) {
          res.render('login');
          return;
      }
      
      SerialPort.list()
      .then((deviceList) => {
          serialPorts = deviceList.map(port => ({
              path: port.path,
              friendlyName: port.friendlyName,
          }));
          
          parseAudioDevice((result) => {
              const processUptimeInSeconds = Math.floor(process.uptime());
              const formattedProcessUptime = helpers.formatUptime(processUptimeInSeconds);
              
              const updatedConfig = loadConfig();  // Reload the config every time
              res.render('setup', {
                  isAdminAuthenticated: req.session.isAdminAuthenticated,
                  videoDevices: result.audioDevices,
                  audioDevices: result.videoDevices,
                  serialPorts: serialPorts,
                  memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
                  processUptime: formattedProcessUptime,
                  consoleOutput: logs,
                  plugins: allPluginConfigs,
                  enabledPlugins: updatedConfig.plugins,
                  onlineUsers: dataHandler.dataToSend.users,
                  connectedUsers: storage.connectedUsers,
                  banlist: updatedConfig.webserver.banlist // Updated banlist from the latest config
              });
          });
      }) 
  });
  

router.get('/rds', (req, res) => {
    res.send('Please connect using a WebSocket compatible app to obtain RDS stream.');
});

router.get('/rdsspy', (req, res) => {
    res.send('Please connect using a WebSocket compatible app to obtain RDS stream.');
});

router.get('/rds', (req, res) => {
    res.send('Please c onnect using a WebSocket compatible app to obtain RDS stream.');
});

router.get('/rdsspy', (req, res) => {
    res.send('Please connect using a WebSocket compatible app to obtain RDS stream.');
});

router.get('/api', (req, res) => {
    const { ps_errors, rt0_errors, rt1_errors, ims, eq, ant, st_forced, previousFreq, txInfo, ...dataToSend } = dataHandler.dataToSend;
    res.json({
        ...dataToSend,
        txInfo: txInfo,
        ps_errors: ps_errors,
        ant: ant
    });
});


const loginAttempts = {}; // Format: { 'ip': { count: 1, lastAttempt: 1234567890 } }
const MAX_ATTEMPTS = 25;
const WINDOW_MS = 15 * 60 * 1000; 

const authenticate = (req, res, next) => {
    const ip = req.ip || req.connection.remoteAddress;
    const now = Date.now();

    if (!loginAttempts[ip]) {
        loginAttempts[ip] = { count: 0, lastAttempt: now };
    } else if (now - loginAttempts[ip].lastAttempt > WINDOW_MS) {
        loginAttempts[ip] = { count: 0, lastAttempt: now };
    }

    if (loginAttempts[ip].count >= MAX_ATTEMPTS) {
        return res.status(403).json({
            message: 'Too many login attempts. Please try again later.'
        });
    }

    const { password } = req.body;

    loginAttempts[ip].lastAttempt = now;

    if (password === serverConfig.password.adminPass) {
        req.session.isAdminAuthenticated = true;
        req.session.isTuneAuthenticated = true;
        logInfo(`User from ${ip} logged in as an administrator.`);
        loginAttempts[ip].count = 0;
        next();
    } else if (password === serverConfig.password.tunePass) {
        req.session.isAdminAuthenticated = false;
        req.session.isTuneAuthenticated = true;
        logInfo(`User from ${ip} logged in with tune permissions.`);
        loginAttempts[ip].count = 0;
        next();
    } else {
        loginAttempts[ip].count += 1;
        res.status(403).json({ message: 'Login failed. Wrong password?' });
    }
};

// Route for login
router.post('/login', authenticate, (req, res) => {
    // Redirect to the main page after successful login
    res.status(200).json({ message: 'Logged in successfully, refreshing the page...' });
});

router.get('/logout', (req, res) => {
    // Clear the session and redirect to the main page
    req.session.destroy(() => {
        res.status(200).json({ message: 'Logged out successfully, refreshing the page...' });
    });
});

router.get('/kick', (req, res) => {
    const ipAddress = req.query.ip; // Extract the IP address parameter from the query string
    // Terminate the WebSocket connection for the specified IP address
    if(req.session.isAdminAuthenticated) {
        helpers.kickClient(ipAddress);
    }
    setTimeout(() => {
        res.redirect('/setup');
    }, 500);
});

router.get('/addToBanlist', (req, res) => {
    const ipAddress = req.query.ip;
    const location = 'Unknown';
    const date = Date.now();
    const reason = req.query.reason;

    userBanData = [ipAddress, location, date, reason];

    if (typeof serverConfig.webserver.banlist !== 'object') {
        serverConfig.webserver.banlist = [];
    }

    if (req.session.isAdminAuthenticated) {
        serverConfig.webserver.banlist.push(userBanData);
        configSave();
        res.json({ success: true, message: 'IP address added to banlist.' });
        helpers.kickClient(ipAddress);
    } else {
        res.status(403).json({ success: false, message: 'Unauthorized access.' });
    }
});

router.get('/removeFromBanlist', (req, res) => {
    const ipAddress = req.query.ip;

    if (typeof serverConfig.webserver.banlist !== 'object') {
        serverConfig.webserver.banlist = [];
    }

    const banIndex = serverConfig.webserver.banlist.findIndex(ban => ban[0] === ipAddress);

    if (banIndex === -1) {
        return res.status(404).json({ success: false, message: 'IP address not found in banlist.' });
    }

    serverConfig.webserver.banlist.splice(banIndex, 1);
    configSave();

    res.json({ success: true, message: 'IP address removed from banlist.' });
});


router.post('/saveData', (req, res) => {
    const data = req.body;
    let firstSetup;
    if(req.session.isAdminAuthenticated || configExists() === false) {
        configUpdate(data);
        fmdxList.update();
        
        if(configExists() === false) {
            firstSetup = true;
        }
        logInfo('Server config changed successfully.');
        if(firstSetup === true) {
            res.status(200).send('Data saved successfully!\nPlease, restart the server to load your configuration.');
        } else {
            res.status(200).send('Data saved successfully!\nSome settings may need a server restart to apply.');
        }
    }
});

router.get('/getData', (req, res) => {  
    if (configExists() === false) {
        res.json(serverConfig);
    }
    
    if(req.session.isAdminAuthenticated) {
        // Check if the file exists
        fs.access(configPath, fs.constants.F_OK, (err) => {
            if (err) {
                console.log(err);
            } else {
                // File exists, send it as the response
                res.sendFile(path.join(__dirname, '../' + configName + '.json'));
            }
        });
    }
});

router.get('/getDevices', (req, res) => {
    if (req.session.isAdminAuthenticated || !fs.existsSync(configName + '.json')) {
        parseAudioDevice((result) => {
            res.json(result);
        });
    } else {
        res.status(403).json({ error: 'Unauthorized' });
    }
});

/* Static data are being sent through here on connection - these don't change when the server is running */
router.get('/static_data', (req, res) => {
    res.json({
        qthLatitude: serverConfig.identification.lat,
        qthLongitude: serverConfig.identification.lon,
        presets: serverConfig.webserver.presets || [],
        defaultTheme: serverConfig.webserver.defaultTheme || 'theme1',
        bgImage: serverConfig.webserver.bgImage || '',
        rdsMode: serverConfig.webserver.rdsMode || false,
        tunerName: serverConfig.identification.tunerName || '',
        tunerDesc: serverConfig.identification.tunerDesc || '',
        ant: serverConfig.antennas || {}
    });
});

router.get('/server_time', (req, res) => {
    const serverTime = new Date(); // Get current server time
    const serverTimeUTC = new Date(serverTime.getTime() - (serverTime.getTimezoneOffset() * 60000)); // Adjust server time to UTC
    res.json({
        serverTime: serverTimeUTC,
    });
});

router.get('/ping', (req, res) => {
    res.send('pong');
});  

const logHistory = {};

// Function to check if the ID has been logged within the last 60 minutes
function canLog(id) {
    const now = Date.now();
    const sixtyMinutes = 60 * 60 * 1000; // 60 minutes in milliseconds
    if (logHistory[id] && (now - logHistory[id]) < sixtyMinutes) {
        return false; // Deny logging if less than 60 minutes have passed
    }
    logHistory[id] = now; // Update with the current timestamp
    return true;
}

router.get('/log_fmlist', (req, res) => {
    if (dataHandler.dataToSend.txInfo.tx.length === 0) {
        res.status(500).send('No suitable transmitter to log.');
        return;
    }

    if (serverConfig.extras?.fmlistIntegration === false) {
        res.status(500).send('FMLIST Integration is not enabled on this server.');
        return;
    }

    const clientIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    const txId = dataHandler.dataToSend.txInfo.id; // Extract the ID

    // Check if the ID can be logged (i.e., not logged within the last 60 minutes)
    if (!canLog(txId)) {
        res.status(429).send(`ID ${txId} was already logged recently. Please wait before logging again.`);
        return;
    }

    const postData = JSON.stringify({
        station: {
            freq: dataHandler.dataToSend.freq,
            pi: dataHandler.dataToSend.pi,
            id: dataHandler.dataToSend.txInfo.id,
            rds_ps: dataHandler.dataToSend.ps.replace(/'/g, "\\'"), // Escape quotes
            signal: dataHandler.dataToSend.sig,
            tp: dataHandler.dataToSend.tp,
            ta: dataHandler.dataToSend.ta,
            af_list: dataHandler.dataToSend.af,
        },
        server: {
            uuid: serverConfig.identification.token,
            latitude: serverConfig.identification.lat,
            longitude: serverConfig.identification.lon,
            address: serverConfig.identification.proxyIp.length > 1 ? serverConfig.identification.proxyIp : ('Matches request IP with port ' + serverConfig.webserver.port),
            webserver_name: serverConfig.identification.tunerName.replace(/'/g, "\\'"), // Escape quotes
            omid: serverConfig.extras?.fmlistOmid || '',
        },
        client: {
            request_ip: clientIp
        },
        type: req.query.type ? req.query.type : 'tropo',
        log_msg: "Logged PS: " + dataHandler.dataToSend.ps.replace(/\s+/g, '_') + ", PI: " + dataHandler.dataToSend.pi + ", Signal: " + (dataHandler.dataToSend.sig - 11.25).toFixed(0) + " dBµV",
    });

    const options = {
        hostname: 'api.fmlist.org',
        path: '/fmdx.org/slog.php',
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Content-Length': Buffer.byteLength(postData) // Use Buffer.byteLength for accurate content length
        }
    };

    const request = https.request(options, (response) => {
        let data = '';

        // Collect response chunks
        response.on('data', (chunk) => {
            data += chunk;
        });

        // Response ended
        response.on('end', () => {
            res.status(200).send(data);
        });
    });

    // Handle errors in the request
    request.on('error', (error) => {
        console.error('Error sending POST request:', error);
        res.status(500).send(error.message); // Send error message to client
    });

    // Write the postData and end the request properly
    request.write(postData);
    request.end();
});

module.exports = router;
