#!/usr/bin/python
# -*- coding: utf-8 -*-

import argparse
import itertools
import logging
import os
import os.path
import tempfile
import shutil
import subprocess
import sys
import re

TEMP_DIR = '/tmp/temp-ttl/ttl_7d'

parse_binary_to_string = lambda v: v.decode('utf-8').strip()

class Chalk:
    OK = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'
    ENDCOLOR = '\033[0m'

def print_message(color, *args):
    print(color + ' '.join(map(lambda a: str(a), args)) + Chalk.ENDCOLOR)

def print_info(*args):
    print_message(Chalk.BOLD, *args)

def print_error(*args):
    print_message(Chalk.FAIL + Chalk.BOLD, *args)

def print_warning(*args):
    print_message(Chalk.WARNING + Chalk.BOLD, *args)

def print_success(*args):
    print_message(Chalk.OK + Chalk.BOLD, *args)

class MaxSizeList(list):
    def __init__(self, maxlen):
        self._maxlen = maxlen

    def append(self, element):
        self.__delitem__(slice(0, len(self) == self._maxlen))
        super(MaxSizeList, self).append(element)

def die(message=''):
    sys.stderr.write("%s\n" % message)
    exit(1)

def init_logger(log_file):
    logger = logging.getLogger('dna-release')
    logger.setLevel(logging.INFO)

    formatter = logging.Formatter('%(asctime)s - %(levelname)s: %(message)s')

    sh = logging.StreamHandler()
    sh.setFormatter(formatter)
    logger.addHandler(sh)

    fh = logging.FileHandler(log_file)
    fh.setFormatter(formatter)
    logger.addHandler(fh)

def get_logger():
    return logging.getLogger('dna-release')

def my_tee(cmd, verbose=True, shell=False):
    logger = get_logger()

    if verbose:
        logger.info('going to exec: "%s"' % ' '.join(cmd))

    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=shell)
    lines = MaxSizeList(100)
    while True:
        line = proc.stdout.readline()
        if not line:
            break
        line = line.rstrip()
        lines.append(line)
        logger.info(line)

    proc.wait()
    if proc.returncode != 0:
        msg = 'cmd failed, stop (%s)' % ' '.join(cmd)
        error_msg = '\n'.join([msg, '=========================='])
        logger.error(error_msg)
        die(msg)

    return lines

def mktempdir(username):
    return tempfile.mkdtemp(prefix='dna-release-%s-' % username, dir=TEMP_DIR)

def mklogfile(tmpdir):
    log_file = os.path.join(tmpdir, 'release.log')
    open(log_file, 'a').close()
    my_tee([ 'chmod', '744', log_file ])
    my_tee([ 'chmod', '755', tmpdir ])
    return log_file

def dotenv(path):
    with open(path, 'r') as fh:
        vars_dict = dict(
            tuple(item.strip('"\n') for item in line.split('='))
            for line in fh.readlines() if not line.startswith('#')
        )
    os.environ.update(vars_dict)

