const fsPromises = require('fs').promises;  // Ensure you use the promise-based API
const fs = require('fs');
const path = require('path');
const { decryptFileInPlace, encryptFileInPlace, encryptFileWithKey, decryptFileWithKey} = require("../src/main/aes_encrypt");
const {lockFile, unlockFile} = require("../helpers/lock_mechanism");

async function decryptAndGetServerIP(filePath) {
    try {
        await lockFile(filePath);
        await decryptFileInPlace(filePath);

        const fileContent = await fs.promises.readFile(filePath, 'utf8');
        const jsonData = JSON.parse(fileContent);
        const serverIP = jsonData.ip;

        await encryptFileInPlace(filePath)
        await unlockFile(filePath);
        return serverIP;
    } catch (error) {
        console.error('An error occurred:', error);
        return null;
    }
}

function extractFilePaths(directoryStructure) {
    let paths = [];
    const traverse = (dir, currentPath) => {
        Object.keys(dir).forEach(key => {
            if (key === 'files') {
                dir[key].forEach(file => paths.push(path.join(currentPath, file)));
            } else {
                traverse(dir[key], path.join(currentPath, key));
            }
        });
    };
    traverse(directoryStructure, '');
    return paths;
}

async function fetchFiles(ip, filePaths) {
    let results = {};

    for (const filePath of filePaths) {
        const url = `http://${ip}:3000/file_path`;
        try {
            const response = await fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ filePath })
            });

            if (!response.ok) throw new Error(`HTTP status ${response.status}`);

            const arrayBuffer = await response.arrayBuffer(); // Fetch the response as an ArrayBuffer
            results[filePath] = Buffer.from(arrayBuffer);
        } catch (error) {
            console.error(`Error fetching ${filePath}:`, error);
            results[filePath] = null;
        }
    }

    return results;
}

async function removeDirectory(directoryPath) {
    try {
        // Check if the directory exists
        const stats = await fsPromises.stat(directoryPath);
        if (!stats.isDirectory()) {
            console.log('The specified path is not a directory.');
            return;
        }

        // Read all the contents of the directory
        const files = await fsPromises.readdir(directoryPath);

        // Loop through each file/directory and delete them
        for (const file of files) {
            const currentPath = path.join(directoryPath, file);
            const currentStats = await fsPromises.stat(currentPath);

            if (currentStats.isDirectory()) {
                // Recursive call for directories
                await removeDirectory(currentPath);
            } else {
                await lockFile(currentPath);
                await unlockFile(currentPath);
                await fsPromises.unlink(currentPath);
            }
        }

        // Finally, delete the directory itself
        await fsPromises.rmdir(directoryPath);
        //console.log(`Directory removed: ${directoryPath}`);
    } catch (error) {
        console.error(`Error removing directory: ${error.message}`);
    }
}

async function decryptUserSystemConfig() {
    const configPath = path.join(__dirname, '..', 'usersInSystem.json');
    await lockFile(configPath);
    await decryptFileInPlace(configPath);
}

async function encryptUserSystemConfig(){
    const configPath = path.join(__dirname, '..', 'usersInSystem.json');
    await encryptFileInPlace(configPath);
    await unlockFile(configPath);
}

