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

## как добавить новое приложение -- см. ниже

description = """
# показать, что записано в ZK
dt-autorelease-ctl -a dna show

# проверить, подходит ли время для сборки релиза указанного приложения
dt-autorelease-ctl -a dna check


## Регулярное расписание

# поменять QA в регулярном расписании
dt-autorelease-ctl -a java-logviewer schedule --qa 'staff:lena-san'

# поменять дни в регулярном расписании
dt-autorelease-ctl -a java-logviewer schedule --days '1,3,5'

# поменять часы в регулярном расписании (острожно! Валидации нет, легко испортить расписание)
dt-autorelease-ctl -a java-logviewer schedule --hours '02:00-03:00'

# все параметры вместе: 
# назначить регулярную сборку по вторникам и четвергам с 9 утра до полудня, с указанным qa
dt-autorelease-ctl -a java-logviewer schedule --hours '09:00-12:00' --days '2,4' --qa 'staff:lena-san'


## Разовые команды (зафорсить сборку, запаузить)

## зафорсить сборку
# в ближайшие часы (значение по умолчанию: 3 часа) пытаться собрать указанное приложение:
dt-autorelease-ctl -a dna force

# с указанным release-qa:
dt-autorelease-ctl -a dna force --qa staff:lena-san

# начинать сборку спустя 2 часа:
dt-autorelease-ctl -a dna force -s 2

# продолжать пытаться начать собирать релиз 1 час, потом перестать:
dt-autorelease-ctl -a dna force -d 1

# все параметры вместе:
# через 2 часа начать пробовать и продолжать 3 часа, назначить релиз на указанный логин
dt-autorelease-ctl -a dna force --qa staff:lena-san -s 2 -d 3

# удалить расписание:
dt-autorelease-ctl -a ess-router clear-schedule


## отложить сборку
# в ближайшие часы (значение по умолчанию: 3 часа) НЕ пытаться собрать указанное приложение:
dt-autorelease-ctl -a dna pause

# через два часа начать паузу умолчальной длительности:
dt-autorelease-ctl -a dna pause -s 2

# включить паузу на 1 час:
dt-autorelease-ctl -a dna pause -d 1

# все параметры вместе:
# через 2 часа начать паузу и паузиться 4 часа
dt-autorelease-ctl -a dna pause -s 2 -d 4


## удалить текущую команду
dt-autorelease-ctl -a dna clear-cmd
"""

"""
Добавляем приложение direct:
ноды в ZK:
dt-zkcli -H ppctest-zookeeper01i.sas.yp-c.yandex.net touch /direct/autorelease-ctl/direct
dt-zkcli -H ppctest-zookeeper01i.sas.yp-c.yandex.net touch /direct/autorelease-ctl/direct/schedule  
dt-zkcli -H ppctest-zookeeper01i.sas.yp-c.yandex.net touch /direct/autorelease-ctl/direct/cmd     
dt-zkcli -H ppctest-zookeeper01i.sas.yp-c.yandex.net touch /direct/autorelease-ctl/direct/current

расписание:
dt-autorelease-ctl -a direct schedule --hours '16:00-19:00' --days '1,2,3,4,5' --qa 'abc_duty:qa-release'

дописать в кронтаб

"""



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

import argparse
import datetime
import json
import re
import os
import requests
import urllib
import random

from kazoo.client import KazooClient

ABC_API_URL = 'https://abc-back.yandex-team.ru/api/v4/'
OAUTH_HEADER = {}

def parse_options():
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("-i", "--insistence", dest="insistence", help="Настойчивость форсирования сборки", type=int, default=1)
    parser.add_argument("-s", "--start-in", dest="start_in", help="Форсировать сборку через указанное количество часов", type=int)
    parser.add_argument("-d", "--duration", dest="duration", help="Длительность окна форсированной сборки, часы", type=int)
    parser.add_argument("--qa", dest="release_qa", help="Release QA", type=str)
    parser.add_argument("--days", dest="schedule_days", help="Дни недели для сборки релиза", type=str)
    parser.add_argument("--hours", dest="schedule_hours", help="Часы для сборки релиза ('02:00-15:45')", type=str)
    parser.add_argument("-a", "--app", dest="app", help="Приложение", type=str)
    parser.add_argument("-h", "--help", dest="help", help="Справка", action="store_true")
    default_abc_token_file = os.environ.get( 'ABC_OAUTH_TOKEN_FILE', '~/.abc-auth-token' )
    parser.add_argument('--abc-token-file', default=default_abc_token_file, dest='token_file', help='путь до oauth токена для abc')
    opts, extra = parser.parse_known_args()

    if opts.help:
        print description
        print parser.format_help()
        exit(0)

    if len(extra) <= 0:
        exit("expecting action (check|force|pause|show|clear-cmd|clear-last-try|schedule)")

    opts.cmd = extra.pop(0)

    if not opts.app:
        exit("expecting -a <app>, see direct-release list-apps for list")

    if len(extra) > 0:
        exit("unexpected params %s" % extra)

    return opts


