/**
 * @license
 * Copyright 2018 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import chalk from 'chalk';
import {ExecOptions} from 'child_process';
import * as path from 'path';
import terminalLink from 'terminal-link';
import * as files from '../files';
import {PWD} from '../index';
import {Template} from '../main';
import {Answers} from '../questions';
import * as util from '../util';
import * as appsscript from './appsscript';
import * as validation from './validation';
const clear = require('clear');

const green = chalk.bold.rgb(15, 157, 88);
const blue = chalk.bold.rgb(66, 133, 244);
const yellow = chalk.bold.rgb(244, 160, 0);
const red = chalk.bold.rgb(219, 68, 55);

const getTemplates = (answers: Answers): Template[] => {
  return [
    {match: /{{MANIFEST_NAME}}/, replace: answers.projectName},
    {match: /{{MANIFEST_LOGO_URL}}/, replace: answers.manifestLogoUrl!},
    {match: /{{MANIFEST_COMPANY}}/, replace: answers.manifestCompany!},
    {match: /{{MANIFEST_COMPANY_URL}}/, replace: answers.manifestCompanyUrl!},
    {match: /{{MANIFEST_ADDON_URL}}/, replace: answers.manifestAddonUrl!},
    {match: /{{MANIFEST_SUPPORT_URL}}/, replace: answers.manifestSupportUrl!},
    {match: /{{MANIFEST_DESCRIPTION}}/, replace: answers.manifestDescription!},
    {
      match: /{{MANIFEST_SOURCES}}/,
      replace: `[${answers
        .manifestSources!.split(',')
        .map((a) => `"${a}"`)
        .join(',')}]`,
    },
  ];
};

const ensureAuthenticated = async (execOptions: ExecOptions): Promise<void> => {
  const authenticated = util.spinnify(
    'Ensuring clasp is authenticated...',
    async () => {
      return validation.claspAuthenticated();
    }
  );
  if (!authenticated) {
    const infoText = yellow(
      'Clasp must be globally authenticated for dscc-gen.'
    );
    const claspLogin = green('npx @google/clasp login');
    console.log(`${infoText}\nrunning ${claspLogin} ...\n`);
    await util.exec('npx @google/clasp login', execOptions, true);
  }
};

const installDependencies = async (projectPath: string, answers: Answers) => {
  return util.spinnify('Installing project dependencies...', async () =>
    util.npmInstall(projectPath, answers)
  );
};

const createAppsScriptProject = async (
  projectPath: string,
  projectName: string,
  execOptions: ExecOptions
): Promise<void> => {
  return util.spinnify('Creating Apps Script project...', async () => {
    await appsscript.create(projectPath, projectName);
    // Since clasp creating a new project overwrites the manifest, we want to
    // copy the template manifest over the one generated by clasp.
    await util.exec('mv temp/appsscript.json src/appsscript.json', execOptions);
    await appsscript.push(projectPath);
  });
};

const cloneAppsScriptProject = async (
  projectPath: string,
  scriptId: string
): Promise<void> => {
  // We don't need the template source files since we want the Apps Scripts project's
  return util.spinnify('Cloning existing project...', async () => {
    await util.exec('rm -rf src', {cwd: projectPath});
    await appsscript.clone(projectPath, scriptId, 'src');
  });
};

const manageDeployments = async (projectPath: string, answers: Answers) => {
  let productionDeploymentId: string | undefined;
  if (answers.scriptId !== undefined) {
    // See if there is already a 'Production' deployment.
    productionDeploymentId = await util.spinnify(
      'Checking for a production deployment',
      async () => {
        return appsscript.getDeploymentIdByName(projectPath, 'Production');
      }
    );
  }
  if (productionDeploymentId === undefined) {
    productionDeploymentId = await util.spinnify(
      'Creating a production deployment',
      async () => {
        return await appsscript.deploy(projectPath, 'Production');
      }
    );
  }
  const latestDeploymentId = await util.spinnify(
    'Getting the latest deploymentId',
    async () => {
      return await appsscript.getDeploymentIdByName(projectPath, '@HEAD');
    }
  );
  if (latestDeploymentId === undefined) {
    throw new Error(
      `Wasn't able to get the latest deploymentId. This is probably a bug with dscc-gen.`
    );
  }
  return util.spinnify('Updating templates with your values...', async () => {
    return files.fixTemplates(projectPath, [
      {match: /{{PRODUCTION_DEPLOYMENT_ID}}/, replace: productionDeploymentId!},
      {match: /{{LATEST_DEPLOYMENT_ID}}/, replace: latestDeploymentId!},
    ]);
  });
};

export const createFromTemplate = async (answers: Answers): Promise<number> => {
  clear();
  const {projectName, basePath} = answers;
  const templatePath = path.join(basePath, 'templates', answers.projectChoice);
  const projectPath = path.join(PWD, projectName);
  await files.createAndCopyFiles(projectPath, templatePath, projectName);
  await util.spinnify('Updating templates with your values...', async () => {
    await files.fixTemplates(projectPath, getTemplates(answers));
  });

  const execOptions: ExecOptions = {cwd: projectPath};

  await installDependencies(projectPath, answers);
  await ensureAuthenticated(execOptions);

  if (answers.scriptId !== undefined) {
    await cloneAppsScriptProject(projectPath, answers.scriptId);
  } else {
    await createAppsScriptProject(projectPath, projectName, execOptions);
  }
  await manageDeployments(projectPath, answers);

  // Remove temp directory.
  await util.exec('rm -rf temp', execOptions);

  const connectorOverview = blue(
    terminalLink(
      'connector overview',
      'https://developers.google.com/datastudio/connector/'
    )
  );
  const styledProjectName = green(projectName);
  const cdDirection = yellow(`cd ${projectName}`);
  const runCmd = answers.yarn ? 'yarn' : 'npm run';
  const open = red(`${runCmd} open`);
  const push = blue(`${runCmd} push`);
  const watch = green(`${runCmd} watch`);
  const prettier = yellow(`${runCmd} prettier`);
  const tryLatest = red(`${runCmd} try_latest`);
  const tryProduction = blue(`${runCmd} try_production`);
  const updateProduction = green(`${runCmd} update_production`);

  console.log(
    `\
Created a new community connector: ${styledProjectName}\n\
\n\
If this is your first connector, see ${connectorOverview}\n\
\n\
${cdDirection} to start working on your connector\n\
\n\
Scripts are provided to simplify development:\n\
\n\
${open} - open your project in Apps Script.\n\
${push} - push your local changes to Apps Script.\n\
${watch} - watches for local changes & pushes them to Apps Script.\n\
${prettier} - formats your code using community standards.\n\
${tryLatest} - opens the deployment with your latest code.\n\
${tryProduction} - opens your production deployment.\n\
${updateProduction} - updates your production deployment to use the latest code.\n\
`
  );
  return 0;
};
