'use strict';

const path = require('path');
const fse = require('fs-extra');
const dotenv = require('dotenv');

const AdmZip = require('adm-zip');
const crypto = require('crypto');
const fs = require('fs');
const fg = require('fast-glob');
const runParallelLimit = require('run-parallel-limit');

const defaultExcludes = [
  '.git',
  'package-lock.json',
  'node_modules/serverless',
  'node_modules/aws-sdk/**/*.d.ts',
  'node_modules/aws-sdk/**/*.examples.json',
  'node_modules/aws-sdk/**/*.md',
  'node_modules/aws-sdk/dist',
  'node_modules/aws-sdk/dist-tools',
  'node_modules/aws-sdk/scripts',
];

class FS {
  constructor(data = {}) {
    this.projectRoot = data.projectRoot || process.cwd();

    this.projectFilePath = path.join(this.projectRoot, 'package.json');
    this.dataFilePath = path.join(this.projectRoot, 'data.json');
    this.envFilePath = path.join(this.projectRoot, '.env');

    this.exclude = defaultExcludes.concat(data.exclude || []);
  }

  getProjectRoot() {
    return this.projectRoot;
  }

  readProjectFile() {
    const packageJson = fse.readJson(this.projectFilePath);
    return packageJson;
  }

  async isProjectDirectory() {
    if (await fse.pathExists(this.projectFilePath)) {
      const packageJson = fse.readJson(this.projectFilePath);
      const main = path.join(this.projectRoot, packageJson.main || 'index.js');
      if (await fse.pathExists(main)) {
        return true;
      }
    }
    return false;
  }

  async isCwdEmpty() {
    return fse.promises.readdir(process.cwd()).then((files) => {
      return files.length === 0;
    });
  }

  readEnvFile() {
    const { parsed } = dotenv.config({ path: this.envFilePath });

    return parsed || {};
  }

  async readDataFile() {
    let data;
    try {
      data = JSON.parse(await fse.readFile(this.dataFilePath));
    } catch (e) {
      data = {};
    }

    return data;
  }

  async generateTemplate(templateName = 'simple') {
    await fse.copy(path.join(__dirname, '..', 'templates', templateName), this.projectRoot);
  }

  async readFilesAndMap() {
    const files = {};
    const map = {};

    let bytes = 0;

    // eslint-disable-next-line
    await new Promise(async (resolve, reject) => {
      const filesPaths = await fg('**/*', {
        cwd: this.projectRoot,
        dot: true,
        ignore: this.exclude,
      });
      const tasks = filesPaths.map((filePath) => {
        return (callback) => {
          const absoluteFilePath = path.resolve(this.projectRoot, filePath);

          fs.readFile(absoluteFilePath, (readErr, file) => {
            if (readErr) {
              callback(readErr);
              return;
            }

            // this fixes the following permissions issue:
            // https://github.com/serverless/components/issues/687
            fs.stat(absoluteFilePath, (statErr, stat) => {
              if (statErr) {
                callback(statErr);
                return;
              }

              bytes += stat.size;

              files[filePath] = {
                file,
                stat,
              };

              map[filePath] = crypto.createHash('md5').update(file).digest('hex');

              callback();
            });
          });
        };
      });

      runParallelLimit(tasks, 2048, (err) => {
        if (err) {
          reject(err);
        } else {
          resolve();
        }
      });
    });

    if (bytes > 200000000) {
      throw new Error(
        'Your code size must be less than 200MB.  Try using Webpack or Parcel to reduce your code size.'
      );
    }

    this.map = map;
    this.files = files;

    return { map, files };
  }

  async emptyDir() {
    await fse.emptyDir(this.projectRoot);
  }
}

module.exports = FS;
