#!/usr/bin/env python3
# Copyright 2020 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Git helper functions."""

import collections
import os
from pathlib import Path
import re
import subprocess
import tempfile
from typing import Iterable, Optional, Union


CommitContents = collections.namedtuple("CommitContents", ["url", "cl_number"])


def IsFullGitSHA(s: str) -> bool:
    """Returns if `s` looks like a git SHA."""
    return len(s) == 40 and all(x.isdigit() or "a" <= x <= "f" for x in s)


def CreateBranch(repo: Union[Path, str], branch: str) -> None:
    """Creates a branch in the given repo.

    Args:
        repo: The absolute path to the repo.
        branch: The name of the branch to create.

    Raises:
        ValueError: Failed to create a repo in that directory.
    """

    if not os.path.isdir(repo):
        raise ValueError("Invalid directory path provided: %s" % repo)

    subprocess.check_output(["git", "-C", repo, "reset", "HEAD", "--hard"])

    subprocess.check_output(["repo", "start", branch], cwd=repo)


def DeleteBranch(repo: Union[Path, str], branch: str) -> None:
    """Deletes a branch in the given repo.

    Args:
        repo: The absolute path of the repo.
        branch: The name of the branch to delete.

    Raises:
        ValueError: Failed to delete the repo in that directory.
    """

    if not os.path.isdir(repo):
        raise ValueError("Invalid directory path provided: %s" % repo)

    def run_checked(cmd):
        subprocess.run(["git", "-C", repo] + cmd, check=True)

    run_checked(["checkout", "-q", "m/main"])
    run_checked(["reset", "-q", "HEAD", "--hard"])
    run_checked(["branch", "-q", "-D", branch])


def CommitChanges(
    repo: Union[Path, str], commit_messages: Iterable[str]
) -> None:
    """Commit changes without uploading them.

    Args:
        repo: The absolute path to the repo where changes were made.
        commit_messages: Messages to concatenate to form the commit message.
    """
    if not os.path.isdir(repo):
        raise ValueError("Invalid path provided: %s" % repo)

    # Create a git commit.
    with tempfile.NamedTemporaryFile(mode="w+t", encoding="utf-8") as f:
        f.write("\n".join(commit_messages))
        f.flush()

        subprocess.check_output(["git", "commit", "-F", f.name], cwd=repo)


def UploadChanges(
    repo: Union[Path, str],
    branch: str,
    reviewers: Optional[Iterable[str]] = None,
    cc: Optional[Iterable[str]] = None,
    wip: bool = False,
) -> CommitContents:
    """Uploads the changes in the specifed branch of the given repo for review.

    Args:
        repo: The absolute path to the repo where changes were made.
        branch: The name of the branch to upload.
        of the changes made.
        reviewers: A list of reviewers to add to the CL.
        cc: A list of contributors to CC about the CL.
        wip: Whether to upload the change as a work-in-progress.

    Returns:
        A CommitContents value containing the commit URL and change list number.

    Raises:
        ValueError: Failed to create a commit or failed to upload the
        changes for review.
    """

    if not os.path.isdir(repo):
        raise ValueError("Invalid path provided: %s" % repo)

    # Upload the changes for review.
    git_args = [
        "repo",
        "upload",
        "--yes",
        f'--reviewers={",".join(reviewers)}' if reviewers else "--ne",
        "--no-verify",
        f"--br={branch}",
    ]

    if cc:
        git_args.append(f'--cc={",".join(cc)}')
    if wip:
        git_args.append("--wip")

    out = subprocess.check_output(
        git_args,
        stderr=subprocess.STDOUT,
        cwd=repo,
        encoding="utf-8",
    )

    print(out)
    # Matches both internal and external CLs.
    found_url = re.search(
        r"https?://[\w-]*-review.googlesource.com/c/.*/([0-9]+)",
        out.rstrip(),
    )
    if not found_url:
        raise ValueError("Failed to find change list URL.")

    return CommitContents(
        url=found_url.group(0), cl_number=int(found_url.group(1))
    )
