const os = require("os");
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const https = require('https');
const AdmZip = require('adm-zip');
const rimraf = require("rimraf");

const isOSWindows = process.platform === "win32";
const baseDirName = "Isograd-Adobe";
const defaultWorkingDirectory = `${os.homedir().replace(/\\/g, '/')}/documents`;
const publicDir = isOSWindows ? process.env['PUBLIC'].replace(/\\/g, '/') : "/Users/Shared";
const baseDirPath = `${publicDir}${isOSWindows ? "/documents" : ""}/${baseDirName}`;
const settingsPath = `${baseDirPath}/settings.json`;

function getCurrentSettings() {
	let settings = {};
	try {
		const fileContent = fs.readFileSync(settingsPath, 'utf8');
		settings = JSON.parse(fileContent);
	} catch (error) { }
	return settings;
}

function getWorkingDirectory() {
	const settings = getCurrentSettings();
	if (settings && settings.workingDirectory) {
		return settings.workingDirectory
	}
	return defaultWorkingDirectory;
}

function setWorkingDirectory(settings) {
	checkAndCreateDirectory(baseDirPath);
	fs.writeFileSync(settingsPath, JSON.stringify(settings), { encoding: 'utf8', flag: 'w' });
}

function validateDirectory(dir) {
	if (!fs.existsSync(dir)) {
		throw Error(`The directory '${dir}' does not exist`);
	}

	if (!fs.statSync(dir).isDirectory()) {
		throw Error(`The path '${dir}' is not a directory`);
	}

	try {
		fs.writeFileSync(`${dir}/test.txt`, 'test');
		fs.unlinkSync(`${dir}/test.txt`);
	} catch (error) {
		throw Error(`You do not have permission to access the directory '${dir}'`);
	}
}

function checkAndCreateDirectory(dirPath, mode) {
	if (!fs.existsSync(dirPath)) {
		fs.mkdirSync(dirPath);
		if (mode) {
			fs.chmodSync(dirPath, mode);
		}
	}
}

class FileManager {
	constructor({ appDirName, startersDirName, resourcesDirName, myProjectsDirName, langCode }) {

		this.dirInfo = {
			starterDir: "StarterFiles",
			resourceDir: "ResourceFiles"
		};

		const publicDocuments = isOSWindows ? "documents/" : "";
		const workingDirectory = getWorkingDirectory();

		this.publicDirInfo = {
			baseDir: `${publicDir}/${publicDocuments}${baseDirName}`,
			appDir: `${publicDir}/${publicDocuments}${baseDirName}/${appDirName}`,
			startersDir: `${publicDir}/${publicDocuments}${baseDirName}/${appDirName}/${this.dirInfo.starterDir}`,
			startersLangDir: `${publicDir}/${publicDocuments}${baseDirName}/${appDirName}/${this.dirInfo.starterDir}/${langCode}`,
			resourcesDir: `${publicDir}/${publicDocuments}${baseDirName}/${appDirName}/${this.dirInfo.resourceDir}`
		};

		this.homeDirInfo = {
			baseDir: `${workingDirectory}/${baseDirName}`,
			appDir: `${workingDirectory}/${baseDirName}/${appDirName}`,
			startersDir: `${workingDirectory}/${baseDirName}/${appDirName}/${startersDirName}`,
			resourcesDir: `${workingDirectory}/${baseDirName}/${appDirName}/${resourcesDirName}`,
			myProjectsDir: `${workingDirectory}/${baseDirName}/${appDirName}/${myProjectsDirName}`
		};
	}

	async checkDirsAndDeleteHomeFiles() {
		checkAndCreateDirectory(this.publicDirInfo.baseDir, 0o777);
		checkAndCreateDirectory(this.publicDirInfo.appDir, 0o777);
		checkAndCreateDirectory(this.publicDirInfo.startersDir, 0o777);
		checkAndCreateDirectory(this.publicDirInfo.startersLangDir, 0o777);
		checkAndCreateDirectory(this.publicDirInfo.resourcesDir, 0o777);

		await this.deleteDirectory(this.homeDirInfo.appDir);
		checkAndCreateDirectory(this.homeDirInfo.baseDir);
		checkAndCreateDirectory(this.homeDirInfo.appDir);
		checkAndCreateDirectory(this.homeDirInfo.startersDir);
		checkAndCreateDirectory(this.homeDirInfo.resourcesDir);
		checkAndCreateDirectory(this.homeDirInfo.myProjectsDir);

		return this.deleteFiles(this.homeDirInfo.appDir);
	}

