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

import argparse
import copy
import errno
import fcntl
import getpass
import jinja2
import json
import os
import pwd
import re
import socket
import subprocess
import sys
import tempfile
import logging

sys.path.insert(0, '/opt/direct-py/startrek-python-client-sni-fix')
from startrek_client import Startrek

logging.basicConfig(level=logging.INFO, format="[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s")

logger = logging.getLogger(__name__)

def main():
    services = {
        'infoblock': {
            'prepare': prepare_infoblock,
            'enable': enable_infoblock,
            'disable': disable_infoblock,
        },
        'dna': {
            'prepare': prepare_dna,
            'enable': enable_dna,
            'disable': disable_dna,
            'update': update_dna,
        },
        'js-templater': {
            'prepare': prepare_js_templater,
            'enable': enable_js_templater,
            'disable': disable_js_templater,
        },
        'canvas': {
            'prepare': prepare_canvas,
            'enable': enable_canvas,
            'disable': disable_canvas,
        },
    }
    def list_services():
        for s in sorted(services.keys()):
            print s
    actions = [
        {
            'name': 'normalize-settings',
            'code': normalize_settings,
            'desc': u'нормализовать настройки: добавить недостающие и удалить устаревшие ключи',
        },
        {
            'name': 'get-settings',
            'code': get_settings,
            'desc': u'вывести настройки в json',
        },
        {
            'name': 'set',
            'code': lambda(args_dict): set_var(*(args_dict['name=value'].split('=', 1))),
            'desc': u'выставить значение переменной (set var=value)',
            'args': ['name=value'],
        },
        {
            'name': 'set-from-json',
            'code': lambda(args_dict): set_var_from_json(*(args_dict['name=json'].split('=', 1))),
            'desc': u'выставить значение переменной-объекта (set-from-json var=\'{"foo": ["bar", "baz"]}\')',
            'args': ['name=json'],
        },
        {
            'name': 'unset',
            'code': lambda(args_dict): unset_var(args_dict['name']),
            'desc': u'сбросить значение переменной (unset db_config_file)',
            'args': ['name'],
        },
        {
            'name': 'use-db',
            'code': lambda(args_dict): set_db_config_file(args_dict['db_name']),
            'desc': u'переключиться на базу данных (use-db dev7)',
            'args': ['db_name'],
        },
        {
            'name': 'use-mod-beta',
            'code': lambda(args_dict): set_mod_beta(int(args_dict['mod_beta_num'])),
            'desc': u'переключиться на бету Модерации (use-mod-beta 9781)',
            'args': ['mod_beta_num'],
        },
        ### до следующего "###" не очень хорошее решение.
        # список сервисов есть здесь и в use_java_from_ssh_tunnel
        # кроме того, однотипные команды скопипащены с поправками
        # либо параметризовать команду (нужно хорошее название), либо генерировать в цикле (в python замыкания в цикле работают не так, как ожидается, так что с наскоку не получилось)
        {
            'name': 'use-logviewer-from-ssh-tunnel',
            'code': lambda(args_dict): use_java_from_ssh_tunnel('logviewer', **{k: args_dict[k] for k in args_dict if k != 'service_name'}),
            'desc': u'использовать java-сервис logviewer, запущенный через ssh-тоннель (например, с рабочего ноутбука)',
            'args': ['--local-port', '--host'],
        },
        {
            'name': 'use-intapi-from-ssh-tunnel',
            'code': lambda(args_dict): use_java_from_ssh_tunnel('intapi', **{k: args_dict[k] for k in args_dict if k != 'service_name'}),
            'desc': u'использовать java-сервис intapi, запущенный через ssh-тоннель (например, с рабочего ноутбука)',
            'args': ['--local-port', '--host'],
        },
        {
            'name': 'use-api5-from-ssh-tunnel',
            'code': lambda(args_dict): use_java_from_ssh_tunnel('api5', **{k: args_dict[k] for k in args_dict if k != 'service_name'}),
            'desc': u'использовать java-сервис api5, запущенный через ssh-тоннель (например, с рабочего ноутбука)',
            'args': ['--local-port', '--host'],
        },
        {
            'name': 'use-web-from-ssh-tunnel',
            'code': lambda(args_dict): use_java_from_ssh_tunnel('web', **{k: args_dict[k] for k in args_dict if k != 'service_name'}),
            'desc': u'использовать java-сервис web, запущенный через ssh-тоннель (например, с рабочего ноутбука)',
            'args': ['--local-port', '--host'],
        },
        {
            'name': 'use-canvas-from-ssh-tunnel',
            'code': lambda(args_dict): use_java_from_ssh_tunnel('canvas', **{k: args_dict[k] for k in args_dict if k != 'service_name'}),
            'desc': u'использовать java-сервис canvas, запущенный через ssh-тоннель (например, с рабочего ноутбука)',
            'args': ['--local-port', '--host'],
        },
        ###
        {
            'name': 'list-services',
            'code': list_services,
            'desc': u'вывести список всех доступных сервисов'
        },
        {
            'name': 'setup',
            'code': lambda(args_dict): setup_service(args_dict['service_name'], **{k: args_dict[k] for k in args_dict if k != 'service_name'}),
            'desc': u'использовать локальный сервис (get+prepare+start+enable)',
            'args': ['service_name', '--branch', '--commit', '--build', '--fast'],
        },
        {
            'name': 'get',
            'code': lambda(args_dict): get_files(args_dict['scource'], **{k: args_dict[k] for k in args_dict if k != 'source'}),
            'desc': u'привезти на бету файлы для запуска сервиса (например, рабочую копию инфоблока из github: get infoblock-git)',
            'args': ['source', '--branch', '--revision'],
        },
        {
            'name': 'update',
            'code': lambda(args_dict): update_files(args_dict['source'], **{k: args_dict[k] for k in args_dict if k != 'source'}),
            'desc': u'обновить рабочую копию или артефакты сборки',
            'args': ['source', '--revision'],
        },
        {
            'name': 'update-dna',
            'code': lambda(args_dict): services['dna']['update'](),
            'desc': u'обновить dna',
            'args': [],
        },
        {
            'name': 'prepare',
            'code': lambda(args_dict): services[args_dict['service_name']]['prepare'](),      # TODO перенести логику prepare в репозитории с сервисами
            'desc': u'подготовить локальный сервис к запуску (создать файлы конфигурации, скрипты запуска/остановки)',
            'args': ['service_name'],
        },
        {
            'name': 'start',
            'code': lambda(args_dict): start_service(args_dict['service_name']),
            'desc': u'запустить локальный сервис',
            'args': ['service_name'],
        },
        {
            'name': 'stop',
            'code': lambda(args_dict): stop_service(args_dict['service_name']),
            'desc': u'остановить локальный сервис',
            'args': ['service_name'],
        },
        {
            'name': 'restart',
            'code': lambda(args_dict): restart_service(args_dict['service_name'], **{k: args_dict[k] for k in args_dict if k != 'service_name'}),
            'desc': u'перезапустить локальный сервис',
            'args': ['service_name', '--only-if-up?'],
        },
        {
            'name': 'enable',
            'code': lambda(args_dict): services[args_dict['service_name']]['enable'](),
            'desc': u'включить локальный сервис на бете (направить бету на него)',
            'args': ['service_name'],
        },
        {
            'name': 'disable',
            'code': lambda(args_dict): services[args_dict['service_name']]['disable'](),
            'desc': u'отключить локальный сервис на бете',
            'args': ['service_name'],
        },
        {
            'name': 'canvas-update-id',
            'code': lambda(args_dict): canvas_update_id(**args_dict),
            'desc': u'обновить creative id в канвасе (если не указан, берётся из БД)',
            'args': ['--creative_id'],
        },
        {
            'name': 'switch-canvas-tag',
            'code': lambda(args_dict): switch_canvas_tag(args_dict['tag']),
            'desc': u'поменять активный тег канваса и, если сервис запущен, перезапустить его',
            'args': ['tag'],
        },
        {
            'name': 'canvas-enable-frontends-nginx',
            'code': lambda(args_dict): canvas_enable_frontends(**args_dict),
            'desc': u'Направить nginx на локальные frontend и freact без докера',
            'args': [],
        },
        {
            'name': 'canvas-build',
            'code': lambda(args_dict): canvas_build(**args_dict),
            'desc': u'собрать канвас из локальной копии. Если репозитория канваса нет, он будет клонирован с веткой branch,'
                    u' иначе опция игнорируется, и используется существующая копия.',
            'args': ['--tag', '--branch'],
        },
        {
            'name': 'enable-dna-override',
            'code': dna_hashsums_override_enable,
            'desc': u'включить переопределение хэшсумм DNA на бете'
        },
        {
            'name': 'disable-dna-override',
            'code': dna_hashsums_override_disable,
            'desc': u'выключить переопределение хэшсумм DNA на бете'
        },
    ]
    def list_actions():
        for a in actions:
            print u"{:<20} {}".format(a['name'], a['desc'])
    actions.insert(0, {'name': 'list-actions', 'code': list_actions, 'desc': u'вывести список действий'})
    actions_dict = {}
    for a in actions:
        d = dict(a)
        name = d['name']
        del d['name']
        actions_dict[name] = d

    parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument('--dir', nargs='?', help=u'директория с бетой, по умолчанию текущая')
    subparsers = parser.add_subparsers(dest='action', metavar='action', help=u'действие, которое нужно выполнить')
    for a in actions:
        p = subparsers.add_parser(a['name'], help=a['desc'])
        if 'args' in a:
            for arg in a['args']:
                if a['name'] == 'get' and arg == 'source':    # TODO перетащить в спецификацию действия
                    p.add_argument(
                        arg, choices=['infoblock-git', 'dna-arc', 'arcadia', 'canvas-git'])
                elif arg == 'service_name':
                    available_services = services.keys()
                    if a['name'] in ['stop', 'restart']:
                        available_services.append('all')
                    p.add_argument(arg, choices=available_services)
                else:
                    if arg[-1] == '?':
                        arg = arg[:-1]
                        p.add_argument(arg, nargs='?', const=True, default=False)
                    else:
                        p.add_argument(arg)
    args = parser.parse_args()
    beta_dir = args.dir
    action = args.action
    if beta_dir is None:
        beta_dir = os.path.normpath(os.getcwd())
    beta_root = ''
    # TODO не привязываться к пути, например, искать beta-settings.json
    match = re.search(r'(.*/www/beta\.[^.]+\.([0-9]+)(?:/direct/perl)?)', beta_dir)
    if match:
        # beta_root -- корень беты. beta_perl_root -- корень perl-подкаталога. Для svn-бет совпадают, для arc-бет различаются
        beta_root = match.group(1)
        beta_perl_root = beta_root
        beta_path_port = int(match.group(2))
        if os.path.isdir( beta_root + "/arcadia" ) and os.path.isdir( beta_root + "/store" ):
            beta_perl_root = beta_root + "/arcadia/direct/perl"
    else:
        if action == 'get-settings':
            print '{}'
            sys.exit(0)
        else:
            sys.exit('директория {} не является бетой'.format(beta_dir))
    can_write_settings = action not in ['list', 'get-settings']
    settings_file = os.path.join(beta_root, 'beta-settings.json')
    # TODO для get-settings брать по-хорошему нужно, но пока лок берётся на всю бету для любой операции, возникают проблемы с запуском beta-ctl из beta-ctl (например, опосредованно при перезапуске апача)
    # пока beta-ctl используется мало, это не очень страшно, но когда он будет использоваться всюду, могут возникать трудноуловимые ошибки, когда апач может читать что-то неожиданное из конфига, пока какой-то beta-ctl туда пишет.
    # нужно сделать более гранулированную блокировку -- отдельно на настройки, отдельно на бету
    # и может быть, отдельную для каждого сервиса
    if action != 'list' and action != 'get-settings' and 'BETA_CTL_LOCK' not in os.environ:
        lock_file = os.path.join(beta_root, 'beta-ctl.lock')
        os.environ['BETA_CTL_LOCK'] = lock_file
        try:
            lf = open(lock_file, 'w')
            if can_write_settings:
                fcntl.lockf(lf, fcntl.LOCK_EX | fcntl.LOCK_NB)
            else:
                lf.close()
                # в принципе, тут файл может кто-нибудь удалить, но вероятность этого мала, а потери невелики -- просто упадёт get-settings
                lf = open(lock_file, 'r')
                fcntl.lockf(lf, fcntl.LOCK_SH | fcntl.LOCK_NB)
        except IOError, e:
            if e.errno == errno.EAGAIN:
                sys.exit(u"не удалось взять лок, скорее всего, на этой бете работает другой процесс beta-ctl? (файл {} )".format(lock_file))
            else:
                raise
    global settings
    settings = {}
    no_old_settings = False
    try:
        with open(settings_file, 'r') as f:
            settings = json.loads(f.read())
    except IOError, e:
        if e.errno == errno.ENOENT:
            if can_write_settings:
                settings = {
                    'user': pwd.getpwuid(os.getuid()).pw_name,
                    'root': beta_root,
                    'perl_root': beta_perl_root,
                    'port': beta_path_port,
                    'ppcdev_number': int((beta_path_port - 7000) / 1000),
                    '_version': settings_format_version(),
                }
                no_old_settings = True
            else:
                pass
        else:
            sys.exit(u'ошибка: не удалось прочитать настройки: {} (файл {} )'.format(e.strerror, settings_file))
    except ValueError:
        sys.exit(u'ошибка: не удалось прочитать настройки: невалидный json (файл {} )'.format(settings_file))

    if no_old_settings:
        old_settings = {}
    else:
        old_settings = copy.deepcopy(settings)
    try:
        if 'args' in actions_dict[action]:
            args_dict = vars(args)
            del args_dict['action']
            del args_dict['dir']
            for arg in args_dict.keys():
                if args_dict[arg] is None:
                    del args_dict[arg]
            actions_dict[action]['code'](args_dict)
        else:
            actions_dict[action]['code']()
    except BetaCtlActionError, e:
        sys.exit(u'ошибка при выполнении действия {}: {}'.format(action, unicode(e)))
    if settings != old_settings and can_write_settings:
        try:
            with open(settings_file, 'w') as f:
                f.write(json.dumps(settings, sort_keys=True, indent=4, separators=(',', ': ')))
        except IOError, e:
            sys.exit(u'ошибка: не удалось записать новые настройки: {} (файл {} )'.format(e.strerror, settings_file))

