#!/usr/bin/env node

import * as chalk from 'chalk';
import * as yargsParser from 'yargs-parser';

import * as AutoPublishIfApplicable from './commands/internal/auto-publish-if-applicable';
import * as CommitAndTagVersion from './commands/commit-and-tag-version';
import * as CopyAndCommitVersionForSubpackage from './commands/copy-and-commit-version-for-subpackage';
import * as CreateChangelog from './commands/internal/create-changelog';
import * as CreateReleaseAnnouncement from './commands/internal/create-release-announcement';
import * as FailOnPreVersionDependencies from './commands/fail-on-pre-version-dependencies';
import * as NpmInstallOnly from './commands/npm-install-only';
import * as PrepareVersion from './commands/prepare-version';
import * as PublishNpmPackage from './commands/publish-npm-package';
import * as SetupGitAndNpmConnections from './commands/internal/setup-git-and-npm-connections';
import * as UpdateGithubRelease from './commands/update-github-release';
import * as UpgradeDependenciesWithPreVersions from './commands/upgrade-dependencies-with-pre-versions';
import * as PublishReleasenotesOnSlack from './commands/publish-releasenotes-on-slack';
import * as GetVersion from './commands/get-version';
import * as SetVersion from './commands/set-version';
import * as IsNugetPackagePublished from './legacy/is-nuget-package-published';
import * as ReplaceDistTagsWithRealVersions from './commands/replace-dist-tags-with-real-versions';

import { getGitBranch } from './git/git';
import { PRIMARY_BRANCHES } from './versions/increment_version';
import { PACKAGE_MODE_DOTNET, PACKAGE_MODE_NODE, PACKAGE_MODE_PYTHON } from './contracts/modes';

const COMMAND_HANDLERS = {
  'commit-and-tag-version': CommitAndTagVersion,
  'copy-and-commit-version-for-subpackage': CopyAndCommitVersionForSubpackage,
  'fail-on-pre-version-dependencies': FailOnPreVersionDependencies,
  'prepare-version': PrepareVersion,
  'publish-npm-package': PublishNpmPackage,
  'npm-install-only': NpmInstallOnly,
  'update-github-release': UpdateGithubRelease,
  'upgrade-dependencies-with-pre-versions': UpgradeDependenciesWithPreVersions,
  'publish-releasenotes-on-slack': PublishReleasenotesOnSlack,
  'get-version': GetVersion,
  'set-version': SetVersion,
  'is-nuget-package-published': IsNugetPackagePublished,
  'replace-dist-tags-with-real-versions': ReplaceDistTagsWithRealVersions,
};

// Internal commands are only used to develop ci_tools and are not intended for public consumption.
const INTERNAL_COMMAND_HANDLERS = {
  'auto-publish-if-applicable': AutoPublishIfApplicable,
  'create-changelog': CreateChangelog,
  'create-release-announcement': CreateReleaseAnnouncement,
  'update-github-release': UpdateGithubRelease,
  'setup-git-and-npm-connections': SetupGitAndNpmConnections,
};

const DEFAULT_MODE = PACKAGE_MODE_NODE;

async function run(originalArgv: string[]): Promise<void> {
  const [, , ...args] = originalArgv;
  const argv = yargsParser(args, { alias: { help: ['h'] }, default: { mode: DEFAULT_MODE } });
  const mode = argv.mode;

  if (args.length === 0 || (args.length === 1 && argv.help === true)) {
    printHelp();
    process.exit(1);
  }

  if (mode !== PACKAGE_MODE_NODE && mode !== PACKAGE_MODE_DOTNET && mode !== PACKAGE_MODE_PYTHON) {
    console.error('Mode must be set to `dotnet`, `node` or `python`. \nDefault is `node`');
    process.exit(1);
  }

  const [commandName, ...restArgs] = args;

  const commandHandler = COMMAND_HANDLERS[commandName] || INTERNAL_COMMAND_HANDLERS[commandName];

  if (commandHandler == null) {
    console.error(`No handler found for command: ${commandName}`);
    process.exit(1);
  }

  enforceUniversalCommandLineSwitches(commandHandler, commandName, args);

  try {
    await commandHandler.run(...restArgs);
  } catch (error) {
    console.error(error);
    process.exit(1);
  }
}

function enforceUniversalCommandLineSwitches(commandHandler: any, commandName: string, args: string[]): void {
  const badge = `[${commandName}]\t`;
  const argv = yargsParser(args, { alias: { help: ['h'] } });

  if (argv.help) {
    printHelpForCommand(commandHandler, commandName);
    process.exit(0);
  }

  if (argv.onlyOnPrimaryBranches && argv.exceptOnPrimaryBranches) {
    console.error(chalk.red(`${badge}Both --only-on-primary-branches and --except-on-primary-branches given.`));
    console.error(chalk.red(`${badge}This can not work! Aborting.`));
    process.exit(1);
  } else if (argv.onlyOnPrimaryBranches) {
    ensureOnPrimaryBranchOrExit(badge);
  } else if (argv.exceptOnPrimaryBranches) {
    ensureNotOnPrimaryBranchOrExit(badge);
  }

  if (argv.onlyOnBranch) {
    ensureOnBranchOrExit(badge, argv.onlyOnBranch);
  }
}

function ensureOnBranchOrExit(badge: string, requestedBranchName: string): void {
  const branchName = getGitBranch();
  const currentlyOnBranch = requestedBranchName === branchName;

  if (!currentlyOnBranch) {
    console.log(chalk.yellow(`${badge}--only-on-branch given: ${requestedBranchName}`));
    console.log(chalk.yellow(`${badge}Current branch is '${branchName}'.`));
    console.log(chalk.yellow(`${badge}Nothing to do here. Exiting.`));

    process.exit(0);
  }
}

function ensureOnPrimaryBranchOrExit(badge: string): void {
  const branchName = getGitBranch();
  const currentlyOnPrimaryBranch = PRIMARY_BRANCHES.includes(branchName);

  if (!currentlyOnPrimaryBranch) {
    console.log(chalk.yellow(`${badge}--only-on-primary-branches given.`));
    console.log(
      chalk.yellow(`${badge}Current branch is '${branchName}' (primary branches are ${PRIMARY_BRANCHES.join(', ')}).`)
    );
    console.log(chalk.yellow(`${badge}Nothing to do here. Exiting.`));

    process.exit(0);
  }
}

function ensureNotOnPrimaryBranchOrExit(badge: string): void {
  const branchName = getGitBranch();
  const currentlyOnPrimaryBranch = PRIMARY_BRANCHES.includes(branchName);

  if (currentlyOnPrimaryBranch) {
    console.log(chalk.yellow(`${badge}--except-on-primary-branches given.`));
    console.log(
      chalk.yellow(`${badge}Current branch is '${branchName}' (primary branches are ${PRIMARY_BRANCHES.join(', ')}).`)
    );
    console.log(chalk.yellow(`${badge}Nothing to do here. Exiting.`));

    process.exit(0);
  }
}

function printHelp(): void {
  console.log('Usage: ci_tools <COMMAND>');
  console.log('');
  console.log('COMMAND can be any of:');
  Object.keys(COMMAND_HANDLERS).forEach((commandName: string): void => console.log(`  ${commandName}`));
}

function printHelpForCommand(commandHandler: any, commandName: string): void {
  if (commandHandler.printHelp != null) {
    commandHandler.printHelp();
    return;
  }

  console.log(`Usage: ci_tools ${commandName}`);
  console.log('');
  console.log('No further instructions available.');
}

run(process.argv);
