#!/usr/bin/python3
import requests
from git import Repo
import subprocess
import argparse
import datetime
import traceback
import sys
import logging
import shutil
import os
import tempfile
import json
import logging.config

BRANCH_NAME = 'requirements_update'


logging.basicConfig()
logger = logging.getLogger(__name__)


class Request:
    def __init__(self, token, url):
        self.token = 'private_token=' + token
        self.url = url
        msg = 'initialisation of Request class with '
        msg += self.token + ' with ' + self.url
        logger.info(msg)

    def post_branch(self, project_id, branch, project_name):
        msg = 'create branch requirements_' + branch
        msg += ' for update project ' + project_name
        logger.info(msg)
        data = {'branch': BRANCH_NAME + '_' + branch, 'ref': branch}
        url_req = self.url + 'projects/' + project_id + '/repository/branches'
        url_req = url_req + '?' + self.token
        result = requests.post(url_req, data=data)
        if result.status_code == 201:
            return True
        else:
            warning = 'fait to create branch requirements_' + branch
            warning += ' for project ' + project_name + ', status_code :'
            logger.warning(
                warning + str(result.status_code))
            return False

    def get_branch(self, project_id, branch, project_name):
        msg = 'get branch requirements_' + branch + ' for project '
        msg += project_name
        logger.info(msg)
        url_req = self.url + 'projects/' + project_id + '/repository/branches'
        url_req = url_req + '?' + self.token
        result = requests.get(url_req)
        if result.status_code == 200:
            res = result.json()
            for r in res:
                if r['name'] == BRANCH_NAME + '_' + branch:
                    print('wahhhh')
                    return {'status_code': 200, 'branch': False}

            logger.info(
                'branch requirements_%s don\t exist,'
                + ' have to create it for project %s',
                branch,
                project_name
            )
            return {'status_code': 200, 'branch': True}
        else:
            print('tralala')
            return {'status_code': result.status_code}

    def get_project(self, project_name, project_group):
        logger.info(
            'get project ' + project_name + ' in groups ' + project_group)
        url_req = self.url + 'groups/' + project_group + '/projects/'
        url_req = url_req + '?search=' + project_name + '&' + self.token
        return requests.get(url_req)

    def post_merge_request(self, project_id, changes, branch, project_name):
        msg = 'post merge request for branch requirements_' + branch
        msg += ' for project ' + project_name
        logger.info(msg)
        data = {
            'source_branch': BRANCH_NAME + '_' + branch,
            'target_branch': branch,
            'title': 'Update requirements ' + branch,
            'description': changes
        }
        url_req = self.url + 'projects/' + project_id + '/merge_requests'
        url_req = url_req + '?' + self.token
        merge_request = requests.post(url_req, data=data)
        if merge_request.status_code == 201:
            return True
        else:
            warning = 'Can\'t create merge_request for requirements_' + branch
            warning += ' for project ' + project_name
            logger.warning(
                warning + ', status code : %s ',
                merge_request.status_code
            )
            return False


