#!/usr/bin/env python3
# pylint: disable=invalid-name
"""Backup a Git repositories namespace to my GitLab instace.

Which means creating a namespace in GitLab, creating a project for every
repository in the namespace, adding a remote to the repository and pushing
everything to that project.

In this context, a namespace is a directory under ~/Repositories.
"""

import argparse
import pathlib
import sys
import os
import os.path
import re
import gitlab  # pylint: disable=import-error
import sh  # pylint: disable=import-error
from sh.contrib import git  # pylint: disable=import-error


def namespace_path(namespace):
    """Returns the full path to the namespace.

    Raises an exception if the path doesn't exits or isn't a directory.
    """
    path = pathlib.Path(os.path.expanduser("~/Repositories/" + namespace))
    if not path.exists():
        raise argparse.ArgumentTypeError("Path {} does not exit.".format(path))
    if not path.is_dir():
        raise argparse.ArgumentTypeError(
            "Path {} is not a directory.".format(path)
        )
    return path


def name_to_path(name):
    """Converts a name to valid path in GitLab."""
    return re.sub("[^a-zA-Z0-9_-]", "-", name).lower()


def get_group(lab, name):
    """Returns a GitLab group object.

    If a group with that name already exists, return that. Otherwise create a
    new group and return that instead.
    """
    for i in lab.groups.list():
        if i.name == name:
            print(
                "Using existing group id: {}, name: {}, path: {}.".format(
                    i.id, i.name, i.path
                ),
                file=sys.stderr,
            )
            return i
    new_group = lab.groups.create(
        {
            "name": name,
            "path": name_to_path(name),
            "visivility": "internal",
        }
    )
    print(
        "Created new group id: {}, name: {}, path: {}.".format(
            new_group.id, new_group.name, new_group.path
        ),
        file=sys.stderr,
    )
    return group


def get_project(group, name):  # pylint: disable=redefined-outer-name
    """Returns a GitLab project.

    If a project with that name already exists, return that. Otherwise create a
    new project and return that instead.
    """
    for i in group.projects.list():
        if i.name == name:
            print(
                "Using existing project id: {}, name: {}, path: {}.".format(
                    i.id, i.name, i.path
                ),
                file=sys.stderr,
            )
            return i
    new_project = group.projects.gitlab.projects.create(
        {"name": name, "namespace_id": group.id}
    )
    print(
        "Created new project id: {}, name: {}, path: {}.".format(
            new_project.id, new_project.name, new_project.path
        ),
        file=sys.stderr,
    )
    return project


def is_git_repo(path):
    """Returns a boolean if the path is a Git repo."""
    return path.is_dir() and pathlib.Path(path, ".git").is_dir()


def list_repositories(namespace):
    """Returns a list of paths under the namespace that are Git
    repositories."""
    return [x for x in namespace.iterdir() if is_git_repo(x)]


TOKEN = os.environ["GITLAB_PRIVATE_TOKEN"]
URL = os.environ["GITLAB_BASE_URL"].removesuffix("api/v4")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("namespace", type=namespace_path)
    args = parser.parse_args()

    with gitlab.Gitlab(url=URL, private_token=TOKEN) as Lab:
        group = get_group(Lab, args.namespace.name)
        for repo in list_repositories(args.namespace):
            project = get_project(group, repo.name)
            with sh.pushd(repo):
                if "shore.co.il" in git.remote().splitlines():
                    print(
                        "Setting the remote URL in {}.".format(repo.name),
                        file=sys.stderr,
                    )
                    git.remote(
                        "set-url", "shore.co.il", project.ssh_url_to_repo
                    )
                else:
                    print(
                        "Adding remote in {}.".format(repo.name),
                        file=sys.stderr,
                    )
                    git.remote("add", "shore.co.il", project.ssh_url_to_repo)
                print(
                    "Pushing to {}.".format(project.ssh_url_to_repo),
                    file=sys.stderr,
                )
                git.push("--all", "shore.co.il")