/**
 * Copyright (c) 2017-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

const path = require('path');
const shell = require('shelljs');
const fs = require('fs-extra');
const build = require('./build');
const loadConfig = require('../load/config');

module.exports = async function deploy(siteDir) {
  console.log('Deploy command invoked ...');
  if (!shell.which('git')) {
    throw new Error('Sorry, this script requires git');
  }

  const gitUser = process.env.GIT_USER;
  if (!gitUser) {
    throw new Error(`Please set the GIT_USER`);
  }

  // The branch that contains the latest docs changes that will be deployed
  const currentBranch =
    process.env.CURRENT_BRANCH ||
    shell.exec('git rev-parse --abbrev-ref HEAD').stdout.trim();

  const siteConfig = loadConfig.loadConfig(siteDir);
  const organizationName =
    process.env.ORGANIZATION_NAME ||
    process.env.CIRCLE_PROJECT_USERNAME ||
    siteConfig.organizationName;
  if (!organizationName) {
    throw new Error(
      `Missing project organization name. Did you forget to define 'organizationName' in ${
        loadConfig.configFileName
      }? You may also export it via the organizationName environment variable.`,
    );
  }
  const projectName =
    process.env.PROJECT_NAME ||
    process.env.CIRCLE_PROJECT_REPONAME ||
    siteConfig.projectName;
  if (!projectName) {
    throw new Error(
      `Missing project name. Did you forget to define 'projectName' in ${
        loadConfig.configFileName
      }? You may also export it via the projectName environment variable.`,
    );
  }

  // We never deploy on pull request
  const isPullRequest =
    process.env.CI_PULL_REQUEST || process.env.CIRCLE_PULL_REQUEST;
  if (isPullRequest) {
    shell.echo('Skipping deploy on a pull request');
    shell.exit(0);
  }

  // github.io indicates organization repos that deploy via master. All others use gh-pages.
  const deploymentBranch =
    projectName.indexOf('.github.io') !== -1 ? 'master' : 'gh-pages';
  const githubHost =
    process.env.GITHUB_HOST || siteConfig.githubHost || 'github.com';

  const useSSH = process.env.USE_SSH;
  const remoteBranch = useSSH
    ? `git@${githubHost}:${organizationName}/${projectName}.git`
    : `https://${gitUser}@${githubHost}/${organizationName}/${projectName}.git`;

  // Check if this is a cross-repo publish
  const currentRepoUrl = shell
    .exec('git config --get remote.origin.url')
    .stdout.trim();
  const crossRepoPublish = !currentRepoUrl.endsWith(
    `${organizationName}/${projectName}.git`,
  );

  // We don't allow deploying to the same branch unless it's a cross publish
  if (currentBranch === deploymentBranch && !crossRepoPublish) {
    throw new Error(
      `Cannot deploy from a ${deploymentBranch} branch. Only to it`,
    );
  }

  // Save the commit hash that triggers publish-gh-pages before checking out to deployment branch
  const currentCommit = shell.exec('git rev-parse HEAD').stdout.trim();

  // build static html files, then push to deploymentBranch branch of specified repo
  build(siteDir)
    .then(() => {
      shell.cd(siteDir);
      shell.cd('build');

      if (
        shell.exec(
          `git clone ${remoteBranch} ${projectName}-${deploymentBranch}`,
        ).code !== 0
      ) {
        throw new Error('Error: git clone failed');
      }

      shell.cd(`${projectName}-${deploymentBranch}`);

      // If the default branch is the one we're deploying to, then we'll fail to create it.
      // This is the case of a cross-repo publish, where we clone a github.io repo with a default master branch.
      const defaultBranch = shell
        .exec('git rev-parse --abbrev-ref HEAD')
        .stdout.trim();
      if (defaultBranch !== deploymentBranch) {
        if (shell.exec(`git checkout origin/${deploymentBranch}`).code !== 0) {
          if (
            shell.exec(`git checkout --orphan ${deploymentBranch}`).code !== 0
          ) {
            throw new Error(`Error: Git checkout ${deploymentBranch} failed`);
          }
        } else if (
          shell.exec(`git checkout -b ${deploymentBranch}`).code +
            shell.exec(
              `git branch --set-upstream-to=origin/${deploymentBranch}`,
            ).code !==
          0
        ) {
          throw new Error(`Error: Git checkout ${deploymentBranch} failed`);
        }
      }

      shell.exec('git rm -rf .');

      shell.cd('../..');

      const fromPath = path.join('build');
      const toPath = path.join('build', `${projectName}-${deploymentBranch}}`);
      // In github.io case, project is deployed to root. Need to not recursively
      // copy the deployment-branch to be.
      const excludePath = `${projectName}-${deploymentBranch}`;

      // cannot use shell.cp because it doesn't support copying dotfiles and we
      // need to copy directories like .circleci, for example
      // https://github.com/shelljs/shelljs/issues/79
      fs.copy(
        fromPath,
        toPath,
        src => {
          if (src.indexOf('.DS_Store') !== -1) {
            return false;
          }
          if (src.indexOf(excludePath) !== -1) {
            return false;
          }
          return true;
        },
        error => {
          if (error) {
            throw new Error(
              `Error: Copying build assets failed with error '${error}'`,
            );
          }

          shell.cd(path.join('build', `${projectName}-${deploymentBranch}`));
          shell.exec('git add --all');

          const commitMessage =
            process.env.CUSTOM_COMMIT_MESSAGE || 'Deploy website';
          const commitResults = shell.exec(
            `git commit -m "${commitMessage}" -m "Deploy website version based on ${currentCommit}"`,
          );
          if (shell.exec(`git push origin ${deploymentBranch}`).code !== 0) {
            throw new Error('Error: Git push failed');
          } else if (commitResults.code === 0) {
            // The commit might return a non-zero value when site is up to date.
            const websiteURL =
              githubHost === 'github.com'
                ? `https://${organizationName}.github.io/${projectName}` // gh-pages hosted repo
                : `https://${githubHost}/pages/${organizationName}/${projectName}`; // GitHub enterprise hosting.
            shell.echo(`Website is live at: ${websiteURL}`);
            shell.exit(0);
          }
        },
      );
    })
    .catch(buildError => {
      console.error(buildError);
      process.exit(1);
    });
};