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

import urllib
import requests
import random
import time
import subprocess
import re
import json
import yaml
from startrek_client import Startrek
import sys
import os
import logging

import requests.packages.urllib3
requests.packages.urllib3.disable_warnings()

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

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)

logging.basicConfig(stream=sys.stderr, level=logging.INFO, format='[%(asctime)s]\t%(message)s')

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

if 'SPECIAL_REVIEW_TESTING' in os.environ:
    STARTREK_TOKEN_FILE = os.environ['HOME'] + '/.startrek/token'
else:
    STARTREK_TOKEN_FILE = '/etc/direct-tokens/startrek'

if 'SPECIAL_REVIEW_TESTING' in os.environ:
    ARCANUM_TOKEN_FILE = os.environ['HOME'] + '/.arcanum/token'
else:
    ARCANUM_TOKEN_FILE = '/etc/direct-tokens/ppc-arc-oauth'

APPS_CONF_FILE = "/etc/yandex-direct/direct-apps.conf.yaml"

if 'SPECIAL_REVIEW_TESTING' in os.environ:
    SPECIAL_REVIEW_CONF_JSON = '../etc/special-review/special-review.conf.test.json'
else:
    SPECIAL_REVIEW_CONF_JSON = '/etc/special-review/special-review.conf.json'

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

arcanum_token = open(ARCANUM_TOKEN_FILE).readline().rstrip()
ARCANUM_PCI_DSS_API_URL = 'https://a.yandex-team.ru/api/v1/pci-dss'

PCI_DSS_REVIEW_DOC_LINK = 'https://docs.yandex-team.ru/direct-dev/concepts/dev/pci-dss'

apps_dict = yaml.load(open(APPS_CONF_FILE))['apps']
component2app = {apps_dict[app]['tracker-component']: app for app in apps_dict if 'tracker-component' in apps_dict[app]}


def get_app_name(ticket):
    global component2app

    app = None
    for component in ticket.components:
        if component.name in component2app:
            app = component2app[component.name]
            break

    return app


def get_start_end_rev(desc):
    revisions = re.findall(ur'^r([0-9]+) \|', desc, re.M | re.S)
    if revisions:
        revisions = [int(rev) for rev in revisions]
        return min(revisions), max(revisions)

    return 0, 0


def get_commits_from_description(desc):
    commits_from_description = re.findall(ur'^##([a-z1-9]+)##', desc, re.M | re.S)
    return commits_from_description


def my_check_output(cmd):
    for i in xrange(3):
        try:
            output = subprocess.check_output(cmd)
            return output
        except:
            time.sleep(2)

    return u""


def is_content_lines_in_svn_file(contentLines, end_rev, changed_file):
    proc = subprocess.Popen(["svn",
                             "cat",
                             "-r",
                             "r%d" % end_rev, changed_file], stdout=subprocess.PIPE)
    for line in proc.stdout:
        decoded_line = line.decode('utf-8')
        for content in contentLines:
            if content in decoded_line:
                proc.stdout.close()
                proc.wait()
                return True
    proc.wait()
    return False


def get_tickets(svn_output):
    m = re.search(r'\n(DIRECT-[0-9]+):', svn_output)
    return m.group(1) if m else ""


def get_arcanum_pci_express_commits_page(commit_from, commit_to, config):
    params = {
        'from': commit_from,
        'to':  commit_to,
        'page_size': 10,
        'fields': ','.join(('commit_ids', 'next_commit_id')),
        'git_order': 'true',
    }
    pci_express_component = config['pci_express_component_id']

    resp = requests.get(
        ARCANUM_PCI_DSS_API_URL + '/components/{}/commit-ids-page?{}'.format(
            urllib.quote(pci_express_component), urllib.urlencode(params)),
        headers={"Authorization": "OAuth {}".format(arcanum_token)})

    resp = resp.json()

    return resp['data']['commit_ids'], resp['data'].get('next_commit_id')


