import * as chalk from 'chalk';
import fetch from 'cross-fetch';
import * as moment from 'moment';
import * as yargsParser from 'yargs-parser';

import { PullRequest, getMergedPullRequests } from '../../github/pull_requests';
import { getCurrentApiBaseUrlWithAuth, getCurrentRepoNameWithOwner, getGitCommitListSince } from '../../git/git';
import { getPrevVersionTag, getVersionTag } from '../../versions/git_helpers';
import { getPackageVersion } from '../../versions/package_version';

type CommitFromApi = any;

const COMMIT_API_URI = getCurrentApiBaseUrlWithAuth('/commits/:commit_sha');

const BADGE = '[create-release-announcement]\t';

const DEFAULT_MODE = 'node';

const CONSIDER_PULL_REQUESTS_WEEKS_BACK = 30;

/**
 * Creates an announcement based on data available in Git and GitHub:
 *
 * - Git: latest commits and tags
 * - GitHub: PRs
 */
export async function run(...args): Promise<boolean> {
  const argv = yargsParser(args, { alias: { help: ['h'] }, default: { mode: DEFAULT_MODE } });
  const mode = argv.mode;
  const text = await createReleaseAnnouncement(mode);

  console.log(text);
  return true;
}

export async function createReleaseAnnouncement(mode: string): Promise<string> {
  const startRef: string = await getPrevVersionTag(mode);
  const nextVersion = await getPackageVersion(mode);
  const nextVersionTag = getVersionTag(nextVersion);
  const repoUrl = `http://github.com/${getCurrentRepoNameWithOwner()}`;
  const releaseTagUrl = `${repoUrl}/releases/tag/${nextVersionTag}`;
  const getPrUrl = (number: number): string => `${repoUrl}/pull/${number}`;

  const releaseHeadline = `*${nextVersionTag}* was released!`;
  const releaseTagLinkFooter = `Please see the <${releaseTagUrl}|full CHANGELOG> for details.`;

  if (startRef == null) {
    const changelogText = `
${releaseHeadline}

${releaseTagLinkFooter}
  `
      .replace('`', "'")
      .trim();

    return changelogText;
  }

  const apiResponse = await getCommitFromApi(startRef);

  if (apiResponse.commit === undefined) {
    console.error(chalk.red(`${BADGE}API responded with: ${apiResponse.message}`));

    process.exit(3);
  }

  const startCommitDate = apiResponse.commit.committer.date;
  const startDate = moment(startCommitDate).subtract(CONSIDER_PULL_REQUESTS_WEEKS_BACK, 'weeks').toISOString();

  const endRef = 'HEAD';

  if (nextVersion == null) {
    console.error(chalk.red(`${BADGE}Could not determine nextVersion!`));
    process.exit(3);
  }

  printInfo(startRef, startDate, endRef, nextVersion, nextVersionTag);

  const mergedPullRequestsSince = await getMergedPullRequests(startDate);
  const mergedPullRequests = filterPullRequestsForBranch(mergedPullRequestsSince, '', startRef, startDate);

  console.log(`${BADGE}Found ${mergedPullRequests.length} merged pull requests since the last stable release`);

  const hasBreakingChanges = mergedPullRequests.some((pr) => pr.isBreakingChange);
  const breakingChangesHint = hasBreakingChanges
    ? 'Please note there are *BREAKING CHANGES*:'
    : 'The new version includes the following changes:';

  const mergedPullRequestsText = mergedPullRequests
    .map((pr: PullRequest): string => {
      let title = ensureSpaceAfterLeadingEmoji(pr.title);
      if (pr.isBreakingChange) {
        title = `*BREAKING CHANGE:* ${title}`;
      }

      return `- <${getPrUrl(pr.number)}|#${pr.number}> ${title}`;
    })
    .join('\n');

  const changelogText = `
${releaseHeadline}

${breakingChangesHint}

${mergedPullRequestsText}

${releaseTagLinkFooter}
  `
    .replace('`', "'")
    .trim();

  return changelogText;
}

async function getCommitFromApi(ref: string): Promise<CommitFromApi> {
  const url = COMMIT_API_URI.replace(':commit_sha', ref);
  const response = await fetch(url);

  return response.json();
}

function printInfo(
  startRef: string,
  startDate: string,
  endRef: string,
  nextVersion: string,
  nextVersionTag: string
): void {
  console.log(`${BADGE}startRef:`, startRef);
  console.log(`${BADGE}startDate:`, startDate);
  console.log(`${BADGE}endRef:`, endRef);
  console.log(`${BADGE}nextVersion:`, nextVersion);
  console.log(`${BADGE}nextVersionTag:`, nextVersionTag);
  console.log('');
}

function ensureSpaceAfterLeadingEmoji(text: string): string {
  const emojiWithoutTrailingSpaceRegex =
    /([\u{1f300}-\u{1f5ff}\u{1f900}-\u{1f9ff}\u{1f600}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}\u{1f1e6}-\u{1f1ff}\u{1f191}-\u{1f251}\u{1f004}\u{1f0cf}\u{1f170}-\u{1f171}\u{1f17e}-\u{1f17f}\u{1f18e}\u{3030}\u{2b50}\u{2b55}\u{2934}-\u{2935}\u{2b05}-\u{2b07}\u{2b1b}-\u{2b1c}\u{3297}\u{3299}\u{303d}\u{00a9}\u{00ae}\u{2122}\u{23f3}\u{24c2}\u{23e9}-\u{23ef}\u{25b6}\u{23f8}-\u{23fa}])(\S)/gu;

  return text.replace(
    emojiWithoutTrailingSpaceRegex,
    (substring: string, emojiMatch: string, characterAfterEmojiMatch: string): string => {
      return `${emojiMatch} ${characterAfterEmojiMatch}`;
    }
  );
}

function filterPullRequestsForBranch(
  prs: PullRequest[],
  branchName: string,
  startRef: string,
  since: string
): PullRequest[] {
  const allShaInCurrentBranch = getGitCommitListSince(branchName, since).split('\n');
  const allShaInStartRef = getGitCommitListSince(startRef, since).split('\n');
  const newShaInCurrentBranch = allShaInCurrentBranch.filter(
    (currentSha: string): boolean => allShaInStartRef.indexOf(currentSha) === -1
  );
  const filteredPrs = prs.filter(
    (pr: PullRequest): boolean =>
      newShaInCurrentBranch.indexOf(pr.headSha) !== -1 || newShaInCurrentBranch.indexOf(pr.mergeCommitSha) !== -1
  );

  return filteredPrs;
}