def normalize_settings():
    global settings
    if 'perl_root' not in settings:
        settings['perl_root'] = settings['root']
        if os.path.isdir( settings['root'] + "/arcadia" ) and os.path.isdir( settings['root'] + "/store" ):
            settings['perl_root'] = settings['root'] + "/arcadia/direct/perl"
    save_settings()
    return


def get_settings():
    global settings
    internal_vars = [k for k in settings.keys() if k.startswith('_')]
    visible_settings = dict(settings)
    for k in internal_vars:
        del(visible_settings[k])
    print json.dumps(visible_settings, sort_keys=True, indent=4, separators=(',', ': '))

def save_settings():
    global settings
    try:
        settings_file = os.path.join(settings['root'], 'beta-settings.json')
        with open(settings_file, 'w') as f:
            f.write(json.dumps(settings, sort_keys=True, indent=4, separators=(',', ': ')))
    except IOError, e:
        raise BetaCtlActionError(u'ошибка: не удалось записать новые настройки: {} (файл {} )'.format(e.strerror, settings_file))

def set_var(name, value):
    global settings
    if name.startswith('_'):
        raise BetaCtlActionError(u'нельзя установить настройку, начинающуюся с "_" -- предназначена для внутреннего использования')
    settings[name] = value

def set_var_from_json(name, value_json):
    global settings
    try:
        if name.startswith('_'):
            raise BetaCtlActionError(u'нельзя установить настройку, начинающуюся с "_" -- предназначена для внутреннего использования')
        value = json.loads(value_json)
        settings[name] = value
    except ValueError, e:
        raise BetaCtlActionError(e)