def ok_schedule(schedule, now):
    if not 'days' in schedule or not 'hours' in schedule:
        return False
    today_weekday = now.weekday() + 1
    if today_weekday not in schedule['days']:
        return False
    now_time = now.strftime('%H:%M')
    for p in schedule['hours']:
        # TODO обрабатывать случаи с неполными часами и с переходом через полночь
        # [ '23:00', '2:30' ]
        if now_time >= p[0] and now_time <= p[1]:
            return True
    return False


def check_cmd(cmd, now):
    if not 'cmd' in cmd:
        return ''
    now_time = now.strftime('%Y-%m-%d %H:%M:%S')
    if cmd['cmd'] == 'force' and now_time >= cmd['start_time'] and now_time <= cmd['end_time']:
        return 'force'
    if cmd['cmd'] == 'pause' and now_time >= cmd['start_time'] and now_time <= cmd['end_time']:
        return 'pause'
    return ''


def get_release_qa_login(schedule, cmd):
    global OAUTH_HEADER
    if 'cmd' in cmd and cmd['cmd'] == 'force' and 'QA-engineer' in cmd:
        qa = cmd['QA-engineer']
    else:
        qa = schedule['QA-engineer']

    # "staff:lena-san"
    m = re.match(r'staff:([^ ]*)', qa)
    if m:
        return m.group(1)
    # "abc_duty:direct_web_dna_release_duty"
    m = re.match(r'abc_duty:([^ ]*)', qa)
    if m:
        schedule_slug = m.group(1)
        url = ABC_API_URL + '/duty/on_duty/?' + urllib.urlencode({
            'service__slug': 'direct',
            'schedule__slug': schedule_slug,
            })
        resp = requests.get(url, headers=OAUTH_HEADER, timeout=5)
        resp.raise_for_status()
        resp = resp.json()
        return random.choice([e['person']['login'] for e in resp])
    exit("can't parse QA-engineer '%s', stop" % qa)


def read_abc_oauth(token_file):
    global OAUTH_HEADER
    try:
        with open(os.path.expanduser(token_file), 'r') as fh:
            OAUTH_HEADER = {'Authorization': 'OAuth ' + fh.read().strip()}
    except:
        sys.exit("can't read oauth token (file %s), stop" % token_file
                + "\nget new token: https://oauth.yandex-team.ru/authorize?response_type=token&client_id=23db397a10ae4fbcb1a7ab5896dc00f6"
                + "\ndocs: https://wiki.yandex-team.ru/intranet/abc/api/#authentication"
                )