async function processBackup() {
    const usersConfigPath = path.join(__dirname, '..', 'usersInSystem.json');
    const backupConfigPath = path.join(__dirname, '..', 'backupSchemes.json');
    const baseBackupDirPath = path.join(__dirname, '..', 'backup_directories');
    if(!fs.existsSync(backupConfigPath) || !fs.existsSync(usersConfigPath)){
        console.log('Can\'t start backup system: necessary files missing!');
        return;
    }

    await decryptUserSystemConfig();

    try {
        // Read and parse the users configuration
        const usersData = await fsPromises.readFile(usersConfigPath, 'utf8');
        const usersConfig = JSON.parse(usersData);

        // Read and parse the backup configuration
        const backupData = await fsPromises.readFile(backupConfigPath, 'utf8');
        const jsonData = JSON.parse(backupData);

        for (const key in jsonData) {
            const userBackupDir = path.join(baseBackupDirPath, key);
            await removeDirectory(userBackupDir);

            const node = jsonData[key];
            const directoryStructure = JSON.parse(node.directoryStructure);
            const filePaths = extractFilePaths(directoryStructure);
            const files = await fetchFiles(node.ip, filePaths);

            await fsPromises.mkdir(userBackupDir, { recursive: true });

            // Find the encryption key for the user's department
            let encryptionKey = '';
            for (const dept in usersConfig) {
                if (usersConfig[dept].users.includes(key)) {
                    encryptionKey = usersConfig[dept].key;
                    break;
                }
            }

            for (const file in files) {
                if (files[file]) {
                    const fullPath = path.join(userBackupDir, file);
                    await fsPromises.mkdir(path.dirname(fullPath), { recursive: true });
                    await fsPromises.writeFile(fullPath, files[file]); // Write the binary data

                    // Encrypt the file in place after writing it
                    if (encryptionKey) {
                        await encryptFileWithKey(fullPath, encryptionKey);
                    }
                }
            }
        }
    } catch (error) {
        console.error('Failed to read or process the JSON file:', error);
    }

    await encryptUserSystemConfig();
}

async function getAllFilePaths(dirPath) {
    let filePaths = [];
    async function recurse(currentPath) {
        const entries = await fsPromises.readdir(currentPath, { withFileTypes: true });
        // Create promises for each entry and process them in parallel
        const entryPromises = entries.map(async (entry) => {
            const resolvedPath = path.join(currentPath, entry.name);
            if (entry.isDirectory()) {
                // If it's a directory, recurse into it
                await recurse(resolvedPath);
            } else {
                // If it's a file, add it to the file paths array
                filePaths.push(resolvedPath);
            }
        });
        // Wait for all promises to complete
        await Promise.all(entryPromises);
    }
    await recurse(dirPath);
    return filePaths;
}

async function decryptBackupFilesToDirectory(destinationDir) {
    await decryptUserSystemConfig();
    const usersConfigPath = path.join(__dirname, '..', 'usersInSystem.json');

    if(!fs.existsSync(usersConfigPath)){
        return false;
    }

    const usersConfig = JSON.parse(await fsPromises.readFile(usersConfigPath, 'utf8'));
    const sourceDir = path.join(__dirname, '..', 'backup_directories');

    await removeDirectory(destinationDir);

    try {
        for (const department in usersConfig) {
            const { key, users } = usersConfig[department];
            for (const userId of users) {
                const userDir = path.join(sourceDir, userId);
                try {
                    const files = await getAllFilePaths(userDir);
                    for (const filePath of files) {
                        const stats = await fsPromises.stat(filePath);
                        if (stats.isFile()) {
                            const relativePath = path.relative(userDir, filePath);

                            // Construct the destination file path
                            const destinationFilePath = path.join(destinationDir, userId, relativePath);
                            await fsPromises.mkdir(path.dirname(destinationFilePath), { recursive: true });
                            await lockFile(filePath);

                            // Decrypt the file in its original location
                            await decryptFileWithKey(filePath, key);

                            // Copy the decrypted file to the destination directory
                            await fsPromises.copyFile(filePath, destinationFilePath);
                            console.log(`Decrypted file copied to: ${destinationFilePath}`);

                            // Re-encrypt the file in its original location (optional)
                            await encryptFileWithKey(filePath, key);
                            await unlockFile(filePath);
                        }
                    }
                } catch (error) {
                    console.error(`Error processing files for user ${userId}: ${error.message}`);
                    await encryptUserSystemConfig();
                    return false;
                }
            }
        }
    } catch (error) {
        console.error(`Error processing decryption: ${error.message}`);
        await encryptUserSystemConfig();
        return false;
    }

    await encryptUserSystemConfig();
    return true;
}