def unset_var(name):
    global settings
    try:
        if name.startswith('_'):
            raise BetaCtlActionError(u'нельзя сбросить настройку, начинающуюся с "_" -- предназначена для внутреннего использования')
        del settings[name]
    except KeyError:
        pass

def get_service_dir(name):
    global settings
    beta_services_dir = os.path.join(settings['root'], 'beta-services')
    return os.path.join(beta_services_dir, name)

def mark_service_up(name):
    '''Пометить, что сервис запущен (нужно, чтобы понимать, что нужно перезапустить/остановить при restart/stop all)'''
    global settings
    open(os.path.join(get_service_dir(name), 'up'), 'a').close()

def mark_service_down(name):
    global settings
    try:
        os.remove(os.path.join(get_service_dir(name), 'up'))
    except OSError, e:
        if e.errno == errno.ENOENT:
            pass
        else:
            raise

def is_service_marked_up(name):
    global settings
    return os.path.exists(os.path.join(get_service_dir(name), 'up'))

def start_service(name):
    global settings
    service_dir = get_service_dir(name)
    mark_service_up(name)
    start_script = os.path.join(service_dir, 'start')
    print "Starting service {}".format(name)
    subprocess.check_call([start_script])

def stop_service(name):
    global settings
    beta_services_dir = os.path.join(settings['root'], 'beta-services')
    if name == 'all':
        services_to_stop = [s for s in os.listdir(beta_services_dir) if is_service_marked_up(s)]
        for s in services_to_stop:
            stop_service(s)
    else:
        mark_service_down(name)
        print "Stopping service {}".format(name)
        stop_script = os.path.join(get_service_dir(name), 'stop')
        subprocess.check_call([stop_script])

def restart_service(name, only_if_up=False):
    global settings
    beta_services_dir = os.path.join(settings['root'], 'beta-services')
    services_to_restart = []
    if name == 'all':
        services_to_restart = [s for s in os.listdir(beta_services_dir) if is_service_marked_up(s)]
    else:
        if not only_if_up or (only_if_up and is_service_marked_up(name)):
            services_to_restart = [name]
    for s in services_to_restart:
        stop_service(s)
        start_service(s)

def git_clone_infoblock(branch=None, commit=None):
    global settings
    infoblock_git_url = 'https://github.yandex-team.ru/direct/yandex-ppc-direct.infoblock.git'
    infoblock_dir = os.path.join(settings['root'], 'infoblock')
    if not os.path.exists(infoblock_dir):
        print 'Cloning infoblock'
        cmd = ['git', 'clone', infoblock_git_url, infoblock_dir]
        if branch is not None:
            cmd += ['--branch', branch]
        subprocess.check_call(cmd)
    else:
        print 'infloblock already present, skip'

