# -*- coding: utf8 -*-
import logging
from time import time
from os import mkdir, rename
from os.path import basename, dirname, exists, join
from shutil import rmtree
from threading import Thread
from subprocess import CalledProcessError, check_output
import requests
import yaml
from startrek_client import Startrek

from robot import ticket_pattern_new, ticket_pattern_old, secret
from errs import TaTaskError
from utils import proceed_form_value, get_cs_src, get_src_cur, normalize_login


logger = logging.getLogger(__name__)


ME_TEXT = u'\n'.join([
    u'{where}: слишком много данных, для сборки отчетов выделенного времени не хватило.',
    u'',
    u'Пожалуйста, запустите расчет техноаудита локально или упростите задание:',
    u'1. Сократить количество заказанных дней',
    u'2. Использовать пресет **mks_light** (ограниченный набор вкладок)',
    u'3. Использовать опцию **Отдельный отчет на каждый логин**',
    u'4. Создать отдельную задачу на каждый тип отчета',
    u'5. Использовать опцию **Дополнительно ограничить кампаниями**'
])

SQ_TEXT = u'\n'.join([
    u'Не удалось построить отчет по поисковым запросам - слишком мало данных:',
    u'1. Попробуйте увеличить период выгрузки',
    u'2. Добавьте больше кампаний/логинов в выгрузку',
    u'',
    u''
])


