import fs from 'fs';
import rimraf from 'rimraf';
import path from 'path';
import sanitize from 'sanitize-filename';
import os from 'os';
import allureReporter from '@wdio/allure-reporter';
import publisherUtils from './core/reporter/publishers/publisher-utils.js';
import * as utils from './core/utils.js';
import VaultService from './core/services/vault-service.js';
import AzureMaintEventService from './core/services/azure-maintenance-events.js';
import MonitorMonitor from './core/monitor-monitor/monitor-monitor.js';
import SaucelabsService from './core/services/saucelabs.js';
import PuppeteerHar from './core/puppeteer-har.js';
import * as networkUtils from './core/networkUtils.js';
import { expect as expectChai } from 'chai';
import allure from 'allure-commandline';
import coreCommands from './commands/core-commands.js';
import { fileURLToPath } from 'url';


global.tmpDir = path.join(process.cwd(), '.tmp');
global.screenshotPath = path.join(process.cwd(), '.tmp/screenshots',);
global.tesseractTmpFolder = path.join(tmpDir, 'tesseract-screenshots');
global.browser = { config: {} };

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const stepReportGroupTag = 'STEP_';

let resourcesPath = path.join(__dirname, 'core/reporter/resources')
let openReportPowershell = 'openreport.ps1';
let openReportBatch = 'openreport.sh';

let configuration = {
  updateJob: false,
  injectGlobals: true,
  logLevel: 'silent',
  coloredLogs: true,
  execArgv: [],
  screenshotPath: screenshotPath,
  waitforTimeout: 30000,
  waitforInterval: 500,
  deprecationWarnings: false,
  maintenances: false,
  currentFrame: 'parent',
  joburl: '',
  plugins: {
    'wdio-screenshot': {},
  },
  framework: 'mocha',
  mochaOpts: {
    ui: 'bdd',
    compilers: ['js:@babel/register'],
    timeout: 2400 * 1000
  },
}