def prepare_infoblock():
    global settings
    infoblock_dir = os.path.join(settings['root'], 'infoblock', 'infoblock')
    service_dir = os.path.join(settings['root'], 'beta-services', 'infoblock')
    settings.setdefault('infoblock_dir', infoblock_dir)
    settings.setdefault('infoblock_host', '127.0.0.17')
    settings.setdefault('infoblock_pidfile', os.path.join(service_dir, 'pid'))
    settings.setdefault('infoblock_log_dir', service_dir)
    settings.setdefault('jsonrpc_url', 'http://{}.beta{}.intapi.direct.yandex.ru/jsonrpc'.format(settings['port'], settings['ppcdev_number']))
    settings.setdefault('infoblock_uwsgi_conf_file', os.path.join(service_dir, 'infoblock_uwsgi_beta.ini'))
    if 'db_config_file' not in settings:
        db_config_file = subprocess.check_output(['perl', '-I', os.path.join(settings['root'], 'perl', 'settings'), '-MSettings', '-e', 'print $Yandex::DBTools::CONFIG_FILE'])
        settings.setdefault('db_config_file', db_config_file)
    try:
        os.makedirs(service_dir, 0755)
    except OSError, e:
        if e.errno == errno.EEXIST:
            pass
        else:
            raise
    create_file_from_template(os.path.join(infoblock_dir, 'beta', 'infoblock_uwsgi_beta.ini.tmpl'), settings['infoblock_uwsgi_conf_file'], mode=0644)
    create_file_from_template(os.path.join(infoblock_dir, 'beta', 'start.tmpl'), os.path.join(service_dir, 'start'), mode=0755)
    create_file_from_template(os.path.join(infoblock_dir, 'beta', 'stop.tmpl'), os.path.join(service_dir, 'stop'), mode=0755)

def enable_infoblock():
    global settings
    beta_infoblock_url = 'http://{}:{}'.format(settings['infoblock_host'], settings['port'])
    update_and_save_old_var('infoblock_url', beta_infoblock_url)
    update_and_save_old_var('infoblock_ajax_url', '/public?cmd=ajaxInfoblockRequest')
    save_settings()
    subprocess.check_call(['direct-mk', settings['root'], 'httpd-conf'])

def disable_infoblock():
    global settings
    beta_infoblock_url = 'http://{}:{}'.format(settings['infoblock_host'], settings['port'])
    if settings['infoblock_url'] == beta_infoblock_url:
        restore_old_var('infoblock_url')
    #    subprocess.check_call(['direct-mk', settings['root'], 'beta-postupdate'])
    else:
        pass
    save_settings()
    subprocess.check_call(['direct-mk', settings['root'], 'httpd-conf'])

def git_clone_canvas(branch=None):
    global settings

    canvas_dir = os.path.join(settings['root'], 'canvas')

    if not os.path.exists(canvas_dir):
        logger.info('cloning canvas')
        cmd = ['git', 'clone', 'https://github.yandex-team.ru/Canvas/canvas.git', canvas_dir]
        if branch is not None:
            cmd += ['--branch', branch]
        subprocess.check_call(cmd)
    else:
        logger.info('canvas already present, skipping')

def _canvas_set_vars_from_environ():
    global settings

    if settings.get('canvas_vars_set'):
        logger.info('canvas vars already set from environ, skipping')
        return

    build_flag = os.getenv('CANVAS_BUILD')
    if build_flag is None:
        build_flag = False
    else:
        build_flag = bool(int(build_flag))

    video_search_flag = os.getenv('CANVAS_VIDEO_SEARCH')
    if video_search_flag is None:
        video_search_flag = False
    else:
        video_search_flag = bool(int(video_search_flag))

    set_var('canvas_tag', os.getenv('CANVAS_TAG'))
    set_var('canvas_video_search', video_search_flag)
    set_var('canvas_build', build_flag)
    set_var('canvas_branch', os.getenv('CANVAS_BRANCH'))
    set_var('canvas_vars_set', True)
    save_settings()

def prepare_canvas():
    logger.info('preparing canvas')
    global settings

    _canvas_set_vars_from_environ()

    service_dir = os.path.join(settings['root'], 'beta-services', 'canvas')
    try:
        os.makedirs(service_dir, 0755)
    except OSError, e:
        if e.errno == errno.EEXIST:
            pass
        else:
            raise

    canvas_dir = os.path.join(settings['root'], 'canvas')

    tag = settings['canvas_tag']
    if tag is None:
        tag = 'develop'
    video_search = settings['canvas_video_search']
    branch = settings['canvas_branch']

    set_var('canvas_tag', tag)
    save_settings()

    git_clone_canvas(branch=branch)

    if video_search:
        video_search_dir = os.path.join(canvas_dir, 'video_search_model')
        if not os.path.exists(video_search_dir):
            logger.info('downloading video search model')
            subprocess.check_call('cd {} && ./download_files.sh'.format(canvas_dir), shell=True)
    else:
        logger.info('video search is disabled')

    _make_canvas_start_stop()

def switch_canvas_tag(tag):
    logger.info('switching canvas tag to %s', tag)
    global settings

    old_tag = settings['canvas_tag']
    if tag == old_tag:
        logger.info('tag %s already set', tag)
        return

    is_up = is_service_marked_up('canvas')
    if is_up:
        logger.info('stopping old instance')
        stop_service('canvas')

    set_var('canvas_tag', tag)
    _make_canvas_start_stop(force=True)
    save_settings()
    if is_up:
        logger.info('starting new instance')
        start_service('canvas')

def canvas_build(tag=None, branch=None):
    global settings

    git_clone_canvas(branch=branch)

    if tag is None:
        tag = 'canvas_beta_{}'.format(settings['port'])
    canvas_dir = os.path.join(settings['root'], 'canvas')

    logger.info('building canvas from working copy with tag %s', tag)
    subprocess.check_call([
        'env', 'BUILD_TAG={}'.format(tag), 'docker-compose', '-f',
        '{}/docker-compose.direct-build.yml'.format(canvas_dir), 'build'
    ])
    return tag

