# 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 os
import pathlib
import re
import shutil
import subprocess
from typing import Dict, Optional, Tuple

import synthtool
import synthtool.preconfig
from synthtool.log import logger
from synthtool import _tracked_paths, cache, metadata, shell

REPO_REGEX = (
    r"(((https:\/\/)|(git@))github.com(:|\/))?(?P<owner>[^\/]+)\/(?P<name>[^\/]+)"
)

USE_SSH = os.environ.get("AUTOSYNTH_USE_SSH", False)


def make_repo_clone_url(repo: str) -> str:
    """Returns a fully-qualified repo URL on GitHub from a string containing
    "owner/repo".

    This returns an https URL by default, but will return an ssh URL if
    AUTOSYNTH_USE_SSH is set.
    """
    if USE_SSH:
        return f"git@github.com:{repo}.git"
    else:
        return f"https://github.com/{repo}.git"


def clone(
    url: str, dest: pathlib.Path = None, committish: str = None, force: bool = False,
) -> pathlib.Path:
    """Clones a remote git repo.

    Will not actually clone the repo if it's already local via two ways:
      1. It's in the cache (the default destitination).
      2. It was supplied via the preconfig file.

    Arguments:
        url {str} -- Url pointing to remote git repo.

    Keyword Arguments:
        dest {pathlib.Path} -- Local folder where repo should be cloned. (default: {None})
        committish {str} -- The commit hash to check out. (default: {None})
        force {bool} -- Wipe out and reclone if it already exists it the cache. (default: {False})

    Returns:
        pathlib.Path -- Local directory where the repo was cloned.
    """
    preclone = get_preclone(url)

    if preclone:
        logger.debug(f"Using precloned repo {preclone}")
        dest = pathlib.Path(preclone)
    else:
        if dest is None:
            dest = cache.get_cache_dir()

        dest = dest / pathlib.Path(url).stem

        if force and dest.exists():
            shutil.rmtree(dest)

        if not dest.exists():
            cmd = ["git", "clone", "--single-branch", url, dest]
            shell.run(cmd, check=True)
        else:
            shell.run(["git", "checkout", "master"], cwd=str(dest), check=True)
            shell.run(["git", "pull"], cwd=str(dest), check=True)
        committish = committish or "master"

    if committish:
        shell.run(["git", "reset", "--hard", committish], cwd=str(dest))

    # track all git repositories
    _tracked_paths.add(dest)

    # add repo to metadata
    sha, message = get_latest_commit(dest)
    commit_metadata = extract_commit_message_metadata(message)

    metadata.add_git_source(
        name=dest.name,
        remote=url,
        sha=sha,
        internal_ref=commit_metadata.get("PiperOrigin-RevId"),
        local_path=str(dest),
    )

    return dest


def parse_repo_url(url: str) -> Dict[str, str]:
    """
    Parses a GitHub url and returns a dict with:
        owner - Owner of the repository
        name  - Name of the repository

    The following are matchable:
        googleapis/nodejs-vision(.git)?
        <EMAIL>:GoogleCloudPlatform/google-cloud-python.git
        https://github.com/GoogleCloudPlatform/google-cloud-python.git
    """
    match = re.search(REPO_REGEX, url)

    if not match:
        raise RuntimeError("repository url is not a properly formatted git string.")

    owner = match.group("owner")
    name = match.group("name")

    if name.endswith(".git"):
        name = name[:-4]

    return {"owner": owner, "name": name}


def get_latest_commit(repo: pathlib.Path = None) -> Tuple[str, str]:
    """Return the sha and commit message of the latest commit."""
    output = subprocess.check_output(
        ["git", "log", "-1", "--pretty=%H%n%B"], cwd=repo
    ).decode("utf-8")
    commit, message = output.split("\n", 1)
    return commit, message


def extract_commit_message_metadata(message: str) -> Dict[str, str]:
    """Extract extended metadata stored in the Git commit message.

    For example, a commit that looks like this::

        Do the thing!

        Piper-Changelog: 1234567

    Will return::

        {"Piper-Changelog": "1234567"}

    """
    metadata = {}
    for line in message.splitlines():
        if ":" not in line:
            continue

        key, value = line.split(":", 1)
        metadata[key] = value.strip()

    return metadata


def get_preclone(url: str) -> Optional[str]:
    """Finds a pre-cloned git repo in the preclone map."""
    preconfig = synthtool.preconfig.load()
    return preconfig.precloned_repos.get(url)
