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

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

from startrek_client import Startrek
import argparse
import json
import re
import subprocess
import logging
import urllib3
import datetime
import dateutil.parser
from collections import Counter
from dateutil import tz
import json
import urlparse

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

STARTREK_TOKEN_FILE = '/etc/direct-tokens/startrek'

ST_QUERY = 'Queue: DIRECT Type: Release Components: "Releases: Direct" Status: !Closed "Sort by": key desc'

HOSTNAME = subprocess.check_output(['hostname', '-f']).strip()
SCRIPT_NAME = os.path.basename(__file__)
SIGN = u"----\nСкрипт %s с машины %s" % (SCRIPT_NAME, HOSTNAME)

startrek_token = open(STARTREK_TOKEN_FILE).readline().strip()
startrek_client = Startrek(token=startrek_token, useragent=SCRIPT_NAME)

regression_match_table = [
    {'summary': ur'веб-интерфейс',
     'profile_names': ['web'],
    },
    {'summary': ur'cmd',
     'profile_names': ['cmd'],
    },
    {'summary': ur'API:',
     'profile_names': ['api'],
    },
    {'summary': ur'INTAPI,',
     'profile_names': ['intapi', 'transport'],
    },
]

class InvalidHowToTestValueError(Exception):
    pass

# Попытаться получить тег запуска в Акве, отыскав в тикете нужное поле или ссылку на запуск.
# При поиске ссылки признаёт комментарии только от робота direct-handles, чтобы труднее было сбить её с толку.
# Если тег записывать в тикет вместе с комментарием (со стороны запускающей джобы), то искать его в комментариях не понадобится. Но там нужно разбираться.
# Для хранения тега спользуется поле howToTest ("Как тестировать"), чтобы не создавать новое, пока непонятно, где его лучше хранить.
# Можно передать set_if_not_in_ticket=True, чтобы после обнаружения в комментариях прописать тег в тикет.
def get_launch_tag(ticket_key, set_if_not_in_ticket=False):
    ticket_obj = startrek_client.issues[ticket_key]
    how_to_test_field_value = ticket_obj.howToTest
    expected_value_prefix = 'aqua_tag:'
    tag = None
    if how_to_test_field_value is not None:
        if how_to_test_field_value.startswith(expected_value_prefix):
            tag = how_to_test_field_value[len(expected_value_prefix):]
        else:
            raise InvalidHowToTestValueError(how_to_test_field_value)
    if tag is None:
        for comment in startrek_client.issues[ticket_key].comments.get_all():
            m = re.search(r'^https://aqua.yandex-team.ru/#(/launches-tag\?\S+)', comment.text, re.MULTILINE)
            if True or (m and comment.createdBy.login == 'direct-handles'):
                # парсим "относительный url", который на самом деле является фрагментом, т. к. идёт после #
                aqua_url = m.group(1)
                qs = urlparse.urlparse(aqua_url)[4]
                tag = urlparse.parse_qs(qs).get('tag')[0]
                if set_if_not_in_ticket:
                    ticket_obj.update(howToTest=expected_value_prefix + tag)
                break
    return tag


def get_regression_tickets(tickets=None):
    result = []

    for ticket in tickets:
        comments = startrek_client.issues[ticket.key].comments.get_all()
        for comment in comments:
            if re.search(ur'Созданы тикеты на регрессию:', comment.text, re.U):
                reg_tickets = re.findall(ur'(?:DIRECT|TESTIRT)-[0-9]+', comment.text)
                result.extend(list(filter(lambda x: startrek_client.issues[x].status.key != 'closed', reg_tickets)))

    return list(set(result))


def get_statistics(tag=None):
    if tag is None:
        # этой ветке кода возможно более правильная замена - брать тег из зукипера
        tag = subprocess.check_output(['grep', '-oP', "direct-release[a-z0-9-]+", '/opt/release-stat-restart/direct_restart_stat_state']).strip()
    return json.loads(subprocess.check_output(['python', '/usr/local/bin/restart.py', '--tag', tag, '-cs']))


def check_finished(ticket, stats, profile_names):
    finished = True
    for stat in stats:
        if stat['profile_name'] in profile_names and not stat['is_passed']:
            finished = False

    return finished


def utc_to_local(utc_dt):
    return utc_dt.replace(tzinfo=tz.gettz('UTC')).astimezone(tz.tzlocal())