def _make_canvas_start_stop(force=False):
    global settings

    tag = settings['canvas_tag']
    video_search = settings['canvas_video_search']

    service_dir = os.path.join(settings['root'], 'beta-services', 'canvas')
    canvas_dir = os.path.join(settings['root'], 'canvas')

    beta_port = settings['port']
    beta_number = settings['ppcdev_number']
    intapi_url = "http://{}.beta{}.intapi.direct.yandex.ru".format(beta_port, beta_number)
    canvas_host = "canvas-{}.beta{}.direct.yandex.ru".format(beta_port, beta_number)

    start_path = os.path.join(service_dir, 'start')
    freact_pidfile = os.path.join(service_dir, 'freact.pid')
    frontend_pidfile = os.path.join(service_dir, 'frontend.pid')
    log_dir = os.path.join(service_dir, 'logs')
    try:
        os.makedirs(log_dir)
    except OSError as e:
        if e.errno == errno.EEXIST:
            pass
        else:
            raise
    freact_log_path = os.path.join(log_dir, 'freact.log')
    frontend_log_path = os.path.join(log_dir, 'frontend.log')
    if os.path.exists(start_path) and not force:
        logger.warn('start script already exists')
    else:
        logger.info('creating start script')
        with open(start_path, 'w') as f:
            up_cmd = '''#!/bin/bash
set -e
printf "Starting freact, logs are written to %s\\n" "{freact_log_path}"
start-stop-daemon --start --pidfile {freact_pidfile} --make-pidfile --background --startas /bin/sh -- -c "exec {freact_beta_run} > {freact_log_path} 2>&1"
printf "Starting frontend, logs are written to %s\\n" "{frontend_log_path}"
start-stop-daemon --start --pidfile {frontend_pidfile} --make-pidfile --background --startas /bin/sh -- -c "exec {frontend_beta_run} > {frontend_log_path} 2>&1"
freact_pid=$(<{freact_pidfile})
frontend_pid=$(<{frontend_pidfile})
printf "Waiting for services to start"
wait_timeout=180
set +e
for i in `seq 1 $wait_timeout`; do
   if [ -z "$freact_done" ]; then freact_done=$(strings /proc/$freact_pid/environ|grep DIRECT_CANVAS_FREACT_SETUP_DONE=1); fi
   if [ -z "$frontend_done" ]; then frontend_done=$(strings /proc/$frontend_pid/environ|grep DIRECT_CANVAS_FRONTEND_SETUP_DONE=1); fi
   if [ -n "$freact_done" -a -n "$frontend_done" ]; then
       printf "done\\n"
       exit 0
   fi
   sleep 1
   printf .
done
set -e
printf "timed out after %d seconds\\n" $wait_timeout
exit 1'''.format(
                freact_pidfile=freact_pidfile,
                frontend_pidfile=frontend_pidfile,
                freact_beta_run=os.path.join(canvas_dir, 'freact', 'beta_run.sh'),
                frontend_beta_run=os.path.join(canvas_dir, 'frontend', 'beta_run.sh'),
                freact_log_path=freact_log_path,
                frontend_log_path=frontend_log_path)
            f.write(up_cmd)
        os.chmod(start_path, 0755);

    stop_path = os.path.join(service_dir, 'stop')
    if os.path.exists(stop_path) and not force:
        logger.warn('stop script already exists')
    else:
        logger.info('creating stop script')
        with open(stop_path, 'w') as f:
            down_cmd = '''#!/bin/bash
set -e
printf "Stopping freact\\n"
start-stop-daemon --stop --pidfile {freact_pidfile} --oknodo
printf "Stopping frontend\\n"
start-stop-daemon --stop --pidfile {frontend_pidfile} --oknodo'''.format(
                freact_pidfile=freact_pidfile,
                frontend_pidfile=frontend_pidfile,
            )
            f.write(down_cmd)
        os.chmod(stop_path, 0755)

def enable_canvas():
    logger.info('enabling canvas')
    global settings
    # настройки из https://github.yandex-team.ru/Canvas/canvas/blob/develop/docker-compose.direct-beta.yml
    set_var('canvas_ui', '127.0.0.31')
    set_var('video_additions_api', '127.0.0.32')
    set_var('canvas_api', '127.0.0.33')

    set_var('canvas_ui_domain', 'canvas-{}.beta{}.direct'.format(settings['port'], settings['ppcdev_number']))
    set_var('bs_canvas_api_url', 'http://canvas-{}.beta{}.direct.yandex.ru/beta-canvas-api/direct'.format(settings['port'], settings['ppcdev_number']))
    set_var('bs_video_additions_api_url', 'https://canvas-{}.beta{}.direct.yandex.ru/beta-canvas-api/video/direct'.format(
        settings['port'], settings['ppcdev_number']))
    save_settings()

    subprocess.check_call(['direct-mk', settings['root'], 'httpd-conf'])

def canvas_enable_frontends():
    logger.info('setting up nginx to work director for canvas freact and frontend')

    set_var('canvas_ui', '127.0.0.31')
    set_var('video_additions_api', '127.0.0.40')
    set_var('canvas_api', '127.0.0.40')

    set_var('canvas_ui_domain', 'canvas-{}.beta{}.direct'.format(settings['port'], settings['ppcdev_number']))
    set_var('bs_canvas_api_url', 'http://canvas-{}.beta{}.direct.yandex.ru/beta-canvas-api/direct'.format(settings['port'], settings['ppcdev_number']))
    set_var('bs_video_additions_api_url', 'https://canvas-{}.beta{}.direct.yandex.ru/beta-canvas-api/video/direct'.format(
        settings['port'], settings['ppcdev_number']))
    set_var('canvas_no_docker', True)
    save_settings()

    subprocess.check_call(['direct-mk', settings['root'], 'httpd-conf'])

def disable_canvas():
    logger.info('disabling canvas')
    global settings
    unset_var('canvas_ui')
    unset_var('video_additions_api')
    unset_var('canvas_api')
    unset_var('canvas_ui_domain')
    unset_var('bs_canvas_api_url')
    unset_var('bs_video_additions_api_url')
    save_settings()

    subprocess.check_call(['direct-mk', settings['root'], 'httpd-conf'])