	deleteFiles(dir) {
		try {
			if (!fs.existsSync(dir)) {
				return { ok: true };
			}

			const files = this.getDirFiles(dir);

			files.forEach(function (file) {
				if (file.indexOf(baseDirName) > -1) {
					fs.unlinkSync(file);
				}
			});

		} catch (error) {
			return { ok: false, errorMessage: error.message };
		}
		return { ok: true };
	}

	async deleteDirectory(dir) {
		return new Promise(function (resolve) { rimraf(dir, resolve); });
	}

	getDirFiles(dirPath, arrayOfFiles) {
		const files = fs.readdirSync(dirPath)

		arrayOfFiles = arrayOfFiles || [];

		for (let i = 0; i < files.length; i++) {
			const file = files[i];

			if (fs.statSync(dirPath + "/" + file).isDirectory()) {
				arrayOfFiles = this.getDirFiles(dirPath + "/" + file, arrayOfFiles)
			} else {
				arrayOfFiles.push(path.join(dirPath, "/", file))
			}
		}

		return arrayOfFiles
	}

	async processFiles(publicDir, homeDir, items) {
		const fileInfo = [];

		for (let i = 0; i < items.length; i++) {
			const item = items[i];
			const filePublicPath = `${publicDir}/${item.name}`;
			const checksum = await this.getFileChecksum(filePublicPath);
			const isFileNew = checksum === null;

			if (item.checksum !== checksum) {
				const url = new URL(item.url);
				const zipfileName = path.basename(url.pathname);
				const zipFilePublicPath = `${publicDir}/${zipfileName}`;

				await this.downloadFile(item.url, zipFilePublicPath);
				this.extractFile(zipFilePublicPath, item.name, zipFilePublicPath.replace(`/${zipfileName}`, ''));
				fs.unlinkSync(zipFilePublicPath);
			}

			const fileLocalPath = `${homeDir}/${item.name}`;

			if (isFileNew) {
				fs.chmodSync(filePublicPath, 0o777);
			}

			fs.copyFileSync(filePublicPath, fileLocalPath);

			fileInfo.push({
				localPath: fileLocalPath,
				autoOpen: item.autoOpen,
				isFileNew
			});
		}

		return fileInfo;
	}

	getFileChecksum(filePath) {
		return new Promise(function (resolve, reject) {
			if (fs.existsSync(filePath)) {
				const hash = crypto.createHash('md5');
				const input = fs.createReadStream(filePath);

				input.on('error', reject);

				input.on('data', function (chunk) {
					hash.update(chunk);
				});

				input.on('close', function () {
					resolve(hash.digest('hex'));
				});
			}
			else {
				resolve(null);
			}
		});
	}

	downloadFile(url, localPath) {
		return new Promise(function (resolve, reject) {
			try {
				if (!url.toLowerCase().startsWith("https://learning-resources.isograd.com/")) {
					throw Error(`The address '${url}' is not allowed`);
				}

				https.get(url, function (response) {
					const file = fs.createWriteStream(localPath);
					response.pipe(file);
					file.on('finish', function () {
						file.close();
						resolve();
					})
				})
			} catch (error) {
				reject(error);
			}
		})
	}

	extractFile(zipFilePath, entryName, targetPath) {
		var zipFile = new AdmZip(zipFilePath);
		zipFile.extractEntryTo(entryName, targetPath, true, true);
	}
}

module.exports = {
	baseDirName,
	publicDir,
	isOSWindows,
	setWorkingDirectory,
	defaultWorkingDirectory,
	getCurrentSettings,
	getWorkingDirectory,
	validateDirectory,
	FileManager
};