def check_gpg_credentials():
    # check that gpg key exists

    key_output, key_error = map(parse_binary_to_string, subprocess.Popen(['gpg', '--list-keys'], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate())
    key_output = key_output.split('\n')
    key_output = filter(lambda str: not re.match(r'^-+$', str), key_output)

    if (len(key_output) < 3 or key_error):
        print_error('У Вас нет GPG ключа. Пожалуйста создайте GPG ключ, следуя инструкции https://docs.yandex-team.ru/direct-dev/guide/initial-setup/gpg-key')
        return False

    # check that gpg-agent configured

    conf_exists = os.path.exists(os.getenv('HOME') + '/.gnupg/gpg.conf')
    if not conf_exists:
        print_error('У Вас нет файла конфигурации для GPG ~/.gnupg/gpg.conf. Пожалуйста создайте файл ~/.gnupg/gpg.conf, следуя инструкции https://docs.yandex-team.ru/direct-dev/guide/initial-setup/gpg-key')
        return False

    # cat ~/.gnupg/gpg.conf | grep ^\s*use-agent\s*$
    conf_output = subprocess.Popen(['cat', os.getenv('HOME') + '/.gnupg/gpg.conf'], stdout=subprocess.PIPE)
    grep_output = subprocess.Popen(['grep', '^\s*use-agent\s*$'], stdin=conf_output.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    conf_output.stdout.close()
    conf_grep_output, conf_grep_error = map(parse_binary_to_string, grep_output.communicate())
    conf_grep_output = conf_grep_output.split('\n')
    if (not conf_grep_output or not os.getenv('GPG_AGENT_INFO') or not os.getenv('GPG_TTY') or conf_grep_error):
        print_error('У Вас не настроен gpg-agent. Пожалуйста настройте gpg-agent, следуя инструкции https://docs.yandex-team.ru/direct-dev/guide/initial-setup/gpg-key')
        return False

    # check that gpg-agent is running
    # ps fux | grep grep-agent | grep -v grep
    ps_output = subprocess.Popen(['ps', 'fux'], stdout=subprocess.PIPE)
    grep_output = subprocess.Popen(['grep', 'gpg-agent'], stdin=ps_output.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    grep_filter_output = subprocess.Popen(['grep', '-v', 'grep'], stdin=grep_output.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    ps_output.stdout.close()
    grep_output.stdout.close()
    ps_filtered_output, ps_filtered_error = map(parse_binary_to_string, grep_filter_output.communicate())
    ps_filtered_output = filter(lambda v: v, ps_filtered_output.split('\n'))

    if (not ps_filtered_output or ps_filtered_error):
        print_error("У Вашего пользователя нет запущеного gpg-agent'a. Пожалуйста настройте gpg-agent, следуя инструкции https://docs.yandex-team.ru/direct-dev/guide/initial-setup/gpg-key или выполните команду 'gpg-connect-agent /bye'")
        return False

    # try to use gpg key
    # echo test | gpg --sign > /dev/null
    sign_input = subprocess.Popen(['echo', 'test'], stdout=subprocess.PIPE)
    with open(os.devnull, 'w') as FNULL:
        try:
            subprocess.check_call(['gpg', '--sign'], stdin=sign_input.stdout, stdout=FNULL)
        except subprocess.CalledProcessError as e:
            print_error('Команда "' + ' '.join(e.cmd) + '" завершилась со статусом ' + str(e.returncode))
            return False

    return True

def check_ssh_credentials():
    # check that we could use key
    # svn info svn+ssh://arcadia.yandex.ru/arc > /dev/null
    with open(os.devnull, 'w') as FNULL:
        return_code = 0
        try:
            return_code = subprocess.check_call(['svn', 'info', 'svn+ssh://arcadia.yandex.ru/arc'], stdout=FNULL, stderr=subprocess.STDOUT)
        except subprocess.CalledProcessError as e:
            return_code = e.returncode
            print('Команда "' + ' '.join(e.cmd) + '" завершилась со статусом ' + str(e.returncode))
        finally:
            if return_code != 0:
                print_error('Невозможно установить ssh-соединение. Пожалуйста создайте ssh-ключ и настройте ssh-agent, следуя инструкциям https://wiki.yandex-team.ru/security/ssh/linux/#gen и https://wiki.yandex-team.ru/security/ssh/linux/#ispolzovanie')
                return False

    return True

def check_credentials(is_robot_ppc):
    """
    проверка различных авторизаций:
    - gpg-ключ
    - токен для авторизации в стартреке
    """

    print_info('Проверяем SSH и GPG ключи')

    is_gpg_credentials_correct = False
    is_ssh_credentials_correct = check_ssh_credentials()

    if is_robot_ppc:
        is_gpg_credentials_correct = True
    else:
        is_gpg_credentials_correct = check_gpg_credentials()

    if not is_gpg_credentials_correct or not is_ssh_credentials_correct:
        print_error('Не смогли проверить GPG и/или SSH ключи')
        die()

    print_success('SSH и GPG ключи проверены')

    print_info('Дополняем переменные окружения из файла /etc/direct-tokens/twilight/.env')

    dotenv('/etc/direct-tokens/twilight/.env')

    # TODO: Добавить проверку доменной авторизации в Startrek
    # https://st.yandex-team.ru/DIRECT-165210

    print_success('Все проверки пройдены')


def get_release_steps(for_robot=False):
    """
    Возвращает массив релизных команд, которые надо выполнить для сборки релиза.
    Если релиз собирается под роботом ppc, шаги могут отличаться от сборки из-под человеческого логина.
    С параметром for_robot возвращает команды, подходящие для роботной сборки
    """
    release_steps = [ 'tag-release', 'deps', 'build', 'deb' ]
    if for_robot:
        release_steps += [ 'deb-sign-ppc' ]
    release_steps += [ 'check-deb-signature' ]
    release_steps += [
        'dupload',
        'dmove-test'
    ] + [
        'update-ts',
        'update-ppcdev-all',
        'create-or-update-ticket',
        'update-8080',
        'update-8999',
        'show-release',
        'mark-as-complete'
    ]
    return release_steps

hotfix_steps = [
    'hotfix',
    'deps',
    'build',
    'deb',
    'check-deb-signature',
    'dupload',
    'dmove-test'
] + [
    'update-ts',
    'update-ppcdev-all',
    'create-or-update-ticket',
    'update-8080',
    'update-8999',
    'show-release',
    'mark-as-complete'
]

def clone_and_checkout_dna(tmpdir, branch):
    dna_dir = os.path.join(tmpdir, 'dna')
    dna_arc_dir = os.path.join(tmpdir, 'dna_arc_dir')
    dna_arc_store_dir = os.path.join(tmpdir, 'dna_arc_store_dir')
    dna_arc_dir_target = os.path.join(dna_arc_dir, 'adv', 'frontend', 'services', 'dna')

    subprocess.check_call(['mkdir', '-p', dna_arc_dir, dna_arc_store_dir])

    print('Mounting arcadia at {}'.format(dna_arc_dir))
    subprocess.check_call(['arc', '--update'])
    subprocess.check_call([
        'arc', 'mount', '--vfs-version', '2', '--allow-other',
        '--mount', dna_arc_dir, '--store', dna_arc_store_dir, '--object-store', '/var/www/arc-object-store',
    ], env={'ARC_ALLOW_WRITE_TO_ALL': '1'})

    subprocess.check_call(['ln', '-sf', dna_arc_dir_target, dna_dir])
    os.chdir(dna_dir)

    subprocess.check_call(['arc', 'fetch', branch])
    subprocess.check_call(['arc', 'checkout', branch])
    subprocess.check_call(['arc', 'pull', branch])


def unmount_dna(tmpdir):
    dna_arc_dir = os.path.join(tmpdir, 'dna_arc_dir')

    if os.path.exists(dna_arc_dir):
        with open(os.devnull, 'w') as FNULL:
            try:
                print('Check arcadia at {}'.format(dna_arc_dir))
                os.chdir(dna_arc_dir)
                subprocess.check_call(['arc', 'info'], stdout=FNULL, stderr=subprocess.STDOUT)
                os.chdir('../')
            except subprocess.CalledProcessError:
                print('Arcadia not mounted at {}'.format(dna_arc_dir))
                return

            print('Unmounting arcadia at {}'.format(dna_arc_dir))
            subprocess.check_call(['arc', 'unmount', dna_arc_dir], stdout=FNULL, stderr=subprocess.STDOUT)


def run():
    parser = argparse.ArgumentParser(add_help=True, description=u'Релиз приложения dna')
    parser.add_argument('--new', dest='new', action='store_true', help=u'собрать новый релиз из текущего мастера')
    parser.add_argument('--robot-ppc', dest='robot_ppc', action='store_true', help=u'релиз собирается из-под робота ppc; проставить все нужные переменные для неинтерактивного режима')
    parser.add_argument('--continue', dest='continue_version', action='store', help=u'продолжить сборку ранее начатого релиза')
    parser.add_argument('--hotfix', dest='hotfix_version', action='store', help=u'собрать хотфикс к указанному релизу')
    args = parser.parse_args()

    if args.robot_ppc:
        os.environ['SKIP_SIGN'] = '1'
        os.environ['DEBEMAIL'] = 'ppc@yandex-team.ru'
        os.environ['DEBFULLNAME'] = 'Robot ppc'
        os.environ['DNA_RELEASE_PPC'] = '1'
        os.environ['GIT_SSH_COMMAND'] = 'ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes'

    if len(sys.argv) == 1:
        parser.print_help()
        die()

    if args.new and args.continue_version:
        die('error: либо new, либо continue')

    if args.robot_ppc and not args.new and not args.continue_version:
        die('--robot-ppc requires --new or --continue, stop')

    [whoami] = my_tee(['whoami'])

    release_steps = get_release_steps(for_robot = args.robot_ppc)

    if args.new:
        if not args.robot_ppc:
            check_credentials(is_robot_ppc=False)

        tmpdir = mktempdir(whoami)

        log_file = mklogfile(tmpdir)

        logger = init_logger(log_file)

        branch = 'trunk'

        clone_and_checkout_dna(tmpdir, branch)
        try:
            my_tee(['scripts/release_commands.sh'] + release_steps)
        finally:
            unmount_dna(tmpdir)

    if args.hotfix_version:
        check_credentials(is_robot_ppc=False)

        tmpdir = mktempdir(whoami)

        log_file = mklogfile(tmpdir)

        logger = init_logger(log_file)

        branch = 'releases/direct/dna/%s' % args.hotfix_version

        clone_and_checkout_dna(tmpdir, branch)
        try:
            my_tee(['scripts/release_commands.sh'] + hotfix_steps)
        finally:
            unmount_dna(tmpdir)

    if args.continue_version:
        folders = my_tee(['ls %s | grep "dna-release-`whoami`"' % TEMP_DIR], shell=True)

        for folder in folders:
            tmpdir = os.path.join(TEMP_DIR, folder)
            release_number_file = os.path.join(tmpdir, 'release-number')
            step_file = os.path.join(tmpdir, 'step')
            if not os.path.isfile(release_number_file) or not os.path.isfile(step_file):
                continue
            rel_num = my_tee(['cat', os.path.join(tmpdir, 'release-number')])
            if rel_num and rel_num[0] == args.continue_version:
                break
        else:
            die('нечего продолжать')

        if not args.robot_ppc:
            check_credentials(is_robot_ppc=False)

        log_file = mklogfile(tmpdir)

        logger = init_logger(log_file)

        is_hotfix = os.path.isfile(os.path.join(tmpdir, 'hotfix'))
        steps = hotfix_steps if is_hotfix else release_steps

        [last_step] = my_tee(['cat', step_file])

        if last_step in steps:
            steps = itertools.dropwhile(lambda x: x != last_step, steps)
            steps = [step for step in steps][1:]

        branch = 'releases/direct/dna/%s' % args.continue_version

        clone_and_checkout_dna(tmpdir, branch)
        try:
            my_tee(['scripts/release_commands.sh'] + steps)
        finally:
            unmount_dna(tmpdir)


if __name__ == '__main__':
    run()