def canvas_update_id(creative_id=None):
    global settings
    if creative_id is None:
        config = settings['db_config_file']
        logger.info('getting creative id from ppcdict using config %s', config)
        creative_id = int(subprocess.check_output([
            'perl', '-MYandex::DBTools', '-e',
            '''
                $Yandex::DBTools::CONFIG_FILE="{}";
                (my $creative_id) = get_one_line_array_sql(ppcdict, "select creative_id from shard_creative_id order by creative_id desc limit 1");
                print "$creative_id"
            '''.format(config)
        ]).strip()) + 1000
    else:
        creative_id = int(creative_id)

    canvas_dir = os.path.join(settings['root'], 'canvas')
    beta_port = settings['port']
    logger.info('setting creative id to %d', creative_id)
    subprocess.check_call([
        "env", "COMPOSE_PROJECT_NAME=canvas_beta_{}".format(beta_port),
        "docker-compose", "-f", "{}/docker-compose.direct-beta.yml".format(canvas_dir),
        "exec", "mongodb", "mongo", "canvas", "--eval",
        "db.sequence.update({{'_id': 'creativeid'}}, {{'seq': {}}}, {{'upsert': true}})".format(creative_id)
    ])

def get_twilight_token():
    file_path = '/etc/direct-tokens/twilight/.env'
    if 'TWILIGHT_OAUTH_TOKEN' not in os.environ:
        with open(file_path) as fd:
            for line in fd:
                name, val = line.split('=')
                os.environ[name] = val.replace('"', '').replace('\n', '')
    return os.environ['TWILIGHT_OAUTH_TOKEN']

users = [
    'aleks-konst',
    'klesteralexey',
    'slepo',
    'invizor',
    'tau-chieftain',
    'sudar',
    'edip-asanov',
    'vovichek62',
    'larilena',
    'yurijskachkov',
    'trapitsyn',
    'olimiya',
    'kturchak',
    'qavaleria',
    'raketa',
    'irineva',
    'l-jul',
    'sonch',
    'ngavrilova',
    'jensenspb',
]


def dna_arc_implicit_testing(branch):
    if branch == 'trunk' or getpass.getuser() not in users:
        return

    client = Startrek(token=get_twilight_token(), useragent=u'robot-twilight')

    implicit_testing_commit = '14ec46d751454132a636cc132349be4f9c44171d'
    implicit_testing_ticket = 'DIRECT-104128'

    issue, = client.issues.find('key: %s' % implicit_testing_ticket)

    os.chdir('dna_arc')

    subprocess.check_call(['arc', 'cherry-pick', implicit_testing_commit])

    diff_files_output = subprocess.check_output(['arc', 'diff', '--git', '--name-only'])
    diff_files = [line for line in diff_files_output.split('\n') if line]

    if len(diff_files):
        subprocess.check_call(['arc', 'reset', '--hard'])
        issue.comments.create(text=u'failed to apply patch to branch: %s' % branch)
    else:
        issue.comments.create(text=u'implicitly testing %s on %s branch on beta: %s' % (implicit_testing_ticket, branch, os.getcwd()))

    os.chdir('../')


def arc_clone_dna(branch=None, commit=None):
    if not branch or branch == 'trunk':
        branch = 'trunk'
    global settings
    dna_dir = os.path.join(settings['root'], 'dna')
    dna_arc_dir = os.path.join(settings['root'], 'dna_arc')
    dna_arc_store_dir = os.path.join(settings['root'], 'dna_arc_store')
    dna_arc_dir_target = os.path.join(dna_arc_dir, 'adv', 'frontend', 'services', 'dna')

    if os.path.exists(dna_arc_dir):
        try:
            print('Unmounting arcadia at {}'.format(dna_arc_dir))
            subprocess.check_call(['arc', 'unmount', dna_arc_dir])
        except subprocess.CalledProcessError as e:
            print('Arcadia not mounted at {}'.format(dna_arc_dir))

    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'})

    os.chdir(dna_arc_dir)
    subprocess.check_call(['arc', 'fetch', branch])
    subprocess.check_call(['arc', 'checkout', branch])
    subprocess.check_call(['arc', 'pull', branch])
    os.chdir('../')

    if commit:
        os.chdir(dna_arc_dir)
        subprocess.check_call(['arc', 'checkout', commit])
        os.chdir('../')

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


def prepare_dna():
    global settings
    dna_dir = os.path.join(settings['root'], 'dna_arc', 'adv', 'frontend', 'services', 'dna')
    data3_dir = os.path.join(settings['root'], 'data3')
    service_dir = os.path.join(settings['root'], 'beta-services', 'dna')
    settings.setdefault('dna_dir', dna_dir)
    settings.setdefault('dna_enabled', False)
    old_dir = os.getcwd()
    os.chdir(dna_dir)
    subprocess.check_call(['npm', 'run', 'install:scripts-deps'])
    subprocess.check_call(['npm', 'run', 'deploy'])
    os.chdir(old_dir)
    subprocess.check_call([
        './protected/maintenance/hash_data2_files.pl',
        'data3/desktop.bundles/direct',
        'data3/desktop.bundles/head',
        'dna/build/static/css/',
        'dna/build/static/js/',
        '-o', 'data3/hashsums.json'
    ])
    os.chdir(data3_dir)
    subprocess.check_call(['make', 'direct-for-robots', 'js-templater-restart'])
    os.chdir(old_dir)

    # Создаем директорию для сервиса dna, в котором будут находится скрипты экшенов
    try:
        os.makedirs(service_dir, 0755)
    except OSError, e:
        if e.errno == errno.EEXIST:
            pass
        else:
            raise

    # Генерируем скрипт для запуска dna
    start_script = os.path.join(service_dir, 'start')
    start_script_content = '#!/bin/bash'
    with open(start_script, 'w') as f:
        f.write(start_script_content)
    os.chmod(start_script, 0755)

    # Гененрируем скрипт остановки dna
    dna_arc_dir = os.path.join(settings['root'], 'dna_arc')
    stop_script = os.path.join(service_dir, 'stop')
    stop_script_content = '''#!/usr/bin/python
# -*- coding: utf-8 -*-

import os
import subprocess

dna_arc_dir = '{}'

if os.path.exists(dna_arc_dir):
    try:
        os.chdir(dna_arc_dir)
        subprocess.check_call(['arc', 'info'])
        os.chdir('../')
    except subprocess.CalledProcessError:
        exit()
    subprocess.check_call(['arc', 'unmount', dna_arc_dir])'''.format(dna_arc_dir)
    with open(stop_script, 'w') as f:
        f.write(stop_script_content)
    os.chmod(stop_script, 0755)

