const path = require('path');
const util = require('util');
const filenamify = require('filenamify');
const findCacheDir = require('find-cache-dir');
const fs = require('fs-extra');
const globby = require('globby');
const Git = require('./git.js');
const copy = require('./util.js').copy;
const getUser = require('./util.js').getUser;

const log = util.debuglog('gh-pages');

/**
 * Get the cache directory.
 * @param {string} [optPath] Optional path.
 * @return {string} The full path to the cache directory.
 */
function getCacheDir(optPath) {
  const dir = findCacheDir({name: 'gh-pages'});
  if (!optPath) {
    return dir;
  }

  return path.join(dir, filenamify(optPath));
}

/**
 * Clean the cache directory.
 */
exports.clean = function clean() {
  fs.removeSync(getCacheDir());
};

exports.defaults = {
  dest: '.',
  add: false,
  git: 'git',
  depth: 1,
  dotfiles: false,
  branch: 'gh-pages',
  remote: 'origin',
  src: '**/*',
  remove: '.',
  push: true,
  history: true,
  message: 'Updates',
  silent: false,
};

exports.getCacheDir = getCacheDir;

function getRepo(options) {
  if (options.repo) {
    return Promise.resolve(options.repo);
  } else {
    const git = new Git(process.cwd(), options.git);
    return git.getRemoteUrl(options.remote);
  }
}

/**
 * Push a git branch to a remote (pushes gh-pages by default).
 * @param {string} basePath The base path.
 * @param {Object} config Publish options.
 * @param {Function} callback Callback.
 * @return {Promise} A promise.
 */
exports.publish = function publish(basePath, config, callback) {
  if (typeof config === 'function') {
    callback = config;
    config = {};
  }

  const options = Object.assign({}, exports.defaults, config);

  // For backward compatibility before fixing #334
  if (options.only) {
    options.remove = options.only;
  }

  if (!callback) {
    callback = function (err) {
      if (err) {
        log(err.message);
      }
    };
  }

  function done(err) {
    try {
      callback(err);
    } catch (err2) {
      log('Publish callback threw: %s', err2.message);
    }
  }

  try {
    if (!fs.statSync(basePath).isDirectory()) {
      const err = new Error('The "base" option must be an existing directory');
      done(err);
      return Promise.reject(err);
    }
  } catch (err) {
    done(err);
    return Promise.reject(err);
  }

  const files = globby
    .sync(options.src, {
      cwd: basePath,
      dot: options.dotfiles,
    })
    .filter((file) => {
      return !fs.statSync(path.join(basePath, file)).isDirectory();
    });

  if (!Array.isArray(files) || files.length === 0) {
    done(
      new Error('The pattern in the "src" property didn\'t match any files.'),
    );
    return;
  }

  let repoUrl;
  let userPromise;
  if (options.user) {
    userPromise = Promise.resolve(options.user);
  } else {
    userPromise = getUser();
  }
  return userPromise.then((user) =>
    getRepo(options)
      .then((repo) => {
        repoUrl = repo;
        const clone = getCacheDir(repo);
        log('Cloning %s into %s', repo, clone);
        return Git.clone(repo, clone, options.branch, options);
      })
      .then((git) => {
        return git.getRemoteUrl(options.remote).then((url) => {
          if (url !== repoUrl) {
            const message =
              'Remote url mismatch.  Got "' +
              url +
              '" ' +
              'but expected "' +
              repoUrl +
              '" in ' +
              git.cwd +
              '.  Try running the `gh-pages-clean` script first.';
            throw new Error(message);
          }
          return git;
        });
      })
      .then((git) => {
        // only required if someone mucks with the checkout between builds
        log('Cleaning');
        return git.clean();
      })
      .then((git) => {
        log('Fetching %s', options.remote);
        return git.fetch(options.remote);
      })
      .then((git) => {
        log('Checking out %s/%s ', options.remote, options.branch);
        return git.checkout(options.remote, options.branch);
      })
      .then((git) => {
        if (!options.history) {
          return git.deleteRef(options.branch);
        } else {
          return git;
        }
      })
      .then((git) => {
        if (options.add) {
          return git;
        }

        log('Removing files');
        const files = globby
          .sync(options.remove, {
            cwd: path.join(git.cwd, options.dest),
          })
          .map((file) => path.join(options.dest, file));
        if (files.length > 0) {
          return git.rm(files);
        } else {
          return git;
        }
      })
      .then((git) => {
        if (options.nojekyll) {
          log('Creating .nojekyll');
          fs.createFileSync(path.join(git.cwd, '.nojekyll'));
        }
        if (options.cname) {
          log('Creating CNAME for %s', options.cname);
          fs.writeFileSync(path.join(git.cwd, 'CNAME'), options.cname);
        }
        log('Copying files');
        return copy(files, basePath, path.join(git.cwd, options.dest)).then(
          function () {
            return git;
          },
        );
      })
      .then((git) => {
        return Promise.resolve(
          options.beforeAdd && options.beforeAdd(git),
        ).then(() => git);
      })
      .then((git) => {
        log('Adding all');
        return git.add('.');
      })
      .then((git) => {
        if (!user) {
          return git;
        }
        return git.exec('config', 'user.email', user.email).then(() => {
          if (!user.name) {
            return git;
          }
          return git.exec('config', 'user.name', user.name);
        });
      })
      .then((git) => {
        log('Committing');
        return git.commit(options.message);
      })
      .then((git) => {
        if (options.tag) {
          log('Tagging');
          return git.tag(options.tag).catch((error) => {
            // tagging failed probably because this tag alredy exists
            log(error);
            log('Tagging failed, continuing');
            return git;
          });
        } else {
          return git;
        }
      })
      .then((git) => {
        if (options.push) {
          log('Pushing');
          return git.push(options.remote, options.branch, !options.history);
        } else {
          return git;
        }
      })
      .then(
        () => done(),
        (error) => {
          if (options.silent) {
            error = new Error(
              'Unspecified error (run without silent option for detail)',
            );
          }
          done(error);
        },
      ),
  );
};
