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

"""
Репортит расхождения в структура большой БД и Песочницы 
Делает тикеты в Трекере и события в juggler. 

Куда будет писать -- регулируется опицями-флагами --tracker и --juggler
Может работать с продакшеном и с тестовой БД, регулируется опцией --env {ts|pr}

Внутри запускает db-structure-diff 

Посмотреть расхождения: 
Продакшен
db-structure-diff pr:ppc:1 pr:ppc:1
db-structure-diff pr:ppcdict pr:ppcdict

ТС
db-structure-diff ts:ppc:1 ts:ppc:1
db-structure-diff ts:ppcdict ts:ppcdict
"""

import sys
import os

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

import yaml
import json
import re
import subprocess
import argparse
import direct_juggler.juggler as dj

from requests import Request

pathlist = ["/usr/local/sbin", "/usr/local/bin", "/usr/sbin", "/usr/bin", "/sbin", "/bin", "/usr/games", "/usr/local/games"]
os.environ["PATH"] = os.pathsep.join(pathlist)

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

STARTREK_TOKEN_FILE = '/etc/direct-tokens/startrek'
SCRIPT_TAG_FAIL = u'db_structure_diff_fail'
SCRIPT_TAG_OK = u'db_structure_diff_ok'
ST_QUERY_RELEASES = u'Queue: Директ Type: Release Components: "Releases: Direct" Status: testing "Sort by": key desc'

def compose_juggler_description(status, env):
    descr_parts = [ status ]
    descr_parts.append( "env = %s" % env )
    descr_parts.append("cmd:notify-db-structure-diff")
    if status == "OK":
        descr_parts.append(u"Основная БД и Песочница консистентны")
    else:
        descr_parts.append(u"Основная БД и Песочница расходятся")
        tickets_url = Request('GET', 'https://st.yandex-team.ru/filters/filter', params={"status": "open", "tags":CONFIG[env]['tracker_tag']}).prepare().url
        descr_parts.append(u"тикеты: %s" % tickets_url)
    descr = "; ".join(descr_parts) 
    return descr

# TODO: 
# + поменять tag на tracker_tag, 
# + разделить заголовок тикета и descr события, 
# + в описание события внести ссылку на выборку тикетов, 
# + добавить отсылку события в конце скрипта ("working"), сделать тикет на заведение агрегатов
# научить db-structure-diff сравнивать mysql с файлами или файлы с файлами 
# отдельно мониторить факт расхождения, отдельно наличие незакрытых тиктетов
# + починить сценарий: если закрыть тикет про расхождения, то скрипт создаст новый, но комментарий про него не напишет
CONFIG = {
             'ts': {
                       'queue': 'DIRECT',
                       'summary': 'Устранить различия схем таблиц ТС и песочницы ТС',
                       'juggler_service': 'sandbox_db_consistency_ts',
                       'juggler_description': compose_juggler_description,
                       'tracker_tag': 'sandbox_db_consistency_ts_direct',
                       'cmds': [['db-structure-diff', 'ts:ppc:1', 'tsb:ppc:1'], ['db-structure-diff', 'ts:ppcdict', 'tsb:ppcdict']],
                   },
             'pr': {
                       'queue': 'DIRECTADMIN',
                       'summary': 'Устранить различия схем таблиц большого продакшена и продакшен-Песочницы',
                       'juggler_service': 'sandbox_db_consistency_prod',
                       'juggler_description': compose_juggler_description,
                       'tracker_tag': 'sandbox_db_consistency_pr_direct',
                       'cmds': [['db-structure-diff', 'pr:ppc:1', 'sb:ppc:1'], ['db-structure-diff', 'pr:ppcdict', 'sb:ppcdict']],
                   }
         }


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


def parse_options():
    global state_storage_type
    parser = argparse.ArgumentParser(description="Оповещает о различиях в схемах таблиц директа")
    parser.add_argument("-st", "--tracker", dest="tracker", help="создать тикет в стартреке", action='store_true')
    parser.add_argument("-e", "--env", dest="env", help="ts или pr", type=str)
    parser.add_argument("-j", "--juggler", dest="juggler", help="отправить в juggler результат сверки", action='store_true')
    opts, extra = parser.parse_known_args()

    if len(extra) > 0:
        die("присутствуют лишние параметры")

    if not opts.env or opts.env not in CONFIG:
        die("укажите тип окружения: ts или pr")

    return opts

def prev_tickets_query(env):
    query = u'Queue: "%s" Tags: %s Status: !Closed "Sort By": Created DESC"' % (CONFIG[env]['queue'], CONFIG[env]['tracker_tag'])
    return query

