import fs from 'fs';
import path from 'path'
import rimraf from 'rimraf';
import crypto from 'crypto';
import archiver from 'archiver';
import extractZip from 'extract-zip';
import subProcess from 'child_process'
import { exec } from "node:child_process";

export async function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
};

export function getRandomdString(length) {
  let result = '';
  let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let charactersLength = characters.length;
  for (let i = 0; i < length; i++) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
  }
  return result;
}

export async function getBrowserlog() {
  let log = 'BROWSERLOG: \n'
  try {
    let browserlog = await browser.getLogs('browser');
    browserlog.forEach(entry => {
      let date = new Date(entry.timestamp);
      log += date.toISOString() + ' ';
      log += entry.level + ': ';
      log += entry.message;
      log += ' SOURCE: ' + entry.source + '\n';
    });
  } catch (error) {
    log += 'Could not retrieve browserlog';
  }
  return log;
}

export async function asyncForEach(array, callback) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array);
  }
}

export async function elementAttributeContains(elementPath, attribute, value) {
  return await (await (await $(elementPath)).getAttribute(attribute)).includes(value);
}

export async function deleteFile(filepath) {
  rimraf.sync(filepath);
}

export function rmdir(dir) {

  if (fs.existsSync(dir)) {
    var list = fs.readdirSync(dir);
    for (var i = 0; i < list.length; i++) {
      var filename = path.join(dir, list[i]);
      var stat = fs.statSync(filename);

      if (filename == "." || filename == "..") {
        // pass these files
      } else if (stat.isDirectory()) {
        this.rmdir(filename);
      } else {
        fs.unlinkSync(filename);
      }
    }
    fs.rmdirSync(dir);
  }
}

export async function zip(sourcePath, zipfilepath) {
  let archive_output = fs.createWriteStream(zipfilepath);
  let archive = archiver('zip', {
    zlib: { level: 9 }
  });
  archive.on('warning', function (err) {
    throw err;
  });

  // good practice to catch this error explicitly
  archive.on('error', function (err) {
    throw err;
  });

  archive.pipe(archive_output);

  // Check if source is a directory
  const stats = fs.statSync(sourcePath);
  if (stats.isDirectory()) {
    archive.directory(sourcePath, false);  // false means don't include the root directory name
  } else {
    let filename = path.basename(sourcePath);
    archive.file(sourcePath, { name: filename });
  }

  archive.finalize();

  let write_end = new Promise((resolve, reject) => {
    archive_output.on('error', err => reject(err));
    archive_output.on('finish', () => resolve());
    setTimeout(() => resolve(new Error('Error zip: write to zip timed out after 30 seconds')), 30000);
  });

  let write_res = await write_end;
  if (write_res instanceof Error) {
    console.error(write_res.message);
    fs.writeFileSync(zipfilepath, write_res.message, { flags: 'w' })
  }
};

export function getUUID() {

  return crypto.randomUUID();
}

export async function unzip(filePath, zipName) {
  const zipFile = path.join(filePath, zipName);
  try {
    await extractZip(zipFile, { dir: filePath });
  } catch (err) {
    console.log('Extract error : ' + err)
  }
}
export async function waitForFileExists(filePath, timeout) {
  let totalTime = 0;
  let checkTime = timeout / 1000;
  return await new Promise((resolve, reject) => {
    const timer = setInterval(function () {
      totalTime += checkTime;
      let fileExists = fs.existsSync(filePath);
      if (fileExists) {
        clearInterval(timer);
        resolve(fileExists);
      }
      if (totalTime >= timeout) {
        clearTimeout(timer);
        const error = new Error('File check timed out for: ' + filePath);
        error.name = 'PATH_CHECK_TIMED_OUT';
        reject(error);
      }
    }, checkTime);
  });
}

export function getFileContent(filePath) {
  return fs.readFileSync(filePath, 'utf-8');
}

export function copy(sourcePath, fileName, destinationPath) {
  let fullPath = path.join(sourcePath, fileName);
  let destinationFullPath = path.join(destinationPath, fileName);
  let fileExists = fs.existsSync(fullPath);
  if (fileExists) {
    fs.copyFileSync(fullPath, destinationFullPath);
  }
}

export async function executeCliSpawn(command, paramaters=[], shell = true, detached = false, unref = false){
  try {
    return new Promise((resolve, reject) => {
      const childProcess = subProcess.spawn(command, paramaters, {shell:shell, detached: detached});
      let output='';
      childProcess.stdout.on('data', (data) => {output += data.toString()});
      childProcess.stderr.on('data', (data => {reject(new Error(data.toString()))}));
      childProcess.on('close', () => {
        console.log('Command: '+ command + ' and paramaters : '+ paramaters + ' finished');
        resolve(output);
      })
      childProcess.on('error', (err)=> { reject(err)})
      if(unref){
        childProcess.unref();
      }
    })
  } catch (error) {
    console.log('Error executing command: ' + command + ' paramaters: ['+paramaters+'], '+ error.message);
  }
}
export async function execute(command){
  return executePromise(command).then(result => result).catch(error => {console.error('Error running command: ' + command, error.toString())});
}
export async function cliSpawn(command){
  return executeCliSpawn(command).then(result => result).catch(error => {console.error('Error running command: ' + command, error.toString())});
}
export async function killProcess(process){
  let command = "kill -9 $(ps aux | grep "+process+" | awk \'{print $2}\')";
  execute(command);
}

export async function executePromise(command) {
  return new Promise((resolve, reject) => {
    exec(command, (error, stdout, stderr) => {
      if (error) {
        reject(error);
      }
      resolve(stdout.toString());
    });
  })
}