#!/usr/bin/env python3
import argparse
import logging
import os
import sys
from time import sleep
import questionary

import gitlab

logger = logging.getLogger(os.path.basename(__file__))

DRY_RUN = os.getenv('DRY_RUN', False)
GITLAB_TOKEN = os.getenv('GITLAB_TOKEN')
GITLAB_URL = os.getenv('GITLAB_URL', 'https://repo.lcloud.pl')


def init_logger(log_level=None, o_format=None):
    """
    Initializes the logger configuration.
    :param log_level: Override default log level
    :param o_format: Output format of the logs
    """
    if not log_level:
        log_level = 'INFO'

    # get the numerical value for the log level specified as string:
    # valid string are: DEBUG, INFO, WARNING, ERROR, CRITICAL
    numeric_level = getattr(logging, log_level.upper(), None)
    if not isinstance(numeric_level, int):
        logger.error(f'[!!] Invalid log level: {log_level}, setting log level to 10')
        numeric_level = 10

    if not o_format or o_format == 'stream':
        o_format = '[%(asctime)s.%(msecs)03d] %(levelname)s :: %(message)s'
    elif o_format == 'json':
        o_format = "{'time':'%(asctime)s.%(msecs)03d'," \
                   " 'name': '%(name)s'," \
                   " 'level': '%(levelname)s'," \
                   " 'message': '%(message)s'}"

    logging.basicConfig(level=numeric_level, format=o_format, datefmt='%H:%M:%S')
    # override github logging configuration
    logging.getLogger('requests').setLevel(logging.WARNING)
    logging.getLogger('requests').propagate = False
    logging.getLogger('urllib3').setLevel(logging.WARNING)
    logging.getLogger('urllib3').propagate = False


def gitlab_client(token):
    if token:
        try:
            gl = gitlab.Gitlab(url=GITLAB_URL, private_token=token)
            # Test if token is valid.
            projects = gl.projects.list(all=False)
            return gl
        except gitlab.exceptions.GitlabAuthenticationError:
            logger.error('Verify private token!')
            sys.exit(1)


def get_top_level_groups(client=None):
    try:
        all_groups = client.groups.list(get_all=True, top_level_only=True)
        return all_groups

    except gitlab.exceptions.GitlabHttpError as e:
        logger.error(f" {e}")
        if "403" in str(e):
            logger.error("You may not have enough permissions for this action.")
        sys.exit(1)
    except gitlab.exceptions.GitlabUpdateError as e:
        logger.error(f" {e}")
        if "403" in str(e):
            logger.error("You may not have enough permissions for this action.")
        sys.exit(1)
    except Exception as e:
        logger.error(e)
        sys.exit(1)


def change_group_all_visibility(client=None, group=None, target_visibility=None):
    group = client.groups.get(group.id)

    subgroups = group.subgroups.list(get_all=True)
    for subgroup in subgroups:
        subgroup = client.groups.get(subgroup.id)
        subsubgroups = subgroup.subgroups.list(get_all=True)
        if (len(subsubgroups) > 0):
            change_group_all_visibility(client, subgroup, target_visibility)

        subgroup_projects = subgroup.projects.list(get_all=True)
        for subgroup_project in subgroup_projects:
            subgroup_project = client.projects.get(subgroup_project.id)
            if not DRY_RUN:
                subgroup_project.visibility = target_visibility
                subgroup_project.save()
                logger.info(
                    f"Changed subgroup project {subgroup_project.name} visibility to {target_visibility}")
            else:
                logger.info(
                    f"[DRY] Changing subgroup project {subgroup_project.name} visibility to {target_visibility}")

        subgroup = client.groups.get(subgroup.id)
        if not DRY_RUN:
            subgroup.visibility = target_visibility
            subgroup.save()
            logger.info(f"Changed subgroup {subgroup.name} visibility to {target_visibility}")
        else:
            logger.info(f"[DRY] Changing subgroup {subgroup.name} visibility to {target_visibility}")

    group_projects = group.projects.list(get_all=True)
    for group_project in group_projects:
        group_project = client.projects.get(group_project.id)
        if not DRY_RUN:
            group_project.visibility = target_visibility
            group_project.save()
            logger.info(f"Changed group project {group_project.name} visibility to {target_visibility}")
        else:
            logger.info(f"[DRY] Changing group project {group_project.name} visibility to {target_visibility}")

    group = client.groups.get(group.id)
    if not DRY_RUN:
        group.visibility = target_visibility
        group.save()
        logger.info(f"Changed group {group.name} visibility to {target_visibility}")
    else:
        logger.info(f"[DRY] Changing group {group.name} visibility to {target_visibility}")


def parse_args(raw_args):
    """
    Parses script parameters.
    :param raw_args: Script parameters.
    :return: Parsed parameters.
    """
    p = argparse.ArgumentParser(
        prog="gl_visibility.py",
        formatter_class=lambda prog: (argparse.RawDescriptionHelpFormatter(prog, max_help_position=60, width=120)))

    p.add_argument('-v', '--target-visibility', required=True, choices=['private', 'internal', 'public'],
                   help='Target group visibility level')
    p.add_argument('--dry-run', required=False, action='store_true',
                   help='Only print changes')
    p.add_argument('-l', '--level', required=False,
                   help='Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL).')
    p.add_argument('-t', '--token', required=False,
                   help='Personal Access Tokens; or pass it with GITLAB_TOKEN variable.')

    if len(sys.argv) == 1:
        p.print_help()
        sys.exit(1)

    return p.parse_args(raw_args), p


def main():
    args, parser = parse_args(sys.argv[1:])

    init_logger(args.level)

    if args.dry_run:
        global DRY_RUN
        DRY_RUN = args.dry_run

    if not (args.token or GITLAB_TOKEN):
        logger.error('You need to provide GitLab Token using -t | --token parameter or GITLAB_TOKEN variable.')
        sys.exit(2)

    token = args.token or GITLAB_TOKEN

    gl_client = gitlab_client(token)

    logger.info("Getting the list of top-level groups. It may take a while to get all information...")

    groups = get_top_level_groups(gl_client)
    if (len(groups) > 0):
        for gid in [g.id for g in groups]:
            group = gl_client.groups.get(gid)
            if group.visibility != args.target_visibility:
                r = questionary.select(
                    f"Group {group.name} visibility is {group.visibility}, do you want to change its visibility to {args.target_visibility}?",
                    choices=["Yes", "No"]
                ).ask()
                if r == "Yes":
                    change_group_all_visibility(gl_client, group, args.target_visibility)
    else:
        logger.info("Nothing to do.")


if __name__ == '__main__':
    sys.exit(main())