def enable_dna():
    global settings
    set_var('dna_enabled', True)
    save_settings()
    subprocess.check_call(['direct-mk', settings['root'], 'httpd-conf'])

def disable_dna():
    global settings
    set_var('dna_enabled', False)
    save_settings()
    subprocess.check_call(['direct-mk', settings['root'], 'httpd-conf'])


def update_dna():
    global settings
    dna_dir = os.path.join(settings['root'], 'dna_arc', 'adv', 'frontend', 'services', 'dna')

    if os.path.exists(dna_dir):
        old_dir = os.getcwd()
        os.chdir(dna_dir)

        subprocess.check_call(['arc', 'pull', '--rebase'])
        subprocess.check_call(['npm', 'run', 'deploy'])

        os.chdir(old_dir)

        subprocess.check_call(['ln', '-sf', dna_dir, os.path.join(settings['root'], 'dna')])
    else:
        print 'dna arc is not set up'

def dna_hashsums_override_enable():
    global settings
    set_var('dna_override_hashsums_allowed', True)
    save_settings()
    subprocess.check_call(['direct-mk', settings['root'], 'httpd-conf'])

def dna_hashsums_override_disable():
    global settings
    set_var('dna_override_hashsums_allowed', False)
    save_settings()
    subprocess.check_call(['direct-mk', settings['root'], 'httpd-conf'])

def prepare_js_templater():
    global settings
    service_dir = get_service_dir('js-templater')
    settings.setdefault('js_templater_host', '127.0.0.10')
    settings.setdefault('js_templater_ops_port', int(settings['port']) + 1000)
    settings.setdefault('js_templater_port', int(settings['port']) + 2000)
    settings.setdefault('js_templater_log_dir', os.path.join(service_dir, 'logs'))
    try:
        os.makedirs(service_dir, 0755)
        os.makedirs(settings['js_templater_log_dir'], 0755)
    except OSError, e:
        if e.errno == errno.EEXIST:
            pass
        else:
            raise

    # TODO перенести эти скрипты в репозиторий Директа?
    for cmd in ['start', 'stop']:
        script = os.path.join(service_dir, cmd)
        with open(script, 'w') as f:
            f.write('#!/bin/sh\n' + os.path.join(settings['root'], 'js-templater', 'init.sh') + ' ' + cmd)  # <...>/js-templater/init.sh {start|stop}
        os.chmod(script, 0755)
    save_settings()
    subprocess.check_call(['direct-mk', settings['root'], 'httpd-conf-pure'])
    # сейчас bem-make делается всегда и второй раз его делать не нужно
    # TODO когда все страницы перейдут на внешнюю шаблонизацию, в etc/quasi-make/Beta.pm все bem-make заменить на prepare/setup js-templater
    # не забыть поискать в остальном репозитории и direct-utils -- как минимум в Makefile сборки бема и direct-create-beta может использоваться direct-mk
    #subprocess.check_call(['direct-mk', settings['root'], 'bem-make'])

def enable_js_templater():
    global settings
    beta_js_templater_url = 'http://{}:{}/template'.format(settings['js_templater_host'], settings['js_templater_port'])
    update_and_save_old_var('js_templater_url', beta_js_templater_url)
    save_settings()
    subprocess.check_call(['direct-mk', settings['root'], 'apache-restart'])

def disable_js_templater():
    global settings
    beta_js_templater_url = 'http://{}:{}/template'.format(settings['js_templater_host'], settings['js_templater_port'])
    if 'js_templater_url' in settings and settings['js_templater_url'] == beta_js_templater_url:
        restore_old_var('js_templater_url')
    if 'js_templater_url' not in settings:
        # пока по умолчанию используется локальный js-templater, потому что много клиентской разработки и каждый раз его включать при создании беты разработчику/тестировщику неудобно.
        # Нужно сделать передачу сервисов, которые сразу хочется включить при создании беты и сделать какие-то удобные шорткаты, чтобы каждый раз об этом не думать.
        # А сейчас при отключении локального js-templater переключаемся на 8998

        settings['js_templater_url'] = 'http://ppcdev1.yandex.ru:10998/template'
    save_settings()
    subprocess.check_call(['direct-mk', settings['root'], 'apache-restart'])

def setup_service(name, **kwargs):
    if name == 'infoblock':
        branch = None
        if 'branch' in kwargs:
            branch = kwargs['branch']
        setup_infoblock(branch)  # TODO commit
    if name == 'dna':
        branch = None
        commit = None
        if 'branch' in kwargs:
            branch = kwargs['branch']
        if 'commit' in kwargs:
            commit = kwargs['commit']
        setup_dna(branch, commit)
    if name == 'js-templater':
        prepare_js_templater()
        start_service('js-templater')
        enable_js_templater()
    if name == 'canvas':
        setup_canvas()

def setup_infoblock(branch=None):
    git_clone_infoblock(branch=branch)   # TODO commit
    prepare_infoblock()
    start_service('infoblock')
    enable_infoblock()

def setup_dna(branch=None, commit=None):
    arc_clone_dna(branch=branch, commit=commit)
    prepare_dna()
    start_service('dna')
    enable_dna()

def setup_canvas():
    _canvas_set_vars_from_environ()
    build_flag = settings['canvas_build']

    if build_flag:
        tag = canvas_build(tag=settings['canvas_tag'], branch=settings['canvas_branch'])
        set_var('canvas_tag', tag)
        save_settings()

    prepare_canvas()
    start_service('canvas')
    enable_canvas()

def get_files(source, branch=None, revision=None):
    if source == 'infoblock-git':
        git_clone_infoblock(branch=branch)
    if source == 'dna-arc':
        arc_clone_dna(branch=branch)
    if source == 'arcadia':
        clone_arcadia(branch=branch, revision=revision)
    if source == 'canvas-git':
        git_clone_canvas(branch=branch)

