const fsPromises = require('fs').promises;
const fs = require('fs');
const path = require('path');
const ip = require('ip');
const {encryptFileInPlace, decryptFileInPlace} = require('../src/main/aes_encrypt');
const {lockFile, unlockFile} = require("../helpers/lock_mechanism");

async function fetchBackupSchemes(serverIp) {
    // Fetching backup schemes from the server
    const depResponse = await fetch(`http://${serverIp}/backup_schemes`, {
        method: 'GET',
        headers: {'x-api-key': 'uc_api'}
    });
    const backupSchemesJson = await depResponse.json();
    const backupSchemes = backupSchemesJson['data'];

    // Specify the path where the backup schemes will be saved
    const destPath = path.join(__dirname, '..', 'backupSchemes.json');

    // Write the fetched backup schemes to the specified file
    try {
        await lockFile(destPath);
        await fsPromises.writeFile(destPath, JSON.stringify(backupSchemes, null, 2)); // Use null, 2 for pretty formatting
        await unlockFile(destPath);
        console.log('Backup schemes saved successfully to', destPath);
    } catch (error) {
        console.error('Failed to save backup schemes:', error);
    }
}

async function fetchUsersAndDepartments(serverIp) {
    try {
        console.log('se asteapta depart');
        // Fetch departments
        const depResponse = await fetch(`http://${serverIp}/users/departments`, {
            method: 'GET',
            headers: {'x-api-key': 'uc_api'}
        });

        const departmentsJson = await depResponse.json();
        const departments = departmentsJson['data'];

        console.log('se asteapta users')
        // Fetch users
        const userResponse = await fetch(`http://${serverIp}/users`, {
            method: 'GET',
            headers: {'x-api-key': 'uc_api'}
        });
        const usersJson = await userResponse.json();
        const users = usersJson['data'];

        // Combine the JSON data
        const combinedData = Object.values(departments).reduce((acc, {name, key}) => {
            acc[name] = {
                key,
                users: users.filter(user => user.department === name).map(user => user.id)
            };
            return acc;
        }, {});

        // Write combined data to a file
        const filePath = path.join(__dirname, '..', 'usersInSystem.json');
        if(!fs.existsSync(filePath)){
            await fsPromises.writeFile(filePath, '');
        }
        await lockFile(filePath);
        await fsPromises.writeFile(filePath, JSON.stringify(combinedData, null, 2), 'utf8');

        await encryptFileInPlace(filePath);
        await unlockFile(filePath);
        console.log('Data has been encrypted and saved.');
    } catch (error) {
        console.error('Failed to fetch or process data:', error);
    }
}

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

        const fileContent = await fsPromises.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;
    }
}

async function getDirectoryStructure(dirPath) {
    const baseName = path.basename(dirPath);
    const entries = await fsPromises.readdir(dirPath, {withFileTypes: true});
    const result = {};
    result[baseName] = {files: []};
    let totalSize = 0;

    for (let entry of entries) {
        const entryPath = path.join(dirPath, entry.name);
        if (entry.isDirectory()) {
            // Recursively get structure for subdirectories
            const {structure, size: subSize} = await getDirectoryStructure(entryPath);
            result[baseName][entry.name] = structure[Object.keys(structure)[0]]; // Object.keys to fetch the first key name
            totalSize += subSize;
        } else {
            // Add file name to the 'files' array and calculate total size
            const stats = await fsPromises.stat(entryPath);
            result[baseName].files.push(entry.name);
            totalSize += stats.size;
        }
    }
    return {structure: result, size: totalSize};
}

async function createBackupScheme(serverIp, structure, size) {
    let filePath = path.join(__dirname, '..', 'loginData.json');
    await lockFile(filePath);
    await decryptFileInPlace(filePath);

    const userContent = await fsPromises.readFile(filePath, 'utf8');
    const userJson = JSON.parse(userContent);

    await encryptFileInPlace(filePath)
    await unlockFile(filePath);

    // Find the IP address of the machine
    const machineIP = ip.address();

    // Prepare the data to be sent
    const dataToSend = {
        id: userJson.id,
        ip: machineIP,
        directoryStructure: JSON.stringify(structure),
        totalSize: size
    };

    const response = await fetch(`http://${serverIp}/backup_schemes`, {
        method: 'PATCH',
        headers: {
            'Content-Type': 'application/json',
            'x-api-key': 'uc_api'
        },
        body: JSON.stringify(dataToSend)
    });

    if (response.ok) {
        console.log("Backup dir structure submitted.")
    } else {
        const data = await response.json()
        console.log(data.message);
    }
}

async function watchDirectoryChanges(serverIp) {
    async function readBackupConfig(filePath) {
        await lockFile(filePath);
        const fileContent = await fsPromises.readFile(filePath, 'utf8');
        await unlockFile(filePath);
        return JSON.parse(fileContent);
    }

    async function updateBackupConfig(filePath, structure, size) {
        const data = {
            path: filePath,
            structure: structure,
            size: size
        };
        await lockFile(filePath);
        await fsPromises.writeFile(path.join(__dirname, '..', 'dirBackup.json'), JSON.stringify(data, null, 2));
        await unlockFile(filePath);
    }

    const configPath = path.join(__dirname, '..', 'dirBackup.json');
    if(!fs.existsSync(configPath)){
        console.log('backupDir file doesn\'t exists');
        return;
    }
    const backupConfig = await readBackupConfig(configPath);
    const storedStructure = backupConfig.structure;

    const {structure: currentStructure, size: currentSize} = await getDirectoryStructure(backupConfig.path);

    if (JSON.stringify(storedStructure) !== JSON.stringify(currentStructure)) {
        console.log("Directory structure has changed. Updating backup...");
        await createBackupScheme(serverIp, currentStructure, currentSize);

        await updateBackupConfig(backupConfig.path, currentStructure, currentSize);
        console.log('FetcherProcess: start backup.');
        process.send({type: 'startBackup'});
    } else {
        console.log("No changes detected in the directory structure.");
    }
}

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

const timeoutInterval = 1 * 20 * 1000 //minutes * seconds * miliseconds

setInterval(() => {
    fetchUsersAndDepartments(serverIp);
}, timeoutInterval);

setInterval(() => {
    watchDirectoryChanges(serverIp);
}, timeoutInterval);

setInterval(() => {
    fetchBackupSchemes(serverIp);
}, timeoutInterval);


console.log("fetcher.js process started.");

process.on('SIGINT', async () => {
    console.log('Shutdown signal received in \'fetcher.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);
    }
});