def working_ok(env):
    # получается, что это событие отсылаем не глядя на опцию juggler. Кажется, не старшно
    dj.queue_events([dict(service="script.notify-db-structure-diff.env-%s.working" % env, status="OK", description="OK; env=%s; cmd:notify-db-structure-diff" % env)])
    return

def run():
    opts = parse_options()

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

    release_issues = []

    if opts.env == 'ts':
        release_issues = startrek_client.issues.find(ST_QUERY_RELEASES)

    diff = u''

    # Получаем дифф и форматируем его для Трекера
    for cmd in CONFIG[opts.env]['cmds']:
        try:
            subprocess.check_output(cmd)
        except subprocess.CalledProcessError as e:
            if e.returncode == 2:
                diff += u'__**%s**__\n<{ Показать diff\n%%%%(diff)%s%%%%\n}>\n\n' % (' '.join(cmd), e.output)

    # Отправляем в juggler событие "консистентность Песочницы ОК/не ОК"
    if opts.juggler:
        juggler_status = 'CRIT'
        if diff:
            juggler_status = 'CRIT'
        else:
            juggler_status = 'OK'

        juggler_description = CONFIG[opts.env]['juggler_description'](status = juggler_status, env=opts.env)
        juggler_service = CONFIG[opts.env]['juggler_service']
        dj.queue_events([dict(service=juggler_service, status=juggler_status, description=juggler_description)])

    # Если работаем с ТС, расхождений нет и об этом еще нет отметки в тикете -- пишем в тикет тег + комментарий
    if not diff:
        print "no diff found"
        if opts.tracker and opts.env == 'ts':
            for ticket in release_issues:
                tags = startrek_client.issues[ticket.key].tags
                if SCRIPT_TAG_FAIL in tags:
                    tags.remove(SCRIPT_TAG_FAIL)
                if SCRIPT_TAG_OK not in tags:
                    tags.append(SCRIPT_TAG_OK)
                    startrek_client.issues[ticket.key].comments.create(text=u"Различий в схемах таблиц ТС и песочницы ТС не найдено\n%s" % SIGN)
                startrek_client.issues[ticket.key].update(tags=tags)

        working_ok(opts.env)
        return

    # Дальше обрабатываем случай "расхождения есть"
    # Создаем если надо новый тикет про расхождение, на ТС пишем об этом в релиз 
    if opts.tracker:
        last_issues = startrek_client.issues.find(prev_tickets_query(opts.env))

        last_issue = None
        if last_issues:
            try:
                last_issue = next(iter(last_issues))
            except StopIteration:
                pass

        description_to_be = diff + u"\n%s" % SIGN
        
        current_issue = None
        new_issue_created = False
        if last_issue and opts.env == 'ts':
            # Для ТС проверять совпадение diff и обновлять описание не хотим. Есть открытый тикет == есть проблема, люди разбираются. 
            # Если захочется новый тикет на новое состояние, то можно закрыть тикет про расхождение и скрипт создаст новый
            current_issue = last_issue
            print "found issue: %s" % (current_issue.key)
        elif last_issue: 
            # продакшен: записываем актуальный дифф в найденный тикет
            current_issue = last_issue
            print "found issue: %s" % (current_issue.key)
            if last_issue.description != description_to_be:
                last_issue.update(description=description_to_be)
                last_issue.comments.create(text=u'Дифф обновлен\n%s' % (SIGN))
                print "updated issue: %s" % (current_issue.key)
        else:
            # ТС, прод: расхождение есть, тикета нет. Создаем
            current_issue = startrek_client.issues.create(
                queue=CONFIG[opts.env]['queue'],
                summary=CONFIG[opts.env]['summary'],
                type={'name': 'Task'},
                description=description_to_be,
                tags=[CONFIG[opts.env]['tracker_tag']],
                components='db',
            )
            new_issue_created = True
            print "created issue: %s" % (current_issue.key)

        if opts.env == 'ts':
            for ticket in release_issues:
                tags = startrek_client.issues[ticket.key].tags

                if SCRIPT_TAG_OK in tags:
                    tags.remove(SCRIPT_TAG_OK)
                if SCRIPT_TAG_FAIL not in tags:
                    tags.append(SCRIPT_TAG_FAIL)
                startrek_client.issues[ticket.key].update(tags=tags)
                if new_issue_created:
                    startrek_client.issues[ticket.key].comments.create(text=u'Найдены различия в схемах таблиц ТС и песочницы ТС\n%s\n%s' % (current_issue.key, SIGN))

    working_ok(opts.env)
    return


if __name__ == '__main__':
    run()
