#!/usr/bin/env python3

import os
import shutil
import pytest
import tarfile
import zipfile

from .commandline import run
from .fs import keep_directory

BUILTIN_GIT_COMMANDS = {
    "add",
    "am",
    "annotate",
    "apply",
    "archive",
    "bisect--helper",
    "blame",
    "branch",
    "bundle",
    "cat-file",
    "check-attr",
    "check-ignore",
    "check-mailmap",
    "check-ref-format",
    "checkout",
    "checkout-index",
    "cherry",
    "cherry-pick",
    "clean",
    "clone",
    "column",
    "commit",
    "commit-tree",
    "config",
    "count-objects",
    "credential",
    "describe",
    "diff",
    "diff-files",
    "diff-index",
    "diff-tree",
    "fast-export",
    "fetch",
    "fetch-pack",
    "fmt-merge-msg",
    "for-each-ref",
    "format-patch",
    "fsck",
    "fsck-objects",
    "gc",
    "get-tar-commit-id",
    "grep",
    "hash-object",
    "help",
    "index-pack",
    "init",
    "init-db",
    "interpret-trailers",
    "log",
    "ls-files",
    "ls-remote",
    "ls-tree",
    "mailinfo",
    "mailsplit",
    "merge",
    "merge-base",
    "merge-file",
    "merge-index",
    "merge-ours",
    "merge-recursive",
    "merge-subtree",
    "merge-tree",
    "mktag",
    "mktree",
    "mv",
    "name-rev",
    "notes",
    "pack-objects",
    "pack-redundant",
    "pack-refs",
    "patch-id",
    "prune",
    "prune-packed",
    "pull",
    "push",
    "read-tree",
    "receive-pack",
    "reflog",
    "remote",
    "remote-ext",
    "remote-fd",
    "remote-ftp",
    "remote-ftps",
    "remote-https",
    "repack",
    "replace",
    "rerere",
    "reset",
    "rev-list",
    "rev-parse",
    "revert",
    "rm",
    "send-pack",
    "shortlog",
    "show",
    "show-branch",
    "show-ref",
    "stage",
    "status",
    "stripspace",
    "submodule--helper",
    "symbolic-ref",
    "tag",
    "unpack-file",
    "unpack-objects",
    "update-index",
    "update-ref",
    "update-server-info",
    "upload-archive",
    "var",
    "verify-commit",
    "verify-pack",
    "verify-tag",
    "whatchanged",
    "worktree",
    "write-tree",
}


# helpers


def delete_tmp_folder(folder):
    """Recursively delete a folder"""
    shutil.rmtree(folder)


def find_git_root(folder):
    """If `folder` contains one folder,
    return this folder. Otherwise `folder`"""
    children = os.listdir(folder)
    if len(children) > 1:
        return folder
    else:
        return os.path.join(folder, children[0])


# implementation


class GitCommand:
    def __init__(self, repo, command):
        self.repo = repo
        self.command = command

    def __call__(self, *args):
        with keep_directory():
            os.chdir(self.repo.root)
            return run("git", self.command, *args)

    def __repr__(self):
        return "command 'git {}'".format(self.command)


class GitRepository:
    """Represents a git repository we are working with"""

    def __init__(self, root, delete_fn=None):
        self.root = root
        self.delete_fn = delete_fn

    def __getattr__(self, name):
        if name not in BUILTIN_GIT_COMMANDS:
            raise ValueError("git command '{}' is unknown".format(name))
        return GitCommand(self, name)

    def __enter__(self):
        return self

    def __exit__(self, *exc_details):
        if self.delete_fn:
            self.delete_fn()

    @property
    def branch(self):
        """Return current git branch"""
        ex, out, err = GitCommand(self, "branch")()
        assert ex == 0
        for line in out.splitlines():
            if line.startswith("*"):
                return line[1:].strip()

    @property
    def version(self):
        """Return git version in use"""
        ex, out, err = GitCommand(self, "--version")()
        assert ex == 0
        return " ".join(out.strip().split()[2:])

    def __repr__(self):
        return "GitRepository at '{}'".format(self.root)


class GitLoader:
    """Provides methods to download a git repository"""

    default = "default-git-repository.tar.gz"

    def __init__(self, tmpdir):
        self.localpath = tmpdir.mkdir("git-pytest")

    @staticmethod
    def extract_archive(src, dest):
        """Extract files and folder in archive `src` to path `dest`"""
        suffix = None
        if src.endswith(".tgz") or src.endswith(".tar.gz"):
            suffix = ":gz"
        elif src.endswith(".tbz2") or src.endswith(".tar.bz2"):
            suffix = ":bz2"
        elif src.endswith(".tar.lzma"):
            suffix = ":xz"
        elif src.endswith(".tar"):
            suffix = ""

        if suffix is not None:
            ar = tarfile.open(src, "r" + suffix)
            ar.extractall(path=dest)
            return dest
        elif src.endswith(".zip"):
            with zipfile.ZipFile(src, "r") as fd:
                fd.extractall(path=dest)
            return dest
        else:
            raise ValueError("Archive of unknown file type: {}".format(src))

    def from_url(self, url):
        """Clone git repository from URL"""
        ex, out, err = run("git", "clone", url, self.localpath)
        assert ex == 0
        return GitRepository(
            find_git_root(self.localpath), delete_fn=self.cleanup
        )

    def from_archive(self, archive_path):
        """Extract git repository from given archive"""
        self.localpath = self.extract_archive(archive_path, self.localpath)
        return GitRepository(
            find_git_root(self.localpath), delete_fn=self.cleanup
        )

    def in_folder(self, path):
        """Assume `path` already contains a git repository"""
        assert os.path.exists(
            os.path.join(path, ".git")
        ), ".git folder must exist in git repository"
        return GitRepository(path)

    def cleanup(self):
        delete_tmp_folder(self.localpath)

    def __repr__(self):
        return "GitLoader for '{}'".format(self.localpath)


@pytest.fixture()
def gitrepo(tmpdir):
    return GitLoader(tmpdir)