def update_files(source, revision=None):
    global settings
    if source == 'infoblock-git':
        old_dir = os.getcwd()
        os.chdir(settings['infoblock_dir'])
        subprocess.check_call(['git', 'pull'])
        os.chdir(old_dir)
    if source == 'dna-arc':
        old_dir = os.getcwd()
        os.chdir(settings['dna_arc_dir'])
        subprocess.check_call(['arc', 'pull'])
        os.chdir(old_dir)
    if source == 'arcadia':
        arcadia_dir = os.path.join(settings['root'], 'arcadia')
        yatool = os.path.join(arcadia_dir, 'devtools', 'ya', 'ya')
        svn_up_cmd = [yatool, 'svn', 'update', arcadia_dir]
        if revision is not None:
            svn_up_cmd += ['--revision', revision]
        subprocess.check_call(svn_up_cmd)
        subprocess.check_call([yatool, 'make', '--checkout', '-j0', os.path.join(arcadia_dir, 'direct')])

def clone_arcadia(branch=None, revision=None):
    global settings
    arcadia_dir = os.path.join(settings['root'], 'arcadia')
    arcadia_svn_url = 'svn+ssh://arcadia.yandex.ru/arc/trunk/arcadia'
    if not os.path.exists(arcadia_dir):
        print 'Cloning arcadia'
        tempdir = tempfile.mkdtemp(prefix='yatool.', dir='/tmp/temp-ttl/ttl_1d')
        subprocess.check_call(['svn', 'checkout', arcadia_svn_url + '/devtools/ya', tempdir])
        temp_yatool = os.path.join(tempdir, 'ya')
        cmd = [temp_yatool, 'clone', '--no-junk', arcadia_dir]
        if branch is not None:
            cmd += ['--branch', 'branches/' + branch]
        if revision is not None:
            cmd += ['--revision', revision]
        subprocess.check_call(cmd)
        os.chdir(arcadia_dir)
        yatool = os.path.join(arcadia_dir, 'devtools', 'ya', 'ya')
        subprocess.check_call([yatool, 'make', '--checkout', '-j0', 'direct'])
    else:
        print 'arcadia already present, skip'

def set_db_config_file(db_name):
    global settings
    # в /etc/project_specific.yaml есть аналогичный список, можно переделать на него
    file_for_db_name = {
        'devtest': '/etc/yandex-direct/db-config-np/db-config.devtest.json',
        'dev7': '/etc/yandex-direct/db-config-np/db-config.dev7.json',
        'test': '/etc/yandex-direct/db-config-np/db-config.test.json',
        'test2': '/etc/yandex-direct/db-config-np/db-config.test2.json',
        'testload': '/etc/yandex-direct/db-config-np/db-config.testload.json',
        'sandboxdevtest': '/etc/yandex-direct/db-config-np/db-config.sandboxdevtest.json',
        'sandboxdev7': '/etc/yandex-direct/db-config-np/db-config.sandboxdev7.json',
        'sandboxtest': '/etc/yandex-direct/db-config-np/db-config.sandboxtest.json',
        'sandboxtest2': '/etc/yandex-direct/db-config-np/db-config.sandboxtest2.json',
        'roprod': '/etc/yandex-direct/db-config.json',
        'dockerdb': os.path.join(settings['root'], 'etc', 'db-config.dockerdb.json'),
    }
    if db_name in file_for_db_name:
        set_var('db_config_file', file_for_db_name[db_name])
    else:
        raise BetaCtlActionError, u'неизвестное имя базы "{0}", должно быть одним из: {1}'.format(db_name, ' '.join(sorted(file_for_db_name.keys())))

def set_mod_beta(beta_num):
    ppcmoddev_num = int(beta_num / 1000) - 7
    set_var('moderate_json_url', 'http://{0}.ppcmoddev{1}.yandex-team.ru/jsonrpc/'.format(beta_num, ppcmoddev_num))
    set_var('moderate_soap_url', 'http://{0}.ppcmoddev{1}.yandex-team.ru/soap/'.format(beta_num, ppcmoddev_num))

def use_java_from_ssh_tunnel(service_name, local_port=8090, host=None):
    global settings
    # скопировано из direct/trunk/etc/quasi-make/Beta.pm, sub _start_java_service
    host_for_service = {
        'logviewer': '127.0.0.11',
        'api5': '127.0.0.12',
        'intapi': '127.0.0.13',
        'web': '127.0.0.16',
        'canvas': '127.0.0.40',  # Бекенд канваса (java)
    }
    var_name = 'java_{}_endpoint'.format(service_name)
    try:
        if host is None:
            host = host_for_service[service_name]
        set_var(var_name, host + ':' + str(settings['port']))
        save_settings()
        subprocess.check_call(['direct-mk', settings['root'], 'httpd-conf'])
        print u'\n\n\n# команда для поднятия ssh-тоннеля выполнять на локальной машине (рабочем ноутбуке)\nssh -R {0}:{1}:localhost:{2} {3}\n# сбросить командой\nbeta-ctl unset {4}'.format(host, settings['port'], local_port, socket.gethostname(), var_name)
    except KeyError:
        raise BetaCtlActionError, u'неизвестное имя сервиса "{0}", должно быть одним из: {1}'.format(service_name, ' '.join(sorted(host_for_service.keys())))

def create_file_from_template(template_path, path, mode=None):
    global settings
    with open(template_path, 'r') as f:
        template = jinja2.Template(f.read().decode('utf-8'))
    with open(path, 'w') as f:
        f.write(template.render(**settings).encode('utf-8'))
    if mode is not None:
        os.chmod(path, mode)

def update_and_save_old_var(name, new_value):
    global settings
    if name in settings and settings[name] == new_value:
        pass
    else:
        if name in settings:
            settings['_old_' + name] = settings[name]
        settings[name] = new_value

def restore_old_var(name):
    global settings
    if ('_old_' + name) in settings:
        settings[name] = settings['_old_' + name]
        del settings['_old_' + name]
    else:
        del settings[name]

def settings_format_version():
    return 1

class BetaCtlActionError(Exception):
    pass

if __name__ == '__main__':
    main()