export const config = Object.assign(configuration, {

  // Gets executed before all workers get launched.
  async onPrepare() {
    console.log(" Total system memory: " + os.totalmem() / 1073741824);
    console.log(" Free system memory: " + os.freemem() / 1073741824);
    try {
      console.log("OS network interfaces: " + JSON.stringify(os.networkInterfaces(), null, 2));
    } catch (error) {
      // ignore
    }
    utils.rmdir(tmpDir);
    if (!fs.existsSync(tmpDir)) {
      fs.mkdirSync(tmpDir);
    }
    if (!fs.existsSync(tesseractTmpFolder)) {
      fs.mkdirSync(tesseractTmpFolder);
    }
    if (!fs.existsSync(screenshotPath)) {
      fs.mkdirSync(screenshotPath);
    }
    let vault = null;
    if (config.vaultConfig) {
      if (!(config.vaultConfig.endpoint && ((config.vaultConfig.user && config.vaultConfig.password) || config.vaultConfig.token))) {
        console.error('Please add or check the config.vaultConfig');
        if (!config.vaultConfig.endpoint) { console.error('config.vaultConfig.endpoint is missing or empty'); }
        if (!config.vaultConfig.user && !config.vaultConfig.token) { console.error('config.vaultConfig.user or token is missing or empty'); }
        if (config.vaultConfig.user) { if (!config.vaultConfig.password) { console.error('config.vaultConfig.password is missing or empty'); } }
        throw new Error('Invalid config.vaultConfig');
      }
      vault = new VaultService(config);
    }
    try {
      if (config.monitorMonitorConfig) {
        let promises = [];
        config.monitorMonitorConfig.checkSources.forEach(checkSource => {
          if (checkSource.sourceConfig.vaultConfig) {
            promises.push(
              vault.getSecret(checkSource.sourceConfig.vaultConfig.path, checkSource.sourceConfig.vaultConfig.key).then(value => {
                if (value[checkSource.sourceConfig.vaultConfig.key]) {
                  Object.keys(value[checkSource.sourceConfig.vaultConfig.key]).forEach(varKey => {
                    if (!checkSource.sourceConfig[varKey]) {
                      checkSource.sourceConfig[varKey] = value[checkSource.sourceConfig.vaultConfig.key][varKey]
                    }
                  })
                }
              })
            );
          }
        });
        await Promise.all(promises);

        let monitorMonitor = new MonitorMonitor(config.monitorMonitorConfig);
        await monitorMonitor.check();
      }
    } catch (error) {
      console.error(error.message);
      if (error.stack) {
        console.error(error.stack);
      }
    }

    let args = process.argv;
    if (Array.isArray(args)) {
      let ind = args.indexOf('--versionCheck');
      if (!(ind > -1)) {
        ind = args.indexOf('--VERSIONCHECK');
      }
      if (ind > -1) {
        ind++;
        if (args.length < ind + 1) {
          console.error("versionCheck argument not passed")
        } else {
          console.log("versionCheck IS " + args[ind]);
          Object.assign(config, { "versionCheck": args[ind] });
        }
      }
      ind = args.indexOf('--bashtimestamp');
      if (!(ind > -1)) {
        ind = args.indexOf('--BASHTIMESTANP');
      }
      if (ind > -1) {
        ind++;
        if (args.length < ind + 1) {
          console.error("bashtimestamp argument not passed")
        } else {
          console.log("bashtimestamp IS " + args[ind]);
          Object.assign(config, { "bashtimestamp": args[ind] });
        }
      }
      ind = args.indexOf('--jiraissuekey');
      if (!(ind > -1)) {
        ind = args.indexOf('--JIRAISSUEKEY');
      }
      if (ind > -1) {
        ind++;
        if (args.length < ind + 1) {
          console.error("jiraissuekey argument not passed")
        } else {
          console.log("jiraissuekey IS " + args[ind]);
          Object.assign(config, { "jiraissuekey": args[ind] });
        }
      }
      ind = args.indexOf('--joburl');
      if (!(ind > -1)) {
        ind = args.indexOf('--JOBURL');
      }
      if (ind > -1) {
        ind++;
        if (args.length < ind + 1) {
          console.error("runner job url argument not passed")
        } else {
          console.log("job url IS " + args[ind]);
          Object.assign(config, { "joburl": args[ind] });
        }
      }
    }
    let maintenanceRetrievalPromise = null;
    if (config.azureMaintEventConfig) {

      if (config.azureMaintEventConfig.vaultConfig &&
        config.azureMaintEventConfig.vaultConfig.path &&
        config.azureMaintEventConfig.vaultConfig.key) {
        if (!vault) {
          if (!config.vaultConfig) {
            throw new Error('Missing config.vaultConfig');
          }
          vault = new VaultService(config);
        }
        await vault.getSecret(config.azureMaintEventConfig.vaultConfig.path, config.azureMaintEventConfig.vaultConfig.key).then(value => {
          if (value[config.azureMaintEventConfig.vaultConfig.key]) {
            Object.keys(value[config.azureMaintEventConfig.vaultConfig.key]).forEach(varKey => {
              if (!config.azureMaintEventConfig[varKey]) {
                config.azureMaintEventConfig[varKey] = value[config.azureMaintEventConfig.vaultConfig.key][varKey]
              }
            })
          }
        })
      }
      try {
        let ames = new AzureMaintEventService(config.azureMaintEventConfig);
        let now = new Date();
        maintenanceRetrievalPromise = ames.isMaintenanceOngoing(false, now, config.env);
      } catch (error) {
        console.error('AzureMaintEventService isMaintenanceOngoing return error: \n' + error);
      }
    }
    let euiFilteredReporters = config.reporters.filter(
      item => Array.isArray(item) && typeof (item[0]) == 'function' && item[0].name == 'EuiReporter'
    )

    let imagePath = path.join(tesseractTmpFolder, Date.now() + '.png');

    if (euiFilteredReporters.length > 0) {
      if (euiFilteredReporters[0][1] && euiFilteredReporters[0][1].loggerOptions) {
        let options = euiFilteredReporters[0][1];
        console.log('eUIReporter FILE CLEANUP');
        if (fs.existsSync(options.loggerOptions.reportDir)) {
          try {
            rimraf.sync(options.loggerOptions.reportDir);
          } catch (error) {
            // retry
            if (fs.existsSync(options.loggerOptions.reportDir)) {
              await rimraf.sync(options.loggerOptions.reportDir);
            }
          }
        }
        try {
          fs.mkdirSync(options.loggerOptions.reportDir);
        } catch (error) {
          // retry
          fs.mkdirSync(options.loggerOptions.reportDir);
        }
        try {
          fs.mkdirSync(path.join(options.loggerOptions.reportDir, 'screenshots'));
        } catch (error) {
          // retry
          fs.mkdirSync(path.join(options.loggerOptions.reportDir, 'screenshots'));
        }
      } else {
        throw new Error('Could not retrieve EuiReporter options for file cleanup, please check the config file')
      }
    }
    let allureFilteredReporter = config.reporters.filter(
      item => Array.isArray(item) && item[0] == 'allure'
    );
    if (allureFilteredReporter.length > 0 && allureFilteredReporter[0][1].cleanBefore) {
      console.log('Allure Reporter FILE CLEANUP');
      if (fs.existsSync(allureFilteredReporter[0][1].outputDir)) {
        rimraf.sync(allureFilteredReporter[0][1].outputDir);
      }
    }
    // Adds all entries of secrets in vaultEnvVars as environment variables (unless they already exist)
    if (config.vaultEnvVars) {
      if (!config.vaultConfig) {
        throw new Error('Missing config.vaultConfig');
      }
      if (!vault) {
        if (!config.vaultConfig) {
          throw new Error('Missing config.vaultConfig');
        }
        vault = new VaultService(config);
      }
      let promises = [];
      Object.keys(config.vaultEnvVars).forEach(varReqKey => {
        if (config.vaultEnvVars[varReqKey].path && config.vaultEnvVars[varReqKey].key) {
          promises.push(
            vault.getSecret(config.vaultEnvVars[varReqKey].path, config.vaultEnvVars[varReqKey].key).then(value => {
              if (value[config.vaultEnvVars[varReqKey].key]) {
                Object.keys(value[config.vaultEnvVars[varReqKey].key]).forEach(envVarKey => {
                  if (!process.env[envVarKey]) {
                    process.env[envVarKey] = value[config.vaultEnvVars[varReqKey].key][envVarKey]
                  }
                })
              }
            })
          );
        }
      });
      await Promise.all(promises);
    }
    if (config.azureMaintEventConfig) {
      try {
        config.maintenances = await maintenanceRetrievalPromise;
      } catch (error) {
        console.error('Maintenance events retrieval return error: \n' + error);
      }
    }
  },
  async beforeSession(configuration, capabilities, specs) {
    console.log("In beforeSession Total system memory: " + os.totalmem() / 1073741824);
    console.log("Free system memory: " + os.freemem() / 1073741824);
    let maintenanceRetrievalPromise = null;
    if (config.azureMaintEventConfig) {
      try {
        let ames = new AzureMaintEventService(config.azureMaintEventConfig);
        let now = new Date();
        maintenanceRetrievalPromise = ames.isMaintenanceOngoing(true, now, config.env);
      } catch (error) {
        console.error('AzureMaintEventService isMaintenanceOngoing return error: \n' + error);
      }
    }

    if (config.testUsers && config.testUsers.length > 0) {
      if (!(config.testUserData)) {
        let testuserConfigError = false;
        if (!config.vaultConfig) {
          console.error('config.vaultConfig is missing');
          testuserConfigError = true;
        } else {
          if (!config.vaultConfig.endpoint) {
            console.error('config.vaultConfig.endpoint is missing or empty');
            testuserConfigError = true;
          }
          if (!config.vaultConfig.token && !config.vaultConfig.user) {
            console.error('config.vaultConfig.user or token is missing or empty');
            testuserConfigError = true;
          }
          if (config.vaultConfig.user) {
            if (!config.vaultConfig.password) {
              console.error('config.vaultConfig.password is missing or empty');
              testuserConfigError = true;
            }
          }
        }

        if (!config.userenv) {
          console.error('Please add or check config.userenv');
          testuserConfigError = true;
        }
        if (!testuserConfigError) {
          let vault = new VaultService(config);

          let index = 0;
          config.testUserData = {};
          let vaultTestusersPath = '/secret/test_users/';
          if (config.vaultConfig.testusersPath) {
            vaultTestusersPath = config.vaultConfig.testusersPath;
          }
          while (index < config.testUsers.length) {
            let count = 0;
            let promises = [];
            for (index; index < config.testUsers.length && count < 5; index++) {
              let user = config.testUsers[index];
              promises.push(
                vault.getSecret(vaultTestusersPath + config.userenv.toLowerCase(), user)
              );
              count++;
            }
            await Promise.all(promises).then((values) => {
              values.forEach(value => {
                Object.assign(config.testUserData, value);
              });
            });
          }
        }
      }
    }
    if (config.azureMaintEventConfig) {
      try {
        config.maintenances = await maintenanceRetrievalPromise;
      } catch (error) {
        console.error('Maintenance events retrieval return error: \n' + error);
      }
    }
    browser.config = config;
  },
  // Gets executed before test execution begins. At this point you will have access to all global
  // variables like `browser`. It is the perfect place to define custom commands.
  async before() {
    console.log("In before");

    global.chaiExpect = expectChai;
    if (config.browserheight && config.browserwidth) {
      browser.setWindowSize(config.browserwidth, config.browserheight)
    }
    coreCommands(config);

    if (!this.puppeteerBrowser && (browser.config.logPageMetrics || browser.config.puppeteerNetworkCapture || (browser.config.automationProtocol && browser.config.automationProtocol == 'devtools'))) {
      this.puppeteerBrowser = await browser.getPuppeteer();
      this.puppeteerPages = await this.puppeteerBrowser.pages();
    }

    if (browser.config.automationProtocol && browser.config.automationProtocol == 'devtools') {

      if (config.browserlog
        && (config.browserlog === 'always' || config.browserlog === 'fail')) {
        this.consoleLog = [];
        function describePuppConsoleArg(jsHandle) {
          // Added as consoleObj didn't always fully resolve resulting in a message text: ERROR JSHandle@object 
          try {
            return jsHandle.executionContext().evaluate(obj => {
              return obj;
            }, jsHandle);
          } catch (error) {
            return false;
          }
        }
        this.puppeteerPages[0].on("console", async consoleObj => {
          let objMsg = await consoleObj.text();
          if (objMsg.indexOf("JSHandle@") > -1) {
            // make sure message is fully resolved, optionally use args
            const args = await Promise.all(consoleObj.args().map(arg => describePuppConsoleArg(arg)));
            objMsg = await consoleObj.text();
          }
          let objType = await consoleObj.type();

          if (objType == "error"){
            objMsg = objMsg  + "\nSTACK: " + JSON.stringify(consoleObj.stackTrace());
          }

          let logItem = {
            timestamp: Date.now(),
            level: objType,
            message: objMsg,
            source: "puppeteer page on console"
          }
          if (this.consoleLog.length == 0 || (JSON.stringify(this.consoleLog[this.consoleLog.length - 1]) !== JSON.stringify(logItem))) {
            this.consoleLog.push(logItem)
          }
        }).on("pageerror", async pageErrorObj => {
          let logItem = {
            timestamp: Date.now(),
            level: await pageErrorObj.type(),
            message: await pageErrorObj.text(),
            source: "puppeteer page on pageerror"
          }
          if (this.consoleLog.length == 0 || (JSON.stringify(this.consoleLog[this.consoleLog.length - 1]) !== JSON.stringify(logItem))) {
            this.consoleLog.push(logItem)
          }
        });
      }
    }
  },

  async beforeSuite(suite) {
    console.log("In beforeSuite testcase: " + suite.title);
    this.suiteInError = false;
    if (browser.config.automationProtocol && browser.config.automationProtocol == 'devtools') {
      await browser.pageClientSend('Page.setDownloadBehavior', { behavior: 'allow', downloadPath: tmpDir });
      if (browser.config.devtoolsProxyCredentials) {
        await browser.pageAuthenticate(browser.config.devtoolsProxyCredentials);
      }
    }
  },

  async beforeTest(test, context) {
    console.log("In beforeTest test step: " + test.title);
    console.log("Free system memory: " + os.freemem() / 1073741824);
    // only works on local execution (no selenium grid, saucelabs or other)
    if (!this.puppeteerBrowser && (browser.config.logPageMetrics || browser.config.puppeteerNetworkCapture || (browser.config.automationProtocol && browser.config.automationProtocol == 'devtools'))) {
      this.puppeteerBrowser = await browser.getPuppeteer();
      this.puppeteerPages = await this.puppeteerBrowser.pages();
    }
    if (browser.config.puppeteerNetworkCapture) {
      if (!this.puppeteerHars) {
        this.puppeteerHars = [];
      }
      if (this.puppeteerHars.length < 1 || !this.puppeteerHars[this.puppeteerHars.length - 1].inProgress) {
        this.puppeteerHars.push(new PuppeteerHar(this.puppeteerPages[0]));
        await this.puppeteerHars[this.puppeteerHars.length - 1].start(browser.config.puppeteerNetworkCapture);
      }
    }
    if (config.browserlog
      && (config.browserlog === 'always' || config.browserlog === 'fail')
      && (browser.config.automationProtocol && browser.config.automationProtocol == 'devtools')) {
      this.consoleLog = [];
    }
  },

  async onReload(oldSessionId, newSessionId) {
    console.log("RELOADING BROWSER old sessionId: " + oldSessionId + " New sessionId: " + newSessionId);
    if (browser.config.logPageMetrics || browser.config.puppeteerNetworkCapture || (browser.config.automationProtocol && browser.config.automationProtocol == 'devtools')) {
      this.puppeteerBrowser = await browser.getPuppeteer();
      this.puppeteerPages = await this.puppeteerBrowser.pages();
    }
    if (browser.config.puppeteerNetworkCapture) {
      this.puppeteerHars.push(new PuppeteerHar(this.puppeteerPages[0]));
      await this.puppeteerHars[this.puppeteerHars.length - 1].start(browser.config.puppeteerNetworkCapture);
    }
  },
  async beforeCommand(commandName, args) {
    // console.log("COMMAND: " + commandName);
    switch (commandName) {
      case "reloadSession":
        if (browser.config.puppeteerNetworkCapture) {
          if (this.puppeteerHars.length > 0) {
            await this.puppeteerHars[this.puppeteerHars.length - 1].stop(browser.config.networkResolveHosts, true);
          }
        }
        break;
      default:
        break;
    }
  },
  async afterCommand(commandName, args, result, error) {
    switch (commandName) {
      case "switchToParentFrame":
        this.previousFrame = browser.config.currentFrame;
        browser.config.currentFrame = "parent";
        break;
      case "switchToFrame":
        this.previousFrame = browser.config.currentFrame;
        browser.config.currentFrame = args[0];
        break;
      case "reloadSession":
        if (browser.config.automationProtocol && browser.config.automationProtocol == 'devtools') {
          await browser.pageClientSend('Page.setDownloadBehavior', { behavior: 'allow', downloadPath: tmpDir });
          if (browser.config.devtoolsProxyCredentials) {
            await browser.pageAuthenticate(browser.config.devtoolsProxyCredentials);
          }
        }
        break;
      default:
        break;
    }
    // if (this.previousFrame !== browser.config.currentFrame) {
    //   if (!this.puppeteerBrowser && (browser.config.logPageMetrics || browser.config.puppeteerNetworkCapture || (browser.config.automationProtocol && browser.config.automationProtocol == 'devtools'))) {
    //     this.puppeteerBrowser = await browser.getPuppeteer();
    //     this.puppeteerPages = await this.puppeteerBrowser.pages();
    //   }
    //   if (browser.config.puppeteerNetworkCapture && (!this.puppeteerHars || !this.puppeteerHars.inProgress)) {
    //     this.puppeteerHars = new PuppeteerHar(this.puppeteerPages[0]);
    //     await this.puppeteerHars.start(browser.config.puppeteerNetworkCapture);
    //   }
    // }
  },

  async afterTest(test, context, { error, result, duration, passed, retries }) {
    console.log("In afterTest test step: " + test.title);
    if (error) {
      this.suiteInError = true;
    }
    try {
      await browser.stopWatching();
    } catch (err) {
      // Don't throw error to avoid overflowing error (into next TC)
    }
    try{
      if(browser.config.currentstepMeta.uuid){
        allureReporter.addAttachment('UUID', browser.config.currentstepMeta.uuid);
      }
    }catch(error){}
    try{
      if(browser.config.currentstepMeta.reportGroups){
        allureReporter.addAttachment('reportGroups', browser.config.currentstepMeta.reportGroups);
      }
    }catch(error){}
    try{
      if(browser.config.currentstepMeta.stepReportGroups){
        allureReporter.addAttachment('stepReportGroups', browser.config.currentstepMeta.stepReportGroups);
      }
    }catch(error){}
    try{
      if( browser.config.loggedInUser){
        allureReporter.addAttachment('Username',  browser.config.loggedInUser);
      }
    }catch(error){}
    try{
      if(browser.config.currentstepMeta.zephyrKey){
        allureReporter.addAttachment('ZephyrKey',  browser.config.currentstepMeta.zephyrKey);
      }
    }catch(error){}
    if (browser.config.logPageMetrics) {
      try {
        let metrics = await this.puppeteerPages[0].metrics();
        metrics["JSHeapUsedSizeInMBytes"] = parseFloat(metrics['JSHeapUsedSize'] / 1024 / 1024).toFixed(2);
        metrics["JSHeapTotalSizeInMBytes"] = parseFloat(metrics['JSHeapTotalSize'] / 1024 / 1024).toFixed(2);
        //try{  // to enable in case we need it
        //  metrics["JSHeapSizeLimitInMBytes"] = await browser.execute('return parseFloat(window.performance.memory.jsHeapSizeLimit/1024/1024).toFixed(2)');
        //}catch(error){}
        let metricsStr = JSON.stringify(metrics, null, 2);
        allureReporter.addAttachment('page metrics', metricsStr);
      } catch (error) {
        console.error("Error getting page metrics: " + error.message);
        if (error.stack) {
          console.error(error.stack);
        }
      }
    }
    if (browser.config.puppeteerNetworkCapture && browser.config.puppeteerNetworkCaptureAfterIt) {
      browser.config.puppeteerNetworkCaptureAfterIt = false;
      let deductTimeStart = new Date().getTime();
      try {
        for (let index = 0; index < this.puppeteerHars.length; index++) {
          const puppeteerHar = this.puppeteerHars[index];
          let networkResults = await puppeteerHar.stop(browser.config.networkResolveHosts);
          let har = networkResults.har;
          allureReporter.addAttachment('networkInfo', JSON.stringify(networkResults.networkInfo, null, 2));

          if (har && har.log && ((har.log.pages && Array.isArray(har.log.pages) && har.log.pages.length > 0) || (har.log.entries && Array.isArray(har.log.entries) && har.log.entries.length > 0))) {
            let harfile = path.join(tmpDir, browser.sessionId + '_network.har');
            if (fs.existsSync(harfile)) {
              await rimraf.sync(harfile);
            }
            // await promisify(fs.writeFile)(harfile, JSON.stringify(har));
            fs.writeFileSync(harfile, JSON.stringify(har));

            let zipfile = path.join(tmpDir, browser.sessionId + '_networkhar.zip');
            if (fs.existsSync(zipfile)) {
              await rimraf.sync(zipfile);
            }
            await utils.zip(harfile, zipfile);
            let zipFileBuffer = fs.readFileSync(zipfile);
            allureReporter.addAttachment('networklog', zipFileBuffer, 'application/zip');
            await rimraf.sync(harfile);
            await rimraf.sync(zipfile);
            if (this.suiteInError && browser.config.networkAdditionalChecks) {
              let resultText = "";
              let harEntries = har.log.entries;
              if (browser.config.networkAdditionalChecks.threshholds) {
                resultText += await networkUtils.checkHarEntries(harEntries, browser.config.networkAdditionalChecks.threshholds);
              }
              if (browser.config.networkAdditionalChecks.traceroute) {
                resultText += await networkUtils.doTraceroute(browser.config.networkAdditionalChecks.traceroute);
              }
              if (resultText !== "") {
                allureReporter.addAttachment('network diagnostic', resultText);
              }
            }
          }
        }
      } catch (err) {
        console.log("puppeteerNetworkCapture ERROR mesage: " + err.message + " During TC: " + test.title);
      } finally {
        let deductTime = deductTimeStart - (new Date().getTime());
        await browser.deductTime(deductTime);
        this.puppeteerHars = [];
      }
    }

    try {
      let url = await browser.getUrl();
      allureReporter.addAttachment('last url', url);
    } catch (err) {
      allureReporter.addAttachment('last url', "An error occured while trying to get the url: " + err.message);
    }
    if (browser.config.maintenances) {
      allureReporter.addAttachment('MAINTENANCE', "A maintenance was ongoing during this test this may have affected the result");
    }

    if (browser.config.currentPuppScreenshots &&
      Array.isArray(browser.config.currentPuppScreenshots) &&
      browser.config.currentPuppScreenshots.length > 0) {
      browser.config.currentPuppScreenshots.forEach(puppScreenshot => {
        let title = path.basename(puppScreenshot, ".png")
        let screenshotFileBuffer = fs.readFileSync(puppScreenshot);
        allureReporter.addAttachment(title, screenshotFileBuffer, 'image/png');
      });
      browser.config.currentPuppScreenshots = [];
    }

    let doScreenshot = false;
    if (browser.config.currentstepMeta) {
      if (browser.config.currentstepMeta.AlwaysReportIfTimeExceeds && (duration >= browser.config.currentstepMeta.AlwaysReportIfTimeExceeds)) {
        doScreenshot = true;
      }
      browser.config.currentstepMeta = false;
    }

    if (config.screenshot &&
      (config.screenshot === 'always' || (error && config.screenshot === 'fail') || doScreenshot)) {
      try {
        let filename = sanitize(test.title + '_' + Date.now() + '.png');
        let screenshotFile = path.join(screenshotPath, filename);
        try {
          // Take a screensot on the html element because taking a screenshot on the browser sometimes changes the view
          await browser.switchToParentFrame();
          let htmlEl = await browser.$('html');
          let screenshot = await htmlEl.saveScreenshot(screenshotFile);
          allureReporter.addAttachment('Screenshot', screenshot, 'image/png');
        } catch (e1) {
          console.log("Error taking screenshot on HTML element in afterTest, retying on browser: " + e1.message)
          await browser.takeScreenshot();
        }
      } catch (e2) {
        console.log("Error taking screenshot on browser in afterTest: " + e2.message)
      }
    }

    if (config.browserlog &&
      (config.browserlog === 'always' || (error && config.browserlog === 'fail'))) {
      let browserlog = null;
      if (browser.config.automationProtocol && browser.config.automationProtocol == 'devtools') {
        if (this.consoleLog && Array.isArray(this.consoleLog) && this.consoleLog.length > 0) {
          browserlog = this.consoleLog;
        }
      } else {
        browserlog = await browser.getLogs('browser');
      }

      let allureFilteredReporter = config.reporters.filter(
        item => Array.isArray(item) && item[0] == 'allure'
      );
      if (Array.isArray(browserlog) && browserlog.length > 0 && allureFilteredReporter.length > 0) {
        let formattedBrowserlog = '';

        let lastLogItem = "";

        browserlog.forEach(browserlogItem => {
          let currentLogItem = "\r\n\r\nTIME: " + publisherUtils.getDateAndTime(new Date(browserlogItem.timestamp), true) +
            '\r\nLEVEL: ' + browserlogItem.level +
            '\r\nMESSAGE: ' + browserlogItem.message.replace('/\n/g', '\r\n') +
            '\r\nSOURCE: ' + browserlogItem.source;
          if (currentLogItem !== lastLogItem) {
            formattedBrowserlog += currentLogItem;
            lastLogItem = currentLogItem;
          }
        });
        allureReporter.addAttachment('browserlog', formattedBrowserlog);
      }
    }
  },

  // Gets executed after all workers got shut down and the process is about to exit. It is not
  // possible to defer the end of the process using a promise.
  async onComplete() {
    console.log("In onComplete");
    let forceReport = false;

    console.log("ONGOING MAINTENANCES: " + JSON.stringify(config.maintenances));
    let euiFilteredReporters = config.reporters.filter(
      item => Array.isArray(item) && typeof (item[0]) == 'function' && item[0].name == 'EuiReporter'
    )
    let allureFilteredReporter = config.reporters.filter(
      item => Array.isArray(item) && item[0] == 'allure'
    );
    if (euiFilteredReporters.length > 0) {
      // Merge report files
      let reportFiles = fs.readdirSync(euiFilteredReporters[0][1].loggerOptions.reportDir);
      reportFiles = reportFiles.filter(file => file.endsWith('.json'));
      let suiteReport = {
        start: '',
        meta: { invalidateResult: false },
        total_duration: 0,
        failed_describes: 0,
        total_failures: 0,
        deducted_failures: 0,
        describes: [],
        extraLink: "",
        joburl: "",
        totalDescribes: 0,
        totalSteps: 0,
        totalDescribesFailed: 0,
        totalStepsFailed: 0,
        describesSuccessRate: 0,
        stepsSuccessRate: 0, 
        describesMap: []
      };
      if (config.maintenances && config.azureMaintEventConfig && config.azureMaintEventConfig.maintEventsOverviewLink) {
        suiteReport.extraLink = config.azureMaintEventConfig.maintEventsOverviewLink;
      }
      if(config.joburl){
        suiteReport.joburl = config.joburl;
      }
      let suiteReportGroups = [];
      let suiteStepReportGroups = [];

      let sauceNerworkHarJobs = [];
      let saucelabsService;

      reportFiles.forEach(reportFile => {
        let describe = fs.readFileSync(path.join(euiFilteredReporters[0][1].loggerOptions.reportDir, reportFile), 'utf8');
        describe = JSON.parse(describe);

        let describeMeta = { reportGroups: [], testedSystems: [], stepReportGroups: [] };
        if (describe.meta) {
          if (describe.meta.uuid) {
            describeMeta.uuid = describe.meta.uuid;
            delete describe.meta['uuid'];
          }
          if (describe.meta.reportGroups) {
            describe.meta.reportGroups.forEach(reportGroup => {
              reportGroup = sanitize(reportGroup.trim().toUpperCase());
              if (!describeMeta.reportGroups.includes(reportGroup)) {
                describeMeta.reportGroups.push(reportGroup);
              }
              if (!suiteReportGroups.includes(reportGroup)) {
                suiteReportGroups.push(reportGroup);
              }
            });
            delete describe.meta['reportGroups'];
          }
          if (describe.meta.stepReportGroups) {
            describe.meta.stepReportGroups.forEach(stepReportGroup => {
              stepReportGroup = sanitize(stepReportGroup.trim().toUpperCase());
              if (!describeMeta.stepReportGroups.includes(stepReportGroup)) {
                describeMeta.stepReportGroups.push(stepReportGroup);
              }
              if (!suiteStepReportGroups.includes(stepReportGroup)) {
                suiteStepReportGroups.push(stepReportGroup);
              }
            });
          }
          if (describe.meta.testedSystems) {
            describe.meta.testedSystems.forEach(testedSystem => {
              if (!describeMeta.testedSystems.includes(testedSystem)) {
                describeMeta.testedSystems.push(testedSystem);
              }
            });
          }
        }

        if (describe.tests && Array.isArray(describe.tests) && describe.tests.length > 0) {
          if (describe.tests[0].meta && describe.tests[0].meta.zephyrKey) {
            describeMeta.zephyrKey = describe.tests[0].meta.zephyrKey;
          }

          describe.tests.forEach(test => {
            delete test.meta['uuid'];
            delete test.meta['zephyrKey'];
            delete test.meta['reportGroups'];
            suiteReport.totalSteps += 1;
            if (config.disableDeductedWait) {
              test.deductedWaits = 0;
            }
            let testDurationMs = test._duration - test.deductedWaits - test.deductTime;
            if (test.meta.AlwaysReportIfTimeExceeds && (testDurationMs >= test.meta.AlwaysReportIfTimeExceeds)) {
              forceReport = true;
            }

            if (test.output && Array.isArray(test.output)) {
              let outputFilteredforRetrieveSauceNetworkHar = test.output.filter(
                item => item.name && item.name == 'retrieveSauceNetworkHar' &&
                  item.result && item.result.file
              )
              if (Array.isArray(outputFilteredforRetrieveSauceNetworkHar) && outputFilteredforRetrieveSauceNetworkHar.length > 0) {
                if (!saucelabsService) {
                  saucelabsService = new SaucelabsService();
                }
                sauceNerworkHarJobs.push(...outputFilteredforRetrieveSauceNetworkHar)
              }
            }
            if(test.state == 'failed'){
              suiteReport.totalStepsFailed += 1;
            }
          });
        }

        describe.start = Date.parse(describe.start);
        describe.end = Date.parse(describe.end);
        suiteReport.totalDescribes += 1;
        if (!suiteReport.start) {
          suiteReport.start = describe.start;
          suiteReport.end = describe.end;
          Object.assign(suiteReport.meta, describe.meta);
        } else {
          if (suiteReport.start > describe.start) {
            suiteReport.start = describe.start;
          }
          if (suiteReport.end < describe.end) {
            suiteReport.end = describe.end;
          }
        }
        if (!suiteReport.deductedWaits) {
          suiteReport.deductedWaits = 0;
        } else if (config.disableDeductedWait) {
          suiteReport.deductedWaits = 0;
        }
        if (!suiteReport.deductTime) {
          suiteReport.deductTime = 0;
        }
        if (describe.failures > 0) {
          suiteReport.failed_describes += 1;
          suiteReport.total_failures += describe.failures;
          suiteReport.totalDescribesFailed +=1;
        }
        suiteReport.deducted_failures += describe.deductedfailures;
        suiteReport.total_duration += describe._duration;
        if (config.disableDeductedWait) {
          describe.deductedWaits = 0;
        }
        suiteReport.deductedWaits += describe.deductedWaits;
        suiteReport.deductTime += describe.deductTime;
        if (describe.meta.invalidateResult) {
          suiteReport.meta.invalidateResult = true;
        }
        delete describe['meta'];
        describe.meta = describeMeta;
        delete describe['type'];
        suiteReport.describes.push(describe);

      });
      suiteReport.describesSuccessRate = ((suiteReport.totalDescribes - suiteReport.totalDescribesFailed)*100)/suiteReport.totalDescribes;
      suiteReport.stepsSuccessRate = ((suiteReport.totalSteps - suiteReport.totalStepsFailed)*100)/suiteReport.totalSteps;
      suiteReport.describes.forEach(describe => {
        suiteReport.describesMap.push(
          {
            'uuid': describe.meta.uuid,
            'totalSteps': describe.tests.length,
            'failures': describe.failures
          }
        );
      });

      let allResults = { allResults: suiteReport, groupedResults: {} };
      let generalTestData = Object.assign({}, suiteReport);
      let allDescribes = generalTestData.describes;
      delete generalTestData['start'];
      delete generalTestData['end'];
      generalTestData.total_duration = 0;
      generalTestData.failed_describes = 0;
      generalTestData.total_failures = 0;
      generalTestData.deducted_failures = 0;
      generalTestData.deductedWaits = 0;
      generalTestData.deductTime = 0;
      generalTestData.totalDescribes = 0,
      generalTestData.totalSteps = 0,
      generalTestData.totalDescribesFailed = 0,
      generalTestData.totalStepsFailed = 0,
      generalTestData.describesSuccessRate = 0,
      generalTestData.stepsSuccessRate = 0
      generalTestData.describesMap = []
      delete generalTestData['describes'];

      suiteReportGroups.forEach(suiteReportGroup => {
        let currentGroupReport = Object.assign({}, generalTestData);
        currentGroupReport.meta.testedSystems = [];
        currentGroupReport.describesMap = [];

        let filteredDescribes = allDescribes.filter(
          item => Array.isArray(item.meta.reportGroups) && item.meta.reportGroups.includes(suiteReportGroup)
        );

        filteredDescribes.forEach(describe => {
          currentGroupReport.totalDescribes += 1;
          if (!currentGroupReport.start) {
            currentGroupReport.start = describe.start;
            currentGroupReport.end = describe.end;
          } else {
            if (currentGroupReport.start > describe.start) {
              currentGroupReport.start = describe.start;
            }
            if (currentGroupReport.end < describe.end) {
              currentGroupReport.end = describe.end;
            }
          }
          if (describe.failures > 0) {
            currentGroupReport.failed_describes += 1;
            currentGroupReport.total_failures += describe.failures;
            currentGroupReport.totalDescribesFailed +=1;
          }
          currentGroupReport.deducted_failures += describe.deductedfailures;
          currentGroupReport.total_duration += describe._duration;
          if (config.disableDeductedWait) {
            describe.deductedWaits = 0;
          }
          currentGroupReport.deductedWaits += describe.deductedWaits;
          currentGroupReport.deductTime += describe.deductTime;
          if (describe.meta.testedSystems) {
            describe.meta.testedSystems.forEach(testedSystem => {
              if (!currentGroupReport.meta.testedSystems.includes(testedSystem)) {
                currentGroupReport.meta.testedSystems.push(testedSystem);
              }
            });
          }
          // add describes map 
          currentGroupReport.describesMap.push(
            {
              'uuid': describe.meta.uuid,
              'totalSteps': describe.tests.length,
              'failures': describe.failures
            }
          );
 
          /// added to calculate the no of steps per report group 

          describe.tests.forEach(test => {
            if(test.retries == 0){
              currentGroupReport.totalSteps += 1;
            }
            if (test.state === 'failed') {
              currentGroupReport.totalStepsFailed += 1;
            }
          });
    
        })
        currentGroupReport.describesSuccessRate = ((currentGroupReport.totalDescribes - currentGroupReport.totalDescribesFailed)*100)/ currentGroupReport.totalDescribes;
        currentGroupReport.stepsSuccessRate = ((currentGroupReport.totalSteps - currentGroupReport.totalStepsFailed)*100)/ currentGroupReport.totalSteps;
        if(currentGroupReport.meta.stepReportGroups){
          delete currentGroupReport.meta['stepReportGroups'];
        }
        allResults.groupedResults[suiteReportGroup] = currentGroupReport;
      });

      //stepReportGroup
      suiteStepReportGroups.forEach(suiteStepReportGroup => {
        if(!suiteStepReportGroup.startsWith(stepReportGroupTag)){
          throw new Error('Suite Step report group: ' +suiteStepReportGroup+ ' should start with ' + stepReportGroupTag)
        }
        if(suiteReportGroups.includes(suiteStepReportGroup)){
          throw new Error('Suite Step report group: ' +suiteStepReportGroup+ ' should not be defined also as reportGroup, SuitesReportGroupList: ' + suiteReportGroups)
        }
        let currentGroupReport = Object.assign({}, generalTestData);
        currentGroupReport.meta.testedSystems = [];
        currentGroupReport.describesMap = [];
        currentGroupReport.total_duration = 0;
        currentGroupReport.deductedWaits = 0;
        currentGroupReport.deductTime = 0;
        currentGroupReport.start = 0;
        currentGroupReport.end = 0;
        currentGroupReport.totalDescribes = 0;
        currentGroupReport.deducted_failures = 0;
        currentGroupReport.failed_describes = 0;
        currentGroupReport.total_failures = 0;
        currentGroupReport.totalSteps = 0,
        currentGroupReport.totalDescribesFailed = 0,
        currentGroupReport.totalStepsFailed = 0,
        currentGroupReport.describesSuccessRate = 0,
        currentGroupReport.stepsSuccessRate = 0


        let filteredDescribes = allDescribes.filter(
          item => Array.isArray(item.meta.stepReportGroups) && item.meta.stepReportGroups.includes(suiteStepReportGroup)
        );

        filteredDescribes.forEach(describe => {
          let stepReportGroupsTotal = 0;
          let stepReportGroupsFailure = 0;
          let describeFailed = false;

          currentGroupReport.totalDescribes += 1;
          if (!currentGroupReport.start) {
            currentGroupReport.start = describe.start;
            currentGroupReport.end = describe.end;
          } else {
            if (currentGroupReport.start > describe.start) {
              currentGroupReport.start = describe.start;
            }
            if (currentGroupReport.end < describe.end) {
              currentGroupReport.end = describe.end;
            }
          }
          
          currentGroupReport.deducted_failures += describe.deductedfailures;
          
          if (describe.meta.testedSystems) {
            describe.meta.testedSystems.forEach(testedSystem => {
              if (!currentGroupReport.meta.testedSystems.includes(testedSystem)) {
                currentGroupReport.meta.testedSystems.push(testedSystem);
              }
            });
          }
          
 
          /// added to calculate the no of steps per report group 
          
          describe.tests.forEach(test => {
            if(test.meta.stepReportGroups){
              if(test.meta.stepReportGroups.includes(suiteStepReportGroup)){                
                currentGroupReport.total_duration += test._duration;
                if (config.disableDeductedWait) {
                  test.deductedWaits = 0;
                }
                currentGroupReport.deductedWaits += test.deductedWaits;
                currentGroupReport.deductTime += test.deductTime;

                stepReportGroupsTotal += 1;
                if(test.retries == 0){
                  currentGroupReport.totalSteps += 1;
                }
                if (test.state === 'failed') {
                  currentGroupReport.totalStepsFailed += 1;
                  stepReportGroupsFailure += 1;
                  describeFailed = true;
                }
                var index = test.meta.stepReportGroups.indexOf(suiteStepReportGroup);
                if (index !== -1) {
                  test.meta.stepReportGroups.splice(index, 1);
                }
              }
              if(test.meta.stepReportGroups == []){
                delete test.meta['stepReportGroups']
              }
            }
          });
          if(describeFailed){
            currentGroupReport.totalDescribesFailed += 1;
          }
          // add describes map 
          currentGroupReport.describesMap.push(
            {
              'uuid': describe.meta.uuid,
              'totalSteps': stepReportGroupsTotal,
              'failures': stepReportGroupsFailure
            }
          );
        })
        currentGroupReport.total_failures = currentGroupReport.totalStepsFailed;
        currentGroupReport.describesSuccessRate = ((currentGroupReport.totalDescribes - currentGroupReport.totalDescribesFailed)*100)/ currentGroupReport.totalDescribes;
        currentGroupReport.stepsSuccessRate = ((currentGroupReport.totalSteps - currentGroupReport.totalStepsFailed)*100)/ currentGroupReport.totalSteps;
        allResults.groupedResults[suiteStepReportGroup] = currentGroupReport;
      });

      let reportfilename = suiteReport.meta.suite + '-' + suiteReport.meta.env + '-' + suiteReport.meta.testedVersion + '.json';
      reportfilename = sanitize(reportfilename);
      let reportFilePath = path.join(euiFilteredReporters[0][1].loggerOptions.reportDir, reportfilename);

      // Remove test output from allResults tests to make the file smaller
      if (allResults.allResults.describes && Array.isArray(allResults.allResults.describes)){
        for (let descrIndex = 0; descrIndex < allResults.allResults.describes.length; descrIndex++) {
          if (allResults.allResults.describes[descrIndex].tests && Array.isArray(allResults.allResults.describes[descrIndex].tests)){
            for (let tstIndex = 0; tstIndex < allResults.allResults.describes[descrIndex].tests.length; tstIndex++) {
              if (allResults.allResults.describes[descrIndex].tests[tstIndex].output){
                delete allResults.allResults.describes[descrIndex].tests[tstIndex]['output'];
              }
              if (allResults.allResults.describes[descrIndex].tests[tstIndex].meta.stepReportGroups){
                delete allResults.allResults.describes[descrIndex].tests[tstIndex].meta['stepReportGroups'];
              }
            }
          }
        }
      }
      fs.writeFileSync(reportFilePath,
        JSON.stringify(allResults), function (err) {
          if (err) {
            return console.error(err);
          }
        }
      );

      console.log("Publishing");
      if (sauceNerworkHarJobs.length > 0) {
        // Need to retrieve network logs from saucelabs
        console.log("Will retrieve network logs from saucelabs");
        console.log("Waiting 30 seconds to allow saucelabs to complete the file");
        await new Promise(resolve => setTimeout(resolve, 30000));
        let sauceNerworkHarPromises = [];
        sauceNerworkHarJobs.forEach(sauceNetworkHarToRetrieve => {
          sauceNerworkHarPromises.push(saucelabsService.getHar(sauceNetworkHarToRetrieve.sessionId, sauceNetworkHarToRetrieve.result.file));
        });
        console.log("Awaiting saucelabs network log retrieval");
        await Promise.all(sauceNerworkHarPromises);
        console.log("Finished saucelabs network log retrieval");
      }

      let reportFolder = global.tmpDir;
      console.log("Publishing Allure");
      if (allureFilteredReporter.length > 0) {
        let allurePromise = new Promise((exit) => {  
          let generation;
          // Skip report generation if disableReportGeneration is true
          if (allureFilteredReporter[0][1].disableReportGeneration === true) {
            console.log('Skipping Allure HTML report generation as disableReportGeneration is true');
            exit(0);
            return;
          }
          if (allureFilteredReporter[0][1].reportDir) {
            reportFolder = allureFilteredReporter[0][1].reportDir;
            generation = allure(['generate', allureFilteredReporter[0][1].outputDir, '-o', allureFilteredReporter[0][1].reportDir, '--clean']);
          } else {
            reportFolder = allureFilteredReporter[0][1].outputDir;
            generation = allure(['generate', allureFilteredReporter[0][1].outputDir, '--clean']);
          }
          generation.on('exit', function (exitCode) {
            console.log('Generation is finished with code:', exitCode);
            exit(exitCode);
          });
        });
        await allurePromise;
      }

      console.log('Copy open report scripts to report folder');
      try{
        utils.copy(resourcesPath, openReportPowershell, path.join(process.cwd(), reportFolder))
        utils.copy(resourcesPath, openReportBatch, path.join(process.cwd(), reportFolder))
      }catch(error){}

      console.log("Publishing eui Reporter publishers");
      if (euiFilteredReporters[0][1] && Array.isArray(euiFilteredReporters[0][1].publishers) &&
        euiFilteredReporters[0][1].loggerOptions && euiFilteredReporters[0][1].loggerOptions.reportDir) {
        let publishers = euiFilteredReporters[0][1].publishers;
        console.log("Test has " + publishers.length + " publishers");


        let vault = null;
        for await (const publisher of publishers) {
          if (publisher.config.vaultConfig && publisher.config.vaultConfig.path && publisher.config.vaultConfig.key) {
            if (!vault) {
              vault = new VaultService(config);
            }
            await vault.getSecret(publisher.config.vaultConfig.path, publisher.config.vaultConfig.key).then(value => {
              if (value[publisher.config.vaultConfig.key]) {
                Object.keys(value[publisher.config.vaultConfig.key]).forEach(varKey => {
                  if (!publisher.config[varKey]) {
                    publisher.config[varKey] = value[publisher.config.vaultConfig.key][varKey]
                  }
                })
              }
            })
          }
        }
        for await (const publisher of publishers) {
          publisher.config.reportDir = euiFilteredReporters[0][1].loggerOptions.reportDir;
          if (config.disableDeductedWait) {
            publisher.config.disableDeductedWait = config.disableDeductedWait;
          }
          let pub = new publisher.type(publisher.config);
          if (publisher.config.publish && 
            (publisher.config.publish === 'always' ||
              (publisher.config.publish === 'fail' && (suiteReport.total_failures > 0 || suiteReport.deducted_failures > 0 || forceReport)))) {
            await pub.publish(reportFilePath);
            console.log(pub.constructor.name + ' finished processing');
          } else {
            console.log("SKIPPING " + pub.constructor.name);
          }
        }
      }
    }

    if (allureFilteredReporter.length > 0) {
      // Only open report if openReport is true and report generation wasn't disabled
      if (allureFilteredReporter[0][1].openReport && allureFilteredReporter[0][1].disableReportGeneration !== true) {
        if (allureFilteredReporter[0][1].reportDir) {
          allure(['open', allureFilteredReporter[0][1].reportDir]);
        } else {
          allure(['open']);
        }
        // Wait 3 seconds to allow allure to open without blocking the spec reporter output
        await new Promise(resolve => setTimeout(resolve, 3000));
      }
    }

    if (config.versionCheck && config.bashtimestamp) {
      fs.writeFileSync(path.join(tmpDir, config.versionCheck +
        '.' + config.bashtimestamp + '.' + 'executioncheck.txt'), 'SUCCESS');
      console.log('Wrote executioncheck result');
    }
  },
});