import path from 'path';
import { promises as fs } from 'fs';
import fse from 'fs-extra';
import pMap from 'p-map';
import { getDirectories, normalizeModData, isMod } from '.';
import { getFileMurmurHash2 } from '../../../common/utils';
import { getAddonsByFingerprint, getAddon } from '../../../common/api';

const modsFingerprintsScan = async instancesPath => {
  const mapFolderToInstance = async instance => {
    try {
      const configPath = path.join(
        path.join(instancesPath, instance, 'config.json')
      );
      const config = await fse.readJSON(configPath);

      if (!config.loader) {
        throw new Error(`Config for ${instance} could not be parsed`);
      }

      const modsFolder = path.join(instancesPath, instance, 'mods');
      const modsFolderExists = await fse.pathExists(modsFolder);

      if (!modsFolderExists) return { ...config, name: instance };

      // Check if config.mods has a different number of mods than the actual number of mods

      // Count the actual mods inside the folder
      const files = await fs.readdir(modsFolder);

      const fileNamesToRemove = [];
      const missingMods = {};
      /* eslint-disable */
      // Check for new mods in local storage that are not present in config
      for (const file of files) {
        try {
          const completeFilePath = path.join(modsFolder, file);
          const stat = await fs.lstat(completeFilePath);
          if (stat.isFile() && isMod(completeFilePath, instancesPath)) {
            // Check if file is in config
            if (!(config?.mods || []).find(mod => mod.fileName === file)) {
              const murmurHash = await getFileMurmurHash2(completeFilePath);
              console.log(
                '[MODS SCANNER] Local mod not found in config',
                file,
                murmurHash
              );
              missingMods[file] = murmurHash;
            }
          }
        } catch {}
      }

      // Check for old mods in config that are not present on local storage
      for (const configMod of config?.mods || []) {
        if (!files.includes(configMod.fileName)) {
          fileNamesToRemove.push(configMod.fileName);
          console.log(
            `[MODS SCANNER] Removing ${configMod.fileName} from config`
          );
        }
      }
      /* eslint-enable */

      let newMods = config?.mods || [];

      if (Object.values(missingMods).length !== 0) {
        let success = false;
        let tries = 10;

        while (!success || tries > 10) {
          try {
            const data = await getAddonsByFingerprint(
              Object.values(missingMods)
            );

            const matches = await Promise.all(
              Object.entries(missingMods).map(async ([fileName, hash]) => {
                const exactMatch = (data.exactMatches || []).find(
                  v => v.file.fileFingerprint === hash
                );
                if (exactMatch?.file) {
                  let addonData = null;
                  try {
                    addonData = await getAddon(exactMatch.file.modId);
                    return {
                      ...normalizeModData(
                        exactMatch.file,
                        exactMatch.file.modId,
                        addonData.name
                      ),
                      fileName
                    };
                  } catch {
                    return {
                      fileName,
                      displayName: fileName,
                      packageFingerprint: hash
                    };
                  }
                }
                return {
                  fileName,
                  displayName: fileName,
                  packageFingerprint: hash
                };
              })
            );

            newMods = [...newMods, ...matches];
            success = true;
          } catch (err) {
            console.error(err);
            tries += 1;
            await new Promise(resolve => {
              setTimeout(() => {
                resolve();
              }, 5000);
            });
          }
        }
      }

      const filterMods = newMods
        .filter(_ => _)
        .filter(v => !fileNamesToRemove.includes(v.fileName));

      const newConfig = {
        ...config,
        mods: filterMods
      };

      if (JSON.stringify(config) !== JSON.stringify(newConfig)) {
        await fse.outputJson(configPath, newConfig);
        return { ...newConfig, name: instance };
      }
      return { ...config, name: instance };
    } catch (err) {
      console.error(err);
    }
    return null;
  };

  const folders = await getDirectories(instancesPath);
  const instances = await pMap(
    folders.filter(folder => folder !== '.DS_Store'),
    mapFolderToInstance,
    {
      concurrency: 5
    }
  );
  const hashMap = {};
  // eslint-disable-next-line
  for (const instance of instances) {
    // eslint-disable-next-line
    if (!instance) continue;
    hashMap[instance.name] = instance;
  }

  return hashMap;
};

export default modsFingerprintsScan;