def get_arcanum_pci_express_commits_info(commits_ids, config):
    params = {
        'commit_ids': ','.join(commits_ids),
        'fields': ','.join(('id', 'state')),
    }
    pci_express_component = config['pci_express_component_id']

    resp = requests.get(
        ARCANUM_PCI_DSS_API_URL + '/components/{}/commits?{}'.format(
            urllib.quote(pci_express_component), urllib.urlencode(params)),
        headers={"Authorization": "OAuth {}".format(arcanum_token)})

    return resp.json()['data']


def get_arcanum_pci_express_commits_state(commit_from, commit_to, config):
    while True:
        commits_ids, commit_to = get_arcanum_pci_express_commits_page(
            commit_from, commit_to, config)

        logging.info('Checking pci express state from commit {} to commit {}'.format(commit_from, commit_to))

        commits = get_arcanum_pci_express_commits_info(commits_ids, config)
        for commit in commits:
            yield commit['id'], commit['state']

        if not commit_to:
            break


def get_pci_express_not_reviewed_commits_to_diff(pci_express_check_from, pci_express_check_to, config):
    logging.info('Checking pci express state from commit {} to commit {}'.format(pci_express_check_from, pci_express_check_to))

    to_diff = []

    for commit, status in get_arcanum_pci_express_commits_state(pci_express_check_from, pci_express_check_to, config):
        if status not in ('shipped', 'approved'):
            to_diff.append((commit, status))

    logging.info('Not reviewed commits diff {}'.format(to_diff))

    return to_diff


def make_pci_express_url(commit_from, commit_to, config):
    pci_express_component = config['pci_express_component_id']
    pci_express_abc_service = config['pci_express_abc_service']

    params = {
        'componentId': pci_express_component,
        'from': commit_from,
        'to': commit_to,
        'pageSize': 100,
        'status': 'all',
    }

    return 'https://a.yandex-team.ru/projects/{}/pci-dss/commits?{}'.format(pci_express_abc_service, urllib.urlencode(params))


def get_changed_files_to_diff(start_rev, end_rev, config):
    logging.info('Checking changed files from revision {} to revision {}'.format(start_rev, end_rev))

    target_files = set(
        [config['svn_url'] + (f[1:] if f[0] == '/' else f) for f in config['files']])

    changed_files = re.findall(ur'svn\+ssh.+', my_check_output(["svn", "diff", "-r", "r%d:r%d" % (start_rev - 1, end_rev), "--summarize", config['svn_url']]), re.M)

    to_diff = []
    for changed_file in changed_files:
        if changed_file in target_files:
            to_diff.append(changed_file)

    if "contentLines" in config:
        for changed_file in changed_files:
            if is_content_lines_in_svn_file(config['contentLines'], end_rev, changed_file):
                to_diff.append(changed_file)

    logging.info('Changed files diff: {}'.format(to_diff))

    return to_diff

