# Copyright (c) ACSONE SA/NV 2018-2019
# Distributed under the MIT License (http://opensource.org/licenses/MIT).

import logging
import os
import shutil
import tempfile
from contextlib import contextmanager

import appdirs
import github3
from celery.exceptions import Retry

from . import config
from .process import CalledProcessError, call, check_call, check_output

_logger = logging.getLogger(__name__)


@contextmanager
def login():
    """GitHub login as decorator so a pool can be implemented later."""
    yield github3.login(token=config.GITHUB_TOKEN)


@contextmanager
def repository(org, repo):
    with login() as gh:
        yield gh.repository(org, repo)


def gh_call(func, *args, **kwargs):
    """Intercept GitHub call to wait when the API rate limit is reached."""
    try:
        return func(*args, **kwargs)
    except github3.exceptions.ForbiddenError as e:
        if not e.response.headers.get("X-RateLimit-Remaining", 1):
            raise Retry(
                message="Retry task after rate limit reset",
                exc=e,
                when=e.response.headers.get("X-RateLimit-Reset"),
            )
        raise


def gh_date(d):
    return d.isoformat()


def gh_datetime(utc_dt):
    return utc_dt.isoformat()[:19] + "+00:00"


class BranchNotFoundError(RuntimeError):
    pass


@contextmanager
def temporary_clone(org, repo, branch):
    """ context manager that clones a git branch into a tremporary directory,
    and yields the temp dir name, with cache """
    # init cache directory
    cache_dir = appdirs.user_cache_dir("oca-mqt")
    repo_cache_dir = os.path.join(cache_dir, "github.com", org.lower(), repo.lower())
    if not os.path.isdir(repo_cache_dir):
        os.makedirs(repo_cache_dir)
        check_call(["git", "init", "--bare"], cwd=repo_cache_dir)
    repo_url = f"https://github.com/{org}/{repo}"
    repo_url_with_token = f"https://{config.GITHUB_TOKEN}@github.com/{org}/{repo}"
    # fetch all branches into cache
    fetch_cmd = [
        "git",
        "fetch",
        "--quiet",
        "--force",
        "--prune",
        repo_url,
        "refs/heads/*:refs/heads/*",
    ]
    check_call(fetch_cmd, cwd=repo_cache_dir)
    # check if branch exist
    branches = check_output(["git", "branch"], cwd=repo_cache_dir)
    branches = [b.strip() for b in branches.split()]
    if branch not in branches:
        raise BranchNotFoundError()
    # clone to temp dir, with --reference to cache
    tempdir = tempfile.mkdtemp()
    try:
        clone_cmd = [
            "git",
            "clone",
            "--quiet",
            "--reference",
            repo_cache_dir,
            "--branch",
            branch,
            "--",
            repo_url,
            tempdir,
        ]
        check_call(clone_cmd, cwd=".")
        if config.GIT_NAME:
            check_call(["git", "config", "user.name", config.GIT_NAME], cwd=tempdir)
        if config.GIT_EMAIL:
            check_call(["git", "config", "user.email", config.GIT_EMAIL], cwd=tempdir)
        check_call(
            ["git", "remote", "set-url", "--push", "origin", repo_url_with_token],
            cwd=tempdir,
        )
        yield tempdir
    finally:
        shutil.rmtree(tempdir)


def git_push_if_needed(remote, branch, cwd=None):
    """
    Push current HEAD to remote branch.

    Return True if push succeeded, False if there was nothing to push.
    Raises a celery Retry exception in case of non-fast-forward push.
    """
    r = call(["git", "diff", "--quiet", "--exit-code", remote + "/" + branch], cwd=cwd)
    if r == 0:
        return False
    try:
        check_call(["git", "push", remote, branch], cwd=cwd, log_error=False)
    except CalledProcessError as e:
        if "non-fast-forward" in e.output:
            raise Retry(
                exc=e,
                message="Retrying because a non-fast-forward git push was attempted.",
            )
        else:
            _logger.error(
                f"command {e.cmd} failed with return code {e.returncode} "
                f"and output:\n{e.output}"
            )
            raise
    return True


def github_user_can_push(gh_repo, username):
    for collaborator in gh_call(gh_repo.collaborators):
        if username == collaborator.login and collaborator.permissions.get("push"):
            return True
    return False


def git_get_head_sha(cwd):
    """ Get the sha of the git HEAD in current directory """
    return check_output(["git", "rev-parse", "HEAD"], cwd=cwd).strip()


def git_get_current_branch(cwd):
    return check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd).strip()