# захардкоженная константа для проверки состояния после N часов
n_hours = 8

def check_after_n_hours(ticket, stats, profile_names):
    created_at = dateutil.parser.parse(startrek_client.issues[ticket].createdAt)
    return (datetime.datetime.now(tz.tzlocal()) - utc_to_local(created_at)).total_seconds() / 60 / 60 >= n_hours


def check_almost_done(ticket, stats, profile_names):
    total = 0
    finished = 0

    for stat in stats:
        if stat['profile_name'] in profile_names:
            if 'total' in stat:
                total += stat['total']

            if 'FINISHED' in stat:
                finished += stat['FINISHED']

    return total > 0 and total - finished < 10 and total - finished > 0

def check_restarter_stopped(ticket, stats, profile_names):
    launch_tag = get_launch_tag(ticket, set_if_not_in_ticket=True)
    restarter_cmd_base = ['python', '/usr/local/bin/restart.py', '--tag', launch_tag, '--conf', '/etc/release-stat-restart/direct_config.yaml']
    show_profiles_output = subprocess.check_output(restarter_cmd_base + ['--show-profiles'])
    packs_for_profile = {}
    current_profile = ''
    for line in show_profiles_output.rstrip().split('\n'):
        if not line.startswith('\t'):
            current_profile = line.strip().rstrip(':')
            packs_for_profile.setdefault(current_profile, [])
        else:
            pack = line.lstrip()
            packs_for_profile[current_profile].append(pack)
    stopped_profiles_cnt = 0
    for profile in profile_names:
        packs_str = ','.join(packs_for_profile[profile])
        # TODO в restart.py возможность задавать список паков просто именем профиля
        exit_code = subprocess.call(restarter_cmd_base + ['--only-check-if-stopped', '--packs', packs_str])
        if exit_code == 0:
            stopped_profiles_cnt += 1
    return stopped_profiles_cnt == len(profile_names)

def get_str_stats(stats, profile_names):
    return json.dumps(list(filter(lambda stat: stat['profile_name'] in profile_names, stats)), indent=2, sort_keys=True)


checks = [
    {'tag': u'regression_done',
     'comment': u'По статистике от скрипта restart.py, успешно прошло 100%% тестов',
     'cmd': check_finished},
    {'tag': u'regression_Nh',
     'comment': u'Прошло %d часов с создания тикета' % n_hours,
     'cmd': check_after_n_hours},
    {'tag': u'regression_almost_done',
     'comment': u'По статистике от скрипта restart.py тесты почти прошли!',
     'cmd': check_almost_done},
    {'tag': u'restarter_stopped',
     'comment': u'Скрипт restart.py закончил перезапускать тесты, можно разбирать результаты',
     'cmd': check_restarter_stopped},
]

def run():
    parser = argparse.ArgumentParser()
    parser.add_argument('--run-restarter-with-config', help=u'запустить перезапускалку с указанным конфигом, не писать ничего в тикеты')
    args = parser.parse_args()

    releases = startrek_client.issues.find(ST_QUERY)

    reg_tickets = get_regression_tickets(releases)

    if not reg_tickets:
        exit(0)

    tags = Counter()
    for ticket in reg_tickets:
        tag = get_launch_tag(ticket)
        tags[tag] += 1
    # берем самый часто встречающийся тег
    tag = tags.most_common(1)[0][0]

    if args.run_restarter_with_config:
        config_path = args.run_restarter_with_config
        os.execvp('python', ['python', '/usr/local/bin/restart.py', '--conf', config_path, '--tag', tag])

    stats = get_statistics(tag)

    for reg_ticket in reg_tickets:
        for reg in regression_match_table:
            if re.search(reg['summary'], startrek_client.issues[reg_ticket].summary, re.I | re.U):
                for check in checks:
                    if check['tag'] not in startrek_client.issues[reg_ticket].tags and check['cmd'](reg_ticket, stats, reg['profile_names']):
                        startrek_client.issues[reg_ticket].update(tags=startrek_client.issues[reg_ticket].tags + [check['tag']])
                        startrek_client.issues[reg_ticket].comments.create(text=check['comment'] + u"\nТекущая статистика:\n" + get_str_stats(stats, reg['profile_names']) + u"\n" + SIGN)

                break
    
    return


if __name__ == '__main__':
    run()

