import { escapeForShell, sh } from '../cli/shell';
import { parseVersion } from '../versions/parse_version';

const CURRENT_BRANCH_MARKER = /^\* /;

const RELEASE_CHANNEL_NAME_TO_BRANCH_MAP = {
  alpha: process.env.CI_TOOLS_ALPHA_BRANCH || 'alpha',
  beta: process.env.CI_TOOLS_BETA_BRANCH || 'beta',
  stable: process.env.CI_TOOLS_STABLE_BRANCH || 'master'
};

type GitCommitMessage = {
  subject: string;
  body: string | null;
};
type GitOperationResult = string;

export function getGitTagList(): string {
  return sh('git tag --sort=-creatordate').trim();
}

export function getGitTagDate(tag: string): string {
  return sh(`git log -1 --format=%aI ${tag}`).trim();
}

export function getGitCommitListSince(ref: string, since: string): string {
  return sh(`git log --format="%H" --since ${since} ${ref}`).trim();
}

export function getGitCommitSha1(ref: string = 'HEAD'): string {
  return sh(`git rev-parse ${ref}`).trim();
}

export function getGitBranch(): string {
  const gitRef = process.env.GIT_BRANCH || process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF;
  if (gitRef != null) {
    const gitRefIsTagReference = gitRef.startsWith('refs/tags/');
    if (gitRefIsTagReference) {
      return getBranchFromRefTag(gitRef);
    }
    return gitRef.replace(/^refs\/heads\//, '');
  }

  return getGitBranchFromGit();
}

export function getGitTagsFromCommit(ref: string): string[] {
  const tags = sh(`git tag -l --points-at ${ref}`).trim();

  return tags.split('\n');
}

export function getFullCommitMessageFromRef(tagOrCommit: string): GitCommitMessage | null {
  const output = sh(`git show -s --format=%B ${tagOrCommit}`);
  const lines = output.split('\n');
  const subject = lines[0];
  const body = lines
    .slice(1, lines.length - 2)
    .join('\n')
    .trim();

  return { subject, body };
}

export function getCurrentRepoNameWithOwner(): string {
  const url = sh('git remote get-url origin');
  const matchData = url.match(/github.com[:/](.+)$/m);

  if (matchData == null) {
    return null;
  }

  return matchData[1].replace(/\.git$/, '');
}

export function getGitHubAuthPart(): string {
  if (process.env.GH_USER != null && process.env.GH_TOKEN != null) {
    console.log('--- Using GH_USER & GH_TOKEN');
    return `${process.env.GH_USER}:${process.env.GH_TOKEN}@`;
  }

  return '';
}

export function getCurrentApiBaseUrlWithAuth(route: string): string {
  const gitHubRepo = getCurrentRepoNameWithOwner();
  if (gitHubRepo == null) {
    return null;
  }
  const authPart = getGitHubAuthPart();

  return `https://${authPart}api.github.com/repos/${gitHubRepo}${route}`;
}

export function gitAdd(...files: string[]): GitOperationResult {
  return sh(`git add ${files.join(' ')}`);
}

export function gitCommit(commitMessage: string): GitOperationResult {
  const escapedCommitMessage = escapeForShell(commitMessage);

  return sh(`git commit --allow-empty -m "${escapedCommitMessage}"`);
}

export function gitTag(newTag: string): GitOperationResult {
  return sh(`git tag ${newTag}`);
}

export function gitPush(remoteName: string, branchName: string): GitOperationResult {
  const cmd = `git push ${remoteName} ${branchName}`;
  console.log(`>> ${cmd}`);
  const output = sh(cmd).trim();
  console.log(output);

  return output;
}

export function gitPushTags(): GitOperationResult {
  const cmd = 'git push --tags';
  console.log(`>> ${cmd}`);
  const output = sh(cmd).trim();
  console.log(output);

  return output;
}

export function isDirty(...pathspec: string[]): boolean {
  return sh(`git status --porcelain --untracked-files=no ${pathspec.join(' ')}`).trim() !== '';
}

export function isExistingTag(name: string): boolean {
  const foundTag = getGitTagList()
    .split('\n')
    .find((line: string): boolean => line === name);

  return foundTag != null;
}

export function isGitHubRemote(): boolean {
  const url = sh('git remote get-url origin');
  const matchData = url.match(/github.com[:/](.+)$/m);

  return matchData != null;
}

export function mapReleaseChannelNameToBranch(releaseChannelName: string): string {
  return RELEASE_CHANNEL_NAME_TO_BRANCH_MAP[releaseChannelName];
}

export function getBranchFromRefTag(gitRef: string): string | null {
  const adjustedGitRef = gitRef.replace(/^refs\/tags\//, '').replace(/^v/, '');
  const versionFromGitRef = parseVersion(adjustedGitRef);

  if (versionFromGitRef == null) {
    return null;
  }

  return mapReleaseChannelNameToBranch(versionFromGitRef.releaseChannelName);
}

function getGitBranchFromGit(): string {
  const outputLines = sh('git branch')
    .trim()
    .split('\n');
  const branchLine = outputLines.find((name: string): boolean => !!name.match(CURRENT_BRANCH_MARKER));

  return branchLine.replace(CURRENT_BRANCH_MARKER, '');
}