async function decryptDepartmentFilesToDirectory() {
    const dirDepartmentPath = path.join(__dirname, '..', 'dirDepartment.json');
    const loginDataPath = path.join(__dirname, '..', 'loginData.json');
    const usersConfigPath = path.join(__dirname, '..', 'usersInSystem.json');
    const sourceDir = path.join(__dirname, '..', 'backup_directories');

    if (!fs.existsSync(usersConfigPath) || !fs.existsSync(loginDataPath) || !fs.existsSync(dirDepartmentPath)) {
        console.error("Missing one or more essential configuration files.");
        return false;
    }

    await lockFile(loginDataPath);
    await lockFile(usersConfigPath);

    await decryptFileInPlace(loginDataPath);
    await decryptFileInPlace(usersConfigPath);

    const { path: destinationDir } = JSON.parse(await fsPromises.readFile(dirDepartmentPath, 'utf8'));
    const { department } = JSON.parse(await fsPromises.readFile(loginDataPath, 'utf8'));
    const usersConfig = JSON.parse(await fsPromises.readFile(usersConfigPath, 'utf8'));

    // Check if department exists in users configuration
    if (!usersConfig[department]) {
        console.error(`No configuration found for department: ${department}`);
        return false;
    }

    const { key, users } = usersConfig[department];

    await encryptFileInPlace(loginDataPath);
    await encryptFileInPlace(usersConfigPath);

    await unlockFile(loginDataPath);
    await unlockFile(usersConfigPath);

    await removeDirectory(destinationDir);

    for (const userId of users) {
        const userDir = path.join(sourceDir, userId);

        try {
            const files = await getAllFilePaths(userDir);
            console.log(`Processing files for user ${userId}: ${files}`);

            for (const filePath of files) {
                const stats = await fsPromises.stat(filePath);
                if (stats.isFile()) {
                    const relativePath = path.relative(userDir, filePath);

                    // Construct the destination file path
                    const destinationFilePath = path.join(destinationDir, userId, relativePath);
                    await fsPromises.mkdir(path.dirname(destinationFilePath), { recursive: true });

                    // Decrypt the file in its original location
                    await decryptFileWithKey(filePath, key);

                    // Copy the decrypted file to the destination directory
                    await fsPromises.copyFile(filePath, destinationFilePath);
                    console.log(`Decrypted file copied to: ${destinationFilePath}`);

                    // Re-encrypt the file in its original location (optional)
                    await encryptFileWithKey(filePath, key);
                }
            }
        } catch (error) {
            console.error(`Error processing files for user ${userId}: ${error.message}`);
            return false;
        }
    }

    console.log("All files processed successfully.");
    return true;
}

let serverIp = null;
decryptAndGetServerIP(path.join(__dirname, '..', 'ipConfig.json')).then(ip => {
    serverIp = ip;
});

const timeoutInterval = 7 * 60 * 1000 //minutes * seconds * miliseconds

setInterval(async () => {
    await decryptDepartmentFilesToDirectory();
}, timeoutInterval);

console.log('backup.js process started!');
process.on('SIGINT', async () => {
    console.log('Shutdown signal received in \'backup.js\'. Cleaning up...');

    try {
        console.log('Performing cleanup tasks...');
        console.log('Cleanup completed successfully.');
    } catch (error) {
        console.error('An error occurred during cleanup:', error);
    } finally {
        console.log('Process terminated');
        process.exit(0);
    }
});

process.on('message', async (message) => {
    if (message.type === 'startBackup') {
        console.log('BackupProcess: Received command to start backup.');
        await processBackup()
    }
    if(message.type === 'decryptBackup'){
        console.log(`BackupProcess: Received command to decrypt backup to ${message.decryptDestPath}`);
        console.log(message.decryptDestPath);
        await decryptBackupFilesToDirectory(message.decryptDestPath);
    }
});