class Janitor:
    def __init__(self, request_cl):
        self.request_cl = request_cl

    def create_dict_projects(self, project_names, branch_names):
        logger.info('Create a list for ids of each projects')
        tab_id_project = []
        for i in range(len(project_names)):
            project_name = project_names[i].split('/')
            if len(project_name) == 1:
                name = project_name[0]
                have_group = False
            else:
                name = project_name[1]
                groups = project_name[0]
                have_group = True

            if have_group:
                result = self.request_cl.get_project(name, groups)
                if result.status_code == 200:
                    res = result.json()
                    for r in res:
                        if name == r['name']:
                            tab_id_project.append(
                                {
                                    'id': str(r['id']),
                                    'name': r['name'],
                                    'ssh_url': r['ssh_url_to_repo'],
                                    'branch': branch_names[i]
                                    }
                            )
            else:
                logger.warning('There are no groups for %s', name)
        return tab_id_project

    def clone_repo(self, path_tmp, project, ssh_key):
        logger.info('clone repository for %s', project['name'])
        kwargs = {}
        if ssh_key:
            kwargs['env'] = {
                'GIT_SSH_COMMAND': 'ssh -i ' + ssh_key
            }

        return Repo.clone_from(
            project['ssh_url'],
            os.path.join(path_tmp, project['name']),
            branch=BRANCH_NAME + '_' + project['branch'],
            **kwargs
        )

    def add_commit_push(self, cloned_repo, project_name, ssh_key):
        cloned_repo.index.add(['requirements.txt'])
        logger.info('%s Add success', project_name)
        now = datetime.datetime.now()
        cloned_repo.index.commit(
            "update requirements at " + now.strftime("%Y-%m-%d %H:%M")
        )
        logger.info('%s Commit success', project_name)
        if ssh_key:
            with cloned_repo.git.custom_environment(
                    GIT_SSH_COMMAND='ssh -i ' + ssh_key):
                cloned_repo.remotes.origin.push()
        else:
            cloned_repo.remotes.origin.push()
        logger.info('%s Push sucess', project_name)
        return True

    def create_requirements_from_file(self, string_file):
        requirements_from_file = []
        for line in string_file:
            if line[0] != '#':
                requirements_from_file.append(line.replace('==', ' ').split())
        return requirements_from_file

    def diff_requirements(self, before, after, project_name):
        logger.info(
            'list changes to add description in merge request for %s',
            project_name)
        requirements_before = self.create_requirements_from_file(before)

        requirements_after = self.create_requirements_from_file(after)

        changes = []
        for l1 in requirements_before:
            if l1:
                length = None
                change = l1[0] + ' '
                if len(l1) == 1:
                    change = change + ' -> '
                    length = True
                else:
                    change = change + l1[1] + ' -> '
                for l2 in requirements_after:
                    if l2[0] == l1[0] and len(l2) > 1:
                        if length:
                            change = change + l2[1] + '     \n'
                            changes.append(change)
                        elif l1[1] != l2[1]:
                            change = change + l2[1] + '     \n'
                            changes.append(change)
        changes = ''.join(changes)
        return changes

    def update(self, project, cloned_repo, path_tmp, ssh_key):
        logger.info(
            'update requirements.txt in branch requirements_%s for %s',
            project['branch'],
            project['name']
        )
        file_requirements = open(
            os.path.join(path_tmp, project['name'], 'requirements.txt'),
            'r'
        )
        before = file_requirements.readlines()
        file_requirements.close()
        subprocess.check_call(
            ['mngdeps.sh', '--update'],
            cwd=os.path.join(path_tmp, project['name'])
        )
        if cloned_repo.index.diff(None):
            file_requirements = open(
                os.path.join(path_tmp, project['name'], 'requirements.txt'),
                'r'
            )
            after = file_requirements.readlines()
            file_requirements.close()
            changes = self.diff_requirements(before, after, project['name'])
            logger.info("Changes :  \n %s", changes)
            self.add_commit_push(cloned_repo, project['name'], ssh_key)
            if self.request_cl.post_merge_request(
                    project['id'],
                    changes, project['branch'],
                    project['name']):
                logger.info(
                    'merge_request for merge branch '
                    'requirements_%s in project %s was '
                    'successfully created',
                    project['branch'],
                    project['name'])
                shutil.rmtree(path_tmp)
                return True
            else:
                logger.warning(
                    'merge_request for merge branch '
                    'requirements_%s in project %s wasn\'t '
                    'successfully created',
                    project['branch'],
                    project['name'])
                shutil.rmtree(path_tmp)
                return False
        else:
            logger.warning(
                'no change for %s in branch requirements_%s',
                project['name'],
                project['branch']
            )
            shutil.rmtree(path_tmp)
            return False

    def create_branch_download_repo(
            self, project, have_to_create_branch, ssh_key):
        logger.info(
            'update branch requirements_%s for project %s',
            project['branch'],
            project['name'])
        if have_to_create_branch:
            branch_exist = self.request_cl.post_branch(
                project['id'], project['branch'], project['name'])
        else:
            branch_exist = True

        if branch_exist:
            path_tmp = tempfile.mkdtemp()
            cloned_repo = self.clone_repo(path_tmp, project, ssh_key)
            if cloned_repo:
                if ssh_key:
                    ssh_cmd = 'ssh -i ' + ssh_key
                    cloned_repo.git.update_environment(GIT_SSH_COMMAND=ssh_cmd)

                return self.update(project, cloned_repo, path_tmp, ssh_key)
        logger.warning(
            'no branch ' + BRANCH_NAME + '_' + project['branch']
            + ' for ' + project['name'] + ' can\'t update this project')
        return False

    def get_have_to_create_branch(self, project_id, branch, project_name):
        return self.request_cl.get_branch(project_id, branch, project_name)

    def project_operations(self, project, ssh_key):
        respons_have_to_create_branch = self.get_have_to_create_branch(
            project['id'], project['branch'], project['branch'])
        if respons_have_to_create_branch['status_code'] == 200:
            have_to_create_branch = respons_have_to_create_branch['branch']

            return self.create_branch_download_repo(
                project, have_to_create_branch, ssh_key)

        else:
            logger.error('can\'t access to details of %s', project['name'])
            return False


def parser_scr():
    parser = argparse.ArgumentParser(description='Janitor Update Script.')
    parser.add_argument(
        'config',
        metavar='config',
        type=str,
        help='path to the config file')
    parser.add_argument(
        '-l',
        '--log_config',
        type=str,
        help='path to a config file for logger'
    )
    return parser


def main(argv):
    if argv[0] == 'the_janitor.py' or argv[0] == '/usr/bin/the_janitor.py':
        del argv[0]

    args = parser_scr().parse_args(argv)
    conf = args.config

    if args.log_config:
        logging.config.fileConfig(args.log_config)

    logger = logging.getLogger()

    logger.info('Started')
    logger.info('config file : %s', conf)

    with open(conf, 'r') as fichier:
        conf_data = json.load(fichier)

    request_cl = Request(conf_data['token'], conf_data['url'])
    ssh_key = conf_data.get('ssh_key')
    project_names = []
    branch_names = []
    for project, branch in conf_data['projects'].items():
        project_names.append(project)
        branch_names.append(branch)
    logger.info('projects we\'ll update  : %s', project_names)

    janitor = Janitor(request_cl)

    projects = janitor.create_dict_projects(project_names, branch_names)

    if projects == [] and project_names != []:
        logger.error('can\'t access to your projects')
        return -2

    logger.info('projects we\'ll update  : %s', projects)
    for project in projects:
        logger.info(
            'Begin updating of project : %s id : %s branch : %s',
            project['name'],
            project['id'],
            project['branch']
        )
        try:
            janitor.project_operations(project, ssh_key)
        except subprocess.CalledProcessError:
            logger.critical('can\'t update %s', project['name'])
            traceback.print_exc()
            return -1
    logger.info('Finished')
    return 0


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