class TaTask(object):
    def __init__(self, test_mode=None, issue=None):
        super(TaTask, self).__init__()
        logger.info('%s: init TaTask', issue.key)
        if not test_mode:
            issue.transitions['start_progress'].execute()

        self.test_mode, self.issue = test_mode, issue
        self._parse_issue_description()
        self._make_work_dirs()

        logger.info('%s: TaTask initialized', issue.key)

    def _parse_issue_description(self):
        logger.info('%s: parse issue description', self.issue.key)

        description = ticket_pattern_new.match(self.issue.description)
        if not description:
            description = ticket_pattern_old.match(self.issue.description)
        if not description:
            if not self.test_mode:
                self.issue.comments.create(text=u'С заданием в теле тикета что-то не так...',
                                           summonees=['vbatraev'])
                self.issue.transitions['need_info'].execute()
            raise TaTaskError('%s: bad issue description')

        description = description.groupdict()
        params = description.get('params', {})
        params = yaml.safe_load(params) if params else params

        self.logins = [normalize_login(login) for login in description.get('logins', '').split('\n') if login.strip()]
        split_logins = proceed_form_value(params.get('split_logins', u'Нет'))
        self.split_logins = True if split_logins in (u'Да', u'Yes') else False

        pptx = proceed_form_value(params.get('pptx', u'Нет'))
        pptx = True if pptx in (u'Да', u'Yes') else False

        self.ta_options = {'presets': proceed_form_value(params.get('presets')),
                           'language': proceed_form_value(params.get('language')),
                           'pptx': pptx}

        logger.info('{}: split_logins {}'.format(self.issue.key, self.split_logins))

    def _make_work_dirs(self):
        logger.info('%s: make work dirs', self.issue.key)
        self.dir, self.work_dirs = join(dirname(dirname(__file__)), self.issue.key), []

        try:
            mkdir(self.dir)
        except OSError:
            pass
        except Exception as exc:
            raise exc

        cs_src_list = get_cs_src(self)
        self._validate_src(cs_src_list)

        for src in cs_src_list:
            src_url, src_name = src
            logger.info('%s: loading %s' % (self.issue.key, src_name))
            request = requests.get(src_url)

            if self.split_logins:
                login = src_name.split('_')[1]
                src_dir = join(self.dir, login)
            else:
                src_dir = self.dir

            try:
                mkdir(src_dir)
            except OSError:
                pass
            except Exception as exc:
                raise exc
            with open(join(src_dir, src_name), 'wb') as fd:
                fd.write(request.content)

            if self.split_logins:
                self.work_dirs += [src_dir]

        if not self.work_dirs:
            self.work_dirs = [self.dir]

    def rm_dir(self):
        rmtree(self.dir)

    def _validate_src(self, cs_src_list):
        logger.info('%s: validate src', self.issue.key)

        if len(cs_src_list) == 0:
            if not self.test_mode:
                self.issue.comments.create(text=u'Отчеты по подсветке не найдены.',
                                           summonees=['vbatraev'])
                self.issue.transitions['need_info'].execute()
            raise TaTaskError('%s: empty sources' % self.issue.key)

        src_names = [src[1] for src in cs_src_list]
        check_src_list = [src for src in src_names if ('.tar.gz' in src and len(src.split('_')) >= 3)]
        if len(src_names) != len(check_src_list):
            if not self.test_mode:
                self.issue.comments.create(
                    text=u'Названия отчетов по подсветке не соответствуют маске '
                         u'<login>_<currency>_<date1>-<date2>.tar.gz.',
                    summonees=['vbatraev']
                )
                self.issue.transitions['need_info'].execute()

            raise TaTaskError('%s: sources don\'t match the mask' % self.issue.key)

        logins = [src.split('_')[1] for src in src_names]
        lost_logins = set(self.logins) - set(logins)
        if lost_logins:
            if not self.test_mode:
                lost_logins = [u'* {}'.format(login) for login in lost_logins]
                self.issue.comments.create(
                    text=u'Отчеты по некоторым логинам из задания не найдены:\n{}'.format(u'\n'.join(lost_logins)),
                    summonees=['vbatraev']
                )
                self.issue.transitions['need_info'].execute()

            raise TaTaskError('%s: lost logins' % self.issue.key)

        curs = [get_src_cur(src) for src in src_names]
        curs = [cur for cur in curs if cur != 'Empty']
        if not self.split_logins and len(set(curs)) > 1:
            if not self.test_mode:
                self.issue.comments.create(
                    text=u'В логинах используется разная валюта ({}).\n'
                         u'Пожалуйста, запустите расчет техноаудита локально '
                         u'с указанием коэффициентов конвертирования.'.format(u', '.join(set(curs))),
                    summonees=[self.issue.createdBy]
                )
                self.issue.transitions['close'].execute(resolution='willNotFix')

            raise TaTaskError('%s: different currencies' % self.issue.key)

        logger.info('%s: cs result validated', self.issue.key)

    def calculate_ta(self):
        logger.info('%s: calculate ta', self.issue.key)
        if not self.test_mode:
            self.issue.comments.create(text=u'Отчет по техноаудиту строится.')

        reports_done, long_time_reports = 0, 0
        reports_failed = []
        for wd in self.work_dirs:
            ta_thread = TaThread(self.issue, wd, self.ta_options)

            ta_thread.start()
            start_time = time()
            while ta_thread.is_alive() and (time() - start_time) < 15 * 60:
                ta_thread.join(timeout=60)
                logger.info('%s: %s seconds timeout' % (self.issue.key, 60))
            if not ta_thread.result:
                ta_thread.stop()

            if ta_thread.result['status'] == 'OK':
                if self.split_logins:
                    reports_path = ta_thread.result['attachments']
                    reports_path = reports_path.replace(basename(reports_path), '%s_reports.zip' % basename(wd))
                    rename(ta_thread.result['attachments'], reports_path)
                    ta_thread.result['attachments'] = reports_path

                if not self.test_mode:
                    comment = u'<{Вывод и отчеты техноаудита:\n%s\n}>' % ta_thread.result['output'].decode('utf-8')
                    if 'there is not enough data to make a forecast' in comment:
                        comment = SQ_TEXT + comment

                    self.issue.comments.create(text=comment,
                                               attachments=[ta_thread.result['attachments']])
                reports_done += 1

            else:
                if self.split_logins:
                    reports_failed += [basename(wd)]
                if ta_thread.result.get('tags'):  # Тег добавляется только для кейса со временем ожидания
                    long_time_reports += 1
                    new_tags = [tag for tag in ta_thread.result['tags'] if tag not in self.issue.tags]
                    st_client = Startrek(useragent='', token=secret['Token'])
                    st_client.issues[self.issue.key].update(tags=self.issue.tags + new_tags)
                    summonees = [self.issue.createdBy.login]

                else:
                    summonees = ['vbatraev']

                self.issue.comments.create(text=ta_thread.result['output'],
                                           attachments=ta_thread.result['attachments'],
                                           summonees=summonees)

        if not self.test_mode:
            text = [u'{} отчетов построено из {}.'.format(reports_done, len(self.work_dirs))]
            if reports_failed:
                reports_failed = [u'* {}'.format(login) for login in reports_failed]
                text += [u'Не удалось построить для:\n{}'.format(u'\n'.join(reports_failed))]

            self.issue.comments.create(text=u'\n'.join(text),
                                       summonees=[self.issue.createdBy])

            if reports_done != len(self.work_dirs) and long_time_reports != len(self.work_dirs):
                self.issue.transitions['need_info'].execute()
                logger.info('%s: completed with errors', self.issue.key)

            else:
                self.issue.transitions['close'].execute(resolution='fixed')
                logger.info('%s: completed', self.issue.key)

        if not self.test_mode:
            self.rm_dir()