def check_single_rule(issue, rule_name, config):
    app = get_app_name(issue)

    start_rev, end_rev = get_start_end_rev(issue.description)

    logging.info('Start revision r{}; end revision r{}'.format(start_rev, end_rev))

    if config['svn_url'][-1] != u'/':
        config['svn_url'] += u'/'

    diff_issue_description = ''

    if 'pci_express' in config and config['pci_express']:
        if start_rev and end_rev:
            pci_express_check_from = 'r{}'.format(start_rev)
            pci_express_check_to = 'r{}'.format(end_rev)
        else:
            commits_from_description = get_commits_from_description(issue.description)
            
            if not commits_from_description:
                return

            pci_express_check_from = commits_from_description[-1]
            pci_express_check_to = commits_from_description[0]

        to_diff = get_pci_express_not_reviewed_commits_to_diff(pci_express_check_from, pci_express_check_to, config)

        if not to_diff:
            return

        pci_express_url = make_pci_express_url(start_rev, end_rev, config)

        diff_issue_description = """В релизе {} присутствуют [коммиты]({}) которые не прошли PCI DSS код-ревью.
        Если коммиты находятся в статусе %%not reviewed%% проведите ревью согласно [документации]({}).
        
        Если коммиты находятся в статусе %%unknown%%, это означает что для них проверка PCI Express ещё не была запущена,
        в таком случае следует подождать выполнения проверки на стороне PCI Express (это может занять несколько часов).
        После окончания проверок проведите код-ревью коммитов в статусе %%not reviewed%% и закройте эту задачу.
        Если после окончания проверок [страница]({}) пустая закройте эту задачу.
        """.format(issue.key, pci_express_url, PCI_DSS_REVIEW_DOC_LINK, pci_express_url)

        logging.info('Found not reviewed commits and may create issue')
        logging.info('Issue description {}'.format(diff_issue_description))

        # TODO: remove after DIRECT-173154
        return
    elif config['files']:
        if not start_rev or not end_rev:
            return

        to_diff = get_changed_files_to_diff(start_rev, end_rev, config)
        if not to_diff:
            return

        diff = my_check_output(
            ["svn", "diff", "-r", "r%d:r%d" % (start_rev - 1, end_rev)] + to_diff)
        if not diff:
            return

        diff_issue_description = u"В релизе %s поменялись файлы из списка '%s':\n%s\n%%%%(diff)%s%%%%\n%s" % (
            issue.key, rule_name, u"\n".join(to_diff), diff.decode('utf-8'), SIGN)

    extra_params = {}

    if "tag" not in config:
        config["tag"] = u"direct_special_review_%s" % app

    for param in [('tag', 'tags'),
                  ('component', 'components'),
                  ('followers', 'followers')]:
        if param[0] in config:
            extra_params[param[1]] = config[param[0]]

    if "assignee" in config:
        extra_params["assignee"] = config["assignee"][random.randint(
            0, len(config["assignee"]) - 1)]
    else:
        extra_params["assignee"] = issue.assignee

    diff_issue = startrek_client.issues.create(
        queue=config['queue'],
        summary=u'%s: cпециальное ревью %s' % (app, rule_name),
        type={'name': 'Task'},
        description=diff_issue_description,
        **extra_params
    )

    if 'list_source_tickets' in config:
        related_tickets = set()
        for diff_element in to_diff:
            related_tickets.add(get_tickets(my_check_output(["svn",
                                                             "log",
                                                             "-v",
                                                             "-r",
                                                             "r%d:r%d" % (
                                                                 start_rev - 1, end_rev),
                                                             diff_element]).decode('utf-8')))

        links = ['https://st.yandex-team.ru/%s' %
                 ticket_key for ticket_key in related_tickets]
        diff_issue \
            .comments \
            .create(text=u'Тикеты из-за которых произошло это изменение:\n\n %s \n\n %s' % ('\n'.join(links), SIGN))

    startrek_client.issues[issue.key].comments.create(
        text=u"создан тикет на дополнительное ревью: %s\n%s" % (diff_issue.key, SIGN))


def run():
    script_tag = 'special_review_checked'

    with open(SPECIAL_REVIEW_CONF_JSON, "r") as config_file:
        config = json.load(config_file)
        for rule in config:
            logging.info('Start to check rule {}'.format(rule))

            st_query = u'Queue: %s Type: Release Status: !Closed Tags: !"%s" "Sort by": key desc' % \
                       (config[rule]['queue'], script_tag)
            issues = startrek_client.issues.find(st_query)

            issue_keys = ', '.join([issue.key for issue in issues])
            logging.info('Found startrek issues to check {}'.format(issue_keys))

            for issue in issues:
                logging.info('Checking issue {}'.format(issue.key))

                app = get_app_name(issue)
                if "app" in config[rule] and config[rule]['app'] == app:
                    logging.info('Checking rule {} for app {} [issue {}]'.format(rule, app, issue.key))

                    check_single_rule(issue, rule, config[rule])
                startrek_client.issues[issue.key].update(
                    tags=startrek_client.issues[issue.key].tags + [script_tag])

    return

if __name__ == '__main__':
    run()