def run():
    ZK_HOSTS = ['ppctest-zookeeper01i.sas.yp-c.yandex.net:2181', 'ppctest-zookeeper01f.myt.yp-c.yandex.net:2181', 'ppctest-zookeeper01v.vla.yp-c.yandex.net:2181']
    zkh = KazooClient(','.join(ZK_HOSTS))
    zkh.start()

    opts = parse_options()
    zk_path_schedule = "/direct/autorelease-ctl/%s/schedule" % opts.app
    zk_path_cmd = "/direct/autorelease-ctl/%s/cmd" % opts.app
    zk_path_current = "/direct/autorelease-ctl/%s/current" % opts.app
    print "%s\n%s\n%s\n\n" % (ZK_HOSTS, zk_path_schedule, zk_path_cmd)
    now = datetime.datetime.now()

    schedule_json, stat = zkh.get(zk_path_schedule)
    schedule_json = schedule_json or "{}"
    schedule = json.loads(schedule_json)

    cmd_json, stat = zkh.get(zk_path_cmd)
    cmd_json = cmd_json or "{}"
    cmd = json.loads(cmd_json)

    last_try_json, stat = zkh.get(zk_path_current)
    last_try_json = last_try_json or "{}"

    if opts.cmd not in ['show', 'clear-cmd']:
        read_abc_oauth( opts.token_file )

    if opts.cmd == "show":
        print "schedule: %s\ncmd:      %s\nlast try: %s" % (schedule_json, cmd_json, last_try_json)
    elif opts.cmd == "check":
        ok_s = ok_schedule(schedule, now)
        insistence = 1

        cmd_result = check_cmd(cmd, now)

        release_qa = None
        ok = False
        if cmd_result == 'force':
            ok = True
            # если делаем форсированную сборку, то QA пересчитываем с учетом параметров force
            release_qa = get_release_qa_login(schedule, cmd)
            if 'insistence' in cmd:
                insistence = cmd['insistence']
        elif cmd_result == 'pause':
            ok = False
        else:
            ok = ok_s
            # по умолчанию QA берем только из регулярного расписания
            release_qa = get_release_qa_login(schedule, {})

        if not ok:
            print "schedule DOESN'T ALLOW to create release for app %s\nwould-be release_qa: %s" % (opts.app, release_qa)
            exit(1)
        print "it's time to create release for app '%s'\nrelease_qa: %s\ninsistence: %s" % (opts.app, release_qa, insistence)
    elif opts.cmd == "force":
        # если записана какая-то команда -- не даем зафорсить сборку, пока команду явно не удалили
        cmd_result = check_cmd(cmd, now)
        if cmd_result != '':
            exit('active cmd found, stop\nuse show to view and clear-cmd to erase\n')

        opts.duration = 3 if not opts.duration else opts.duration
        opts.start_in = 0 if not opts.start_in else opts.start_in
        start_time = now + datetime.timedelta(hours=opts.start_in)
        end_time = start_time + datetime.timedelta(hours=opts.duration)
        cmd_data = {
                "cmd": "force",
                "insistence": opts.insistence,
                "cmd_time": now.strftime('%Y-%m-%d %H:%M:%S'),
                "start_time": start_time.strftime('%Y-%m-%d %H:%M:%S'),
                "end_time": end_time.strftime('%Y-%m-%d %H:%M:%S'),
                "notice_period_min": 0,
                }
        if opts.release_qa:
            cmd_data['QA-engineer'] = opts.release_qa
            release_qa = get_release_qa_login({}, cmd_data)
        elif 'QA-engineer' not in schedule:
            exit('no QA-engineer found in schedule, please specify one')
        cmd_data_json = json.dumps(cmd_data, sort_keys=True)
        zk_path = "/direct/autorelease-ctl/%s/cmd" % opts.app
        zkh.set(zk_path, cmd_data_json)
        print cmd_data_json
    elif opts.cmd == "pause":
        # если записана какая-то команда -- не даем зафорсить сборку, пока команду явно не удалили
        cmd_result = check_cmd(cmd, now)
        if cmd_result != '':
            exit('active cmd "%s" found, stop\nuse show to view and clear-cmd to erase\n' % cmd_result)

        opts.duration = 3 if not opts.duration else opts.duration
        opts.start_in = 0 if not opts.start_in else opts.start_in
        start_time = now + datetime.timedelta(hours=opts.start_in)
        end_time = start_time + datetime.timedelta(hours=opts.duration)
        cmd_data = {
                "cmd": "pause",
                "cmd_time": now.strftime('%Y-%m-%d %H:%M:%S'),
                "start_time": start_time.strftime('%Y-%m-%d %H:%M:%S'),
                "end_time": end_time.strftime('%Y-%m-%d %H:%M:%S'),
                "notice_period_min": 0,
                }
        cmd_data_json = json.dumps(cmd_data, sort_keys=True)
        zk_path = "/direct/autorelease-ctl/%s/cmd" % opts.app
        zkh.set(zk_path, cmd_data_json)
        print cmd_data_json
    elif opts.cmd == "clear-cmd":
        cmd_data = {}
        cmd_data_json = json.dumps(cmd_data, sort_keys=True)
        zk_path = "/direct/autorelease-ctl/%s/cmd" % opts.app
        zkh.set(zk_path, cmd_data_json)
        print cmd_data_json
    elif opts.cmd == "clear-last-try":
        zk_path = "/direct/autorelease-ctl/%s/current" % opts.app
        zkh.set(zk_path, "")
    elif opts.cmd == "schedule":
        if opts.schedule_days:
            days = [ int(d) for d in opts.schedule_days.split(',') ]
            schedule['days'] = days
        if not 'days' in schedule:
            schedule['days'] = [1,2,3,4,5]
        if opts.schedule_hours:
            blocks = opts.schedule_hours.split(',')
            hours = [ b.split('-') for b in blocks ]
            schedule['hours'] = hours

        if not 'hours' in schedule:
            schedule['hours'] = [ [ '18:00', '23:00' ] ]
        if opts.release_qa:
            schedule['QA-engineer'] = opts.release_qa
        if not 'QA-engineer' in schedule:
            exit('QA-engineer is required for schedule, stop\nuse --qa (staff:...|abc_duty:...)')
        release_qa = get_release_qa_login(schedule, {})
        if not 'notice_period_min' in schedule:
            schedule['notice_period_min'] = 0
        schedule_json = json.dumps(schedule, sort_keys=True)
        zk_path = "/direct/autorelease-ctl/%s/schedule" % opts.app
        zkh.set(zk_path, schedule_json)
        print schedule_json
    elif opts.cmd == "clear-schedule":
        zk_path = "/direct/autorelease-ctl/%s/schedule" % opts.app
        zkh.set(zk_path, "")
    else:
        exit("unknown cmd '%s'" % opts.cmd)

    zkh.stop()

    exit(0)

if __name__ == '__main__':
    run()
