const fs = require('fs');
const fsP = require('fs').promises;
const path = require('path');
const moment = require('moment');
const fetch  = require('node-fetch');
const FormData = require('form-data');
const { AbortController } = require('node-abort-controller')
const DigestFetch = require('digest-fetch')
const https = require('https');
module.exports = (processCwd,config) => {
    let httpsAgent = new https.Agent({
        rejectUnauthorized: false,
    });
    const parseJSON = (string) => {
        var parsed
        try{
            parsed = JSON.parse(string)
        }catch(err){

        }
        if(!parsed)parsed = string
        return parsed
    }
    const stringJSON = (json) => {
        try{
            if(json instanceof Object){
                json = JSON.stringify(json)
            }
        }catch(err){

        }
        return json
    }
    const stringContains = (find,string,toLowerCase) => {
        var newString = string + ''
        if(toLowerCase)newString = newString.toLowerCase()
        return newString.indexOf(find) > -1
    }
    function getFileDirectory(filePath){
        const fileParts = filePath.split('/')
        fileParts.pop();
        return fileParts.join('/') + '/';
    }
    const checkCorrectPathEnding = (x) => {
        var length=x.length
        if(x.charAt(length-1)!=='/'){
            x=x+'/'
        }
        return x.replace('__DIR__',processCwd)
    }
    const mergeDeep = function(...objects) {
      const isObject = obj => obj && typeof obj === 'object';

      return objects.reduce((prev, obj) => {
        Object.keys(obj).forEach(key => {
          const pVal = prev[key];
          const oVal = obj[key];

          if (Array.isArray(pVal) && Array.isArray(oVal)) {
            prev[key] = pVal.concat(...oVal);
          }
          else if (isObject(pVal) && isObject(oVal)) {
            prev[key] = mergeDeep(pVal, oVal);
          }
          else {
            prev[key] = oVal;
          }
        });

        return prev;
      }, {});
    }
    const nameToTime = (x) => {
        x = x.split('.')[0].split('T')
        if(x[1])x[1] = x[1].replace(/-/g,':')
        x = x.join(' ')
        return x
    }
    const generateRandomId = (x) => {
        if(!x){x=10};var t = "";var p = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        for( var i=0; i < x; i++ )
            t += p.charAt(Math.floor(Math.random() * p.length));
        return t;
    }
    const utcToLocal = (time) => {
        return moment.utc(time).utcOffset(s.utcOffset).format()
    }
    const localToUtc = (time) => {
        return moment(time).utc()
    }
    const formattedTime = (e,x) => {
        if(!e){e=new Date};if(!x){x='YYYY-MM-DDTHH-mm-ss'};
        return moment(e).format(x);
    }
    const fetchTimeout = (url, ms, { signal, ...options } = {}) => {
        const controller = new AbortController();
        const promise = fetch(url, { signal: controller.signal, agent: httpsAgent, ...options });
        if (signal) signal.addEventListener("abort", () => controller.abort());
        const timeout = setTimeout(() => controller.abort(), ms);
        return promise.finally(() => clearTimeout(timeout));
    }
    async function fetchDownloadAndWrite(downloadUrl,outputPath,readFileAfterWrite,options){
        const writeStream = fs.createWriteStream(outputPath);
        const downloadBuffer = await fetch(downloadUrl,options).then((res) => res.buffer());
        writeStream.write(downloadBuffer);
        writeStream.end();
        if(readFileAfterWrite === 1){
            return fs.createReadStream(outputPath)
        }else if(readFileAfterWrite === 2){
            return downloadBuffer
        }
        return null
    }
    function fetchWithAuthentication(requestUrl,options,callback){
        let hasDigestAuthEnabled = options.digestAuth;
        let theRequester;
        const hasUsernameAndPassword = options.username && typeof options.password === 'string'
        const requestOptions = {
            method : options.method || 'GET',
            headers: {'Content-Type': 'application/json'}
        }
        if(requestOptions.method !== 'GET'){
            if(typeof options.postData === 'object'){
                requestOptions.body = JSON.stringify(options.postData)
            }else if(options.postData && typeof options.postData === 'string'){
                try{
                    JSON.parse(options.postData)
                    requestOptions.body = options.postData
                }catch(err){

                }
            }
        }
        if(hasUsernameAndPassword && hasDigestAuthEnabled){
            theRequester = (new DigestFetch(options.username, options.password)).fetch
        }else if(hasUsernameAndPassword){
            theRequester = (new DigestFetch(options.username, options.password, { basic: true })).fetch
        }else{
            theRequester = fetch
        }
        return theRequester(requestUrl,requestOptions)
    }
    function isEven(value) {
        if (value%2 == 0)
            return true;
        else
            return false;
    }
    function asyncSetTimeout(timeoutAmount) {
        return new Promise((resolve,reject) => {
            setTimeout(function(){
                resolve()
            },timeoutAmount)
        })
    }
    function copyFile(inputFilePath,outputFilePath) {
        const response = {ok: true}
        return new Promise((resolve,reject) => {
            function failed(err){
                response.ok = false
                response.err = err
                resolve(response)
            }
            const readStream = fs.createReadStream(inputFilePath)
            const writeStream = fs.createWriteStream(outputFilePath)
            writeStream.on('finish', () => {
                resolve(response)
            })
            writeStream.on('error', failed)
            readStream.on('error', failed)
            readStream.pipe(writeStream)
        })
    }
    async function moveFile(inputFilePath,outputFilePath) {
        try{
            await fsP.rm(outputFilePath)
        }catch(err){}
        await copyFile(inputFilePath, outputFilePath)
        await fsP.rm(inputFilePath)
    }
    function hmsToSeconds(str) {
        var p = str.split(':'),
            s = 0, m = 1;

        while (p.length > 0) {
            s += m * parseFloat(p.pop(), 10);
            m *= 60;
        }

        return s;
    }
    function setDefaultIfUndefined(config, key, defaultValue) {
        const mustDoDefault = !config.userHasSubscribed;
        if (Array.isArray(defaultValue)) {
            if (config[key] === undefined || mustDoDefault) {
                config[key] = [...defaultValue]; // Spread operator to clone the array
            }
        } else {
            if (config[key] === undefined || mustDoDefault) {
                config[key] = defaultValue;
            }
        }
    }
    async function deleteFilesInFolder(folderPath) {
        try {
            const files = await fsP.readdir(folderPath);
            for (const file of files) {
                const filePath = path.join(folderPath, file);
                await fsP.rm(filePath, { recursive: true });
            }
        } catch (error) {
            console.error(`Error deleting files: ${error.message}`);
        }
    }
    function setTimeoutPromise(theTime){
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve()
            },theTime)
        })
    }
    function cleanStringsInObject(obj, isWithinDetectorFilters = false) {
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          // Check if we're entering the detector_filters structure
          const enteringDetectorFilters = !isWithinDetectorFilters &&
                                       (key === 'detector_filters' ||
                                       (key === 'details' &&
                                        typeof obj[key] === 'object' &&
                                        obj[key].hasOwnProperty('detector_filters')));

          // Determine if we're currently within detector_filters
          let currentIsWithinDetectorFilters = isWithinDetectorFilters || enteringDetectorFilters;

          // Special handling for stringified detector_filters
          if ((key === 'detector_filters' || (key === 'details' && obj[key].hasOwnProperty('detector_filters')))) {
            const detectorFiltersTarget = key === 'details' ? obj[key] : obj;
            const detectorFiltersKey = key === 'details' ? 'detector_filters' : key;

            if (typeof detectorFiltersTarget[detectorFiltersKey] === 'string') {
              try {
                const parsed = JSON.parse(detectorFiltersTarget[detectorFiltersKey]);
                detectorFiltersTarget[detectorFiltersKey] = cleanStringsInObject(parsed, true);
                continue; // Skip further processing as we've replaced and cleaned it
              } catch (e) {
                currentIsWithinDetectorFilters = true;
              }
            }
          }

          if (typeof obj[key] === 'string') {
            try {
              const parsed = JSON.parse(obj[key]);
              obj[key] = cleanStringsInObject(parsed, currentIsWithinDetectorFilters);
            } catch (e) {
              if (currentIsWithinDetectorFilters) {
                // Special handling for detector_filters - allow comparison operators
                obj[key] = obj[key].replace(/[^\w\s.\-=+(){}\[\]*$@!`^%#:?\/&,><=!'|"]/gi, '');
              } else {
                // Normal string cleaning
                obj[key] = obj[key].replace(/[^\w\s.\-=+(){}\[\]*$@!`^%#:?\/&,'|"]/gi, '');
              }
            }
          }
          else if (typeof obj[key] === 'object' && obj[key] !== null) {
            if (enteringDetectorFilters && key === 'details') {
              cleanStringsInObject(obj[key].detector_filters, true);
              cleanStringsInObject(obj[key], false);
            } else {
              cleanStringsInObject(obj[key], currentIsWithinDetectorFilters);
            }
          }
          else if (Array.isArray(obj[key])) {
            obj[key].forEach((item, index) => {
              if (typeof item === 'string') {
                try {
                  const parsed = JSON.parse(item);
                  obj[key][index] = cleanStringsInObject(parsed, currentIsWithinDetectorFilters);
                } catch (e) {
                  if (currentIsWithinDetectorFilters) {
                    obj[key][index] = item.replace(/[^\w\s.\-=+(){}\[\]*$@!`^%#:?\/&,><=!'|"]/gi, '');
                  } else {
                    obj[key][index] = item.replace(/[^\w\s.\-=+(){}\[\]*$@!`^%#:?\/&,'|"]/gi, '');
                  }
                }
              } else if (typeof item === 'object' && item !== null) {
                cleanStringsInObject(item, currentIsWithinDetectorFilters);
              }
            });
          }
        }
      }
      return obj;
    }
    function convertNumbersToStrings(form) {
        if (!form || !form.details || typeof form.details !== 'object') {
            return form;
        }
        for (let key in form.details) {
            if (typeof form.details[key] === 'number') {
                form.details[key] = form.details[key].toString();
            }
        }
        return form;
    }
    return {
        parseJSON: parseJSON,
        stringJSON: stringJSON,
        stringContains: stringContains,
        getFileDirectory: getFileDirectory,
        checkCorrectPathEnding: checkCorrectPathEnding,
        nameToTime: nameToTime,
        mergeDeep: mergeDeep,
        generateRandomId: generateRandomId,
        utcToLocal: utcToLocal,
        localToUtc: localToUtc,
        formattedTime: formattedTime,
        isEven: isEven,
        fetchTimeout: fetchTimeout,
        fetchDownloadAndWrite: fetchDownloadAndWrite,
        fetchWithAuthentication: fetchWithAuthentication,
        asyncSetTimeout: asyncSetTimeout,
        copyFile: copyFile,
        hmsToSeconds,
        setDefaultIfUndefined,
        deleteFilesInFolder,
        moveFile,
        setTimeoutPromise,
        cleanStringsInObject,
        convertNumbersToStrings,
    }
}
