import pygit2
import re
import subprocess

from git_deps.errors import InvalidCommitish
from git_deps.utils import abort


class GitUtils(object):
    @classmethod
    def abbreviate_sha1(cls, sha1):
        """Uniquely abbreviates the given SHA1."""

        # For now we invoke git-rev-parse(1), but hopefully eventually
        # we will be able to do this via pygit2.
        cmd = ['git', 'rev-parse', '--short', sha1]
        # cls.logger.debug(" ".join(cmd))
        out = subprocess.check_output(cmd, universal_newlines=True).strip()
        # cls.logger.debug(out)
        return out

    @classmethod
    def describe(cls, sha1):
        """Returns a human-readable representation of the given SHA1."""

        # For now we invoke git-describe(1), but eventually we will be
        # able to do this via pygit2, since libgit2 already provides
        # an API for this:
        #   https://github.com/libgit2/pygit2/pull/459#issuecomment-68866929
        #   https://github.com/libgit2/libgit2/pull/2592
        cmd = [
            'git', 'describe',
            '--all',       # look for tags and branches
            '--long',      # remotes/github/master-0-g2b6d591
            # '--contains',
            # '--abbrev',
            sha1
        ]
        # cls.logger.debug(" ".join(cmd))
        out = None
        try:
            out = subprocess.check_output(
                cmd, stderr=subprocess.STDOUT, universal_newlines=True)
        except subprocess.CalledProcessError as e:
            if e.output.find('No tags can describe') != -1:
                return ''
            raise

        out = out.strip()
        out = re.sub(r'^(heads|tags|remotes)/', '', out)
        # We already have the abbreviated SHA1 from abbreviate_sha1()
        out = re.sub(r'-g[0-9a-f]{7,}$', '', out)
        # cls.logger.debug(out)
        return out

    @classmethod
    def oneline(cls, commit):
        try:
            ret = commit.message.split('\n', 1)[0]
        except UnicodeDecodeError:
            ret = "Invalid utf-8 commit message"
        return ret

    @classmethod
    def commit_summary(cls, commit):
        return "%s %s" % (commit.hex[:8], cls.oneline(commit))

    @classmethod
    def refs_to(cls, sha1, repo):
        """Returns all refs pointing to the given SHA1."""
        matching = []
        for refname in repo.listall_references():
            symref = repo.lookup_reference(refname)
            dref = symref.resolve()
            oid = dref.target
            commit = repo.get(oid)
            if commit.hex == sha1:
                matching.append(symref.shorthand)

        return matching

    @classmethod
    def rev_list(cls, rev_range):
        cmd = ['git', 'rev-list', rev_range]
        return subprocess.check_output(cmd, universal_newlines=True) \
                         .strip().split('\n')

    @classmethod
    def ref_commit(cls, repo, rev):
        try:
            commit = repo.revparse_single(rev)
        except (KeyError, ValueError):
            raise InvalidCommitish(rev)

        if isinstance(commit, pygit2.Tag):
            commit = commit.get_object()

        return commit

    @classmethod
    def get_repo(cls, path='.'):
        try:
            repo_path = pygit2.discover_repository(path)
        except KeyError:
            abort("Couldn't find a repository in the current directory.")

        return pygit2.Repository(repo_path)