class TaThread(Thread):
    def __init__(self, issue=None, work_dir=None, ta_options=None):
        super(TaThread, self).__init__()
        self.issue, self.work_dir = issue, work_dir
        self.args = self._get_args(ta_options)

        self.go, self.result = True, {}

    def _get_args(self, ta_options):
        def_args = ['ta-report', '-w', self.work_dir, '-z', '-c']
        if not ta_options:
            return def_args

        if ta_options.get('presets'):
            def_args += ['--presets', ta_options['presets']]

        if ta_options.get('language'):
            def_args += ['--lang', ta_options['language']]

        if ta_options.get('pptx'):
            def_args += ['--pptx']

        return def_args

    def run(self):
        logger.info('%s: %s' % (self.issue.key, ' '.join(self.args)))
        while self.go:
            try:
                ta_output = check_output(self.args)
                self.result = {'status': 'OK',
                               'output': ta_output,
                               'attachments': join(self.work_dir, 'reports.zip')}
                self.go = False
                logger.info('{}: TaThred successfully completed in {}'.format(self.issue.key, basename(self.work_dir)))
                break

            except CalledProcessError as exc:
                logger.error('%s: TaThred error', self.issue.key)
                self.result = {
                    'status': 'NOT OK',
                    'attachments': [join(self.work_dir, 'stderr.txt')] if exists(join(self.work_dir, 'stderr.txt')) else [],
                }
                if exc.returncode == 1:
                    self.result['output'] = ME_TEXT.format(where=basename(self.work_dir))
                    self.result['tags'] = ['long_time']

                elif exc.returncode == 2 or exc.returncode == 3:
                    self.result['output'] = u'С отчетами по подсветке что-то не так. ' \
                                            u'<{Подробнее:\n%s\n}>' % exc.output.decode('utf-8')

                else:
                    self.result['output'] = u'При расчете техноаудита возникла ошибка. ' \
                                            u'<{Подробнее:\n%s\n}>' % exc.output.decode('utf-8')

                self.go = False
                break

    def stop(self):
        logger.info('%s: stop TaThread in %s' % (self.issue.key, self.work_dir))
        self.result = {
            'status': 'NOT OK',
            'attachments': [join(self.work_dir, 'stderr.txt')] if exists(join(self.work_dir, 'stderr.txt')) else [],
            'output': ME_TEXT.format(where=basename(self.work_dir)),
            'tags': ['long_time']
        }
        self.go = False
