# -*- coding: utf-8 -*-
from datetime import datetime, timedelta
from io import StringIO
import logging
from os import remove
from os.path import dirname, join
import pandas as pd
from startrek_client import Startrek
from time import sleep
import traceback as tb
import yaml
from yt import wrapper as yt
from yql.api.v1.client import YqlClient
from yql.client.operation import YqlOperationShareIdRequest

from errs import TaskError, EmptyResultError, YQLError
from presentation.ppt_maker import make_ppt
from robot import PATTERN, SECRET, SURVEY_URL, TABLE_PATH, QUERY_TEMPLATE
from staff_worker import get_staff_data
from utils import get_ordered_set, normalize_login, replace_clids

logger = logging.getLogger(__name__)


class YQLWorker(object):
    def __init__(self, test_mode=None, issue=None):
        super(YQLWorker, self).__init__()
        logger.info('{}: init YQLWorker'.format(issue.key))

        self.issue = issue
        self.test_mode = test_mode

        self._parse_issue_description()
        self.query_params = {
            'login': self.issue.createdBy.login,
            'issue': self.issue.key,
            'client_logins': u'\n'.join(self.client),
            'client_type': self.client_type,
            'comp_logins': u'\n'.join(self.competitors),
            'comp_type': self.comp_type,
            'campaigns': self.campaings,
            'date': self.date,
            'time_detail': self.time_detail
        }
        self.query = QUERY_TEMPLATE.format(**self.query_params)
        self.request, self.result = None, None

        self._validate_task()

        self.df, self.camp_df = None, None
        self.xlsx_path, self.pptx_path = None, None
        self.lost_logins = []

        for i in range(4):
            try:
                issue = self.get_issue()
                issue.update(numberCategories=len(self.client) + len(self.competitors))
                break

            except Exception as exc:
                if getattr(exc, 'reason') == 'Conflict':
                    sleep(10)
                else:
                    raise exc

        self._set_created_by()

        logger.info('{}: YQLWorker initialized'.format(issue.key))

    def _parse_issue_description(self):
        logger.info('{}: parse issue description'.format(self.issue.key))

        description = PATTERN.match(self.issue.description)
        if description is None:
            msg = '{}: issue doesn\'t match the mask'.format(self.issue.key)
            logger.error(msg)
            self.stop_progress('need_info', comment={
                'text': u'С заданием что-то не так.\n'
                        u'Пожалуйста, убедитесь, что отчет заказан через (({} эту форму)).'.format(SURVEY_URL),
                'summonees': [self.issue.createdBy.login, 'vbatraev']
            })
            raise TaskError(msg)

        description = description.groupdict()
        ppt_options = description.get('ppt_options', {})
        if ppt_options:
            ppt_options = yaml.safe_load(ppt_options)

        # self.report_type = get_report_type(ppt_options.get('report_type'))
        self.time_detail = get_time_detail(ppt_options.get('time_detail'))
        self.campaings = True if ppt_options.get('campaings') in (u'Да', u'Yes') else False

        self.client_type = get_client_type(ppt_options.get('client_type'))
        client = [login.strip() for login in description.get('client', '').split('\n') if login]

        self.comp_type = get_client_type(ppt_options.get('comp_type'))
        competitors = [login.strip() for login in description.get('competitors', '').split('\n') if login]
        self.client, self.competitors = get_ordered_set(client), get_ordered_set(competitors)

        if self.client_type == 'Login':
            self.client = [normalize_login(login) for login in self.client]
        if self.comp_type == 'Login':
            self.competitors = [normalize_login(login) for login in self.competitors]

        self.date = self._get_date(ppt_options)
        lang = ppt_options.get('lang', u'русский')
        lang = lang if lang in [u'русский', u'английский'] else u'русский'
        self.lang = 'ru' if lang == u'русский' else 'en'

        self.ad_opt_table, self.camp_opt_table = prepare_options(ppt_options)

    def _get_date(self, ppt_options):
        date = ppt_options.get('date')
        date = None if date in [None, 'None', ''] else date

        yt_client = yt.YtClient(token=SECRET['token'], proxy='hahn')
        dates = [datetime.strptime(tab, '%Y-%m-%d') for tab in yt_client.list(TABLE_PATH[self.time_detail])
                 if datetime.strptime(tab, '%Y-%m-%d') >= datetime(2018, 8, 3)]
        first_date, last_date = min(dates), max(dates)

        if not date:
            return last_date.strftime('%Y-%m-%d')

        else:
            if self.time_detail == 'weekly':
                date = date - timedelta(days=date.weekday())

            date = str(date)

            if datetime.strptime(date, '%Y-%m-%d') not in dates:
                logger.warning('{}: unavailable date'.format(self.issue.key))
                if not self.test_mode:
                    self.issue.comments.create(text=u'Выбранная дата недоступна, отчет будет построен за {:%Y-%m-%d}.'
                                               .format(last_date))
                return last_date.strftime('%Y-%m-%d')

            else:
                return date

    def _validate_task(self):
        logger.info('{}: validate task'.format(self.issue.key))

        if not self.client and not self.competitors:
            msg = '{}: client and competitors are None'.format(self.issue.key)
            logger.error(msg)
            self.stop_progress('close', resolution='won\'tFix', comment={
                'text': u'Ни клиент, ни конкуренты не заданы, построить отчет невозможно.\n'
                        u'Пожалуйста, закажите отчет заново (({} тут)) '
                        u'и не забудьте заполнить хотя бы одно из полей.'.format(SURVEY_URL),
                'summonees': [self.issue.createdBy.login]
            })
            raise TaskError(msg)

        intersections = list(set(self.client) & set(self.competitors))
        if len(intersections) != 0:
            msg = '{}: intersections in client and competitors'.format(self.issue.key)
            logger.error(msg)
            self.stop_progress('need_info', comment={
                'text': u'В задании найдены пересечения клиента и конкурентов:\n!!{}!!\n\nПожалуйста, '
                        u'уберите пересечения из одного из полей в теле тикета и переведите его в статус "Открыт" '
                        u'или закажите отчет заново (({} тут)).'.format('\n'.join(intersections), SURVEY_URL),
                'summonees': [self.issue.createdBy.login]
            })
            raise TaskError(msg)

        logger.info('{}: task is validated'.format(self.issue.key))

    def _set_created_by(self):
        created_by = self.issue.createdBy
        self.created_by = get_staff_data(created_by.login, self.lang)

    def run(self):
        logger.info('{}: run YQLWorker'.format(self.issue.key))

        yql_client = YqlClient(token=SECRET['token'])

        request = yql_client.query(self.query, syntax_version=1)
        request.run()
        self.request = request
        self.result = request.get_results()

        if not self.result.is_success:
            msg = '{}: result isn\'t success!'.format(self.issue.key)
            logger.error(msg)
            self.stop_progress('need_info', comment={
                'text': u'((%s Запрос)) завершился ошибкой.\n<{Подробнее:\n%%%%\n%s\n%%%%}>' % (
                    self.get_public_url(), u'\n'.join([err.format_issue() for err in self.result.errors])
                ),
                'summonees': ['vbatraev']
            })
            raise YQLError(msg)

        columns = read_columns()['data_name'].tolist()

        yt_client = yt.YtClient(token=SECRET['token'], proxy='hahn')
        table = yt_client.read_table('//tmp/robot-topview/%s/total' % self.issue.key,
                                     raw=True,
                                     format=yt.SchemafulDsvFormat(
                                         columns=columns,
                                         attributes={'missing_value_mode': 'print_sentinel',
                                                     'missing_value_sentinel': 'null'})
                                     )
        self.df = pd.read_table(StringIO('\n'.join(table).decode('utf-8')), names=columns, encoding='utf-8',
                                decimal='.', na_values=('Null', 'null', 'NULL'), na_filter=True,
                                quoting=3, low_memory=True, engine='c')
        logger.info('{}: request completed'.format(self.issue.key))

        if self.df.empty:
            msg = '{}: result is empty'.format(self.issue.key)
            logger.error(msg)

            if not self.test_mode and 'Empty' not in self.issue.tags:
                issue = self.get_issue()
                issue.update(tags=self.issue.tags + ['Empty'])

            self.stop_progress('close', resolution='fixed', comment={
                'text': u'По выбранным клиентам нет статистики за {}.\nПопробуйте выбрать другую дату отчета в '
                        u'расширенных настройках (({} формы)).'.format(self.date, SURVEY_URL),
                'summonees': ['vbatraev', self.issue.createdBy.login]
            })
            raise EmptyResultError(msg)

        else:
            if self.campaings:
                camp_columns = read_columns('campaign_columns')['data_name'].tolist()
                table = yt_client.read_table('//tmp/robot-topview/%s/campaigns' % self.issue.key,
                                             raw=True,
                                             format=yt.SchemafulDsvFormat(
                                                 columns=camp_columns,
                                                 attributes={'missing_value_mode': 'print_sentinel',
                                                             'missing_value_sentinel': 'null'})
                                             )
                self.camp_df = pd.read_table(StringIO('\n'.join(table).decode('utf-8')), names=camp_columns,
                                             encoding='utf-8', decimal='.', na_values=('Null', 'null', 'NULL'),
                                             na_filter=True, quoting=3, low_memory=True, engine='c')

            if self.client_type == 'Login' or self.comp_type == 'Login':
                self._replace_clids()
            self._prepare_dataframe()

    def _replace_clids(self):
        logger.debug('{}: replace clids in client and competitors'.format(self.issue.key))

        clids = self.df[['Login', 'ClientID']].set_index('ClientID').to_dict(orient='index')
        self.client = replace_clids(self.client, clids)
        self.competitors = replace_clids(self.competitors, clids)

        logger.debug('{}: clids replaced'.format(self.issue.key))

    def _prepare_dataframe(self):
        logger.info('{}: prepare result'.format(self.issue.key))

        df = self.df
        df = df.replace('999', pd.np.nan).ix[:, df.columns != 'data'].set_index('Login').T.fillna('')
        self.ad_opt_table = self.ad_opt_table.join(df, how='left')
        self.camp_opt_table = self.camp_opt_table.join(df, how='left')

        res_columns = read_columns().set_index('data_name')
        res_columns = {k: v.get('table_name_%s' % self.lang, k)
                       for k, v in res_columns.to_dict(orient='index').iteritems()}
        df = df.reset_index().replace(res_columns)
        df.columns = ['Login'] + list(df.columns[1:].get_values())
        if self.lang == 'en':
            df = df.replace(to_replace=[u'Да', u'да'], value=u'yes')
            df = df.replace(to_replace=[u'Нет', u'нет'], value=u'no')
        self.df = df

        self._prepare_ppt_tables()
        self._check_logins()

        if self.campaings:
            self._prepare_camp_df()

        self._make_xlsx()

    def _prepare_camp_df(self):
        columns = read_columns('campaign_columns').set_index('data_name')
        columns = {k: v.get('table_name_%s' % self.lang, k)
                   for k, v in columns.to_dict(orient='index').iteritems()}

        self.camp_df = self.camp_df.rename(columns=columns)

    def _prepare_ppt_tables(self):
        logger.info('{}: prepare ppt tables'.format(self.issue.key))

        client_logins = [login for login in self.client if login in self.df.columns]
        competitors = [comp for comp in self.competitors if comp in self.df.columns]

        if self.comp_type == 'Login':
            login = u'Логин' if self.lang == 'ru' else 'Login'
        elif self.comp_type == 'Brand':
            login = u'Бренд' if self.lang == 'ru' else 'Brand'
        else:
            login = u'Домен' if self.lang == 'ru' else 'Domain'

        competitors = [
            (comp, u'{} {}'.format(login, c_num)) for c_num, comp in enumerate(competitors, 1)
        ]
        logins = client_logins + [comp[1] for comp in competitors]

        self.ad_opt_table = self.ad_opt_table.rename(
            columns=dict(competitors)).loc[:, [u'ppt_name_%s' % self.lang] + logins[:11]]
        self.ad_opt_table = self.ad_opt_table.rename(columns={'ppt_name_%s' % self.lang: 'ppt_name'})

        self.camp_opt_table = self.camp_opt_table.rename(
            columns=dict(competitors)).loc[:, [u'ppt_name_%s' % self.lang] + logins[:11]]
        self.camp_opt_table = self.camp_opt_table.rename(columns={'ppt_name_%s' % self.lang: 'ppt_name'})

    def _check_logins(self):
        logger.info('{}: check logins'.format(self.issue.key))
        lost_logins = []

        for login in self.client + self.competitors:
            if login not in self.df.columns:
                logger.warning(u'{}: {} not in data'.format(self.issue.key, login))
                lost_logins.append(login)

        self.lost_logins = lost_logins

    def _make_xlsx(self):
        logger.info('{}: make xlsx'.format(self.issue.key))

        xlsx_path = join(dirname(dirname(__file__)), 'Login_{}_{}.xlsx'.format(self.issue.key, self.lang))
        writer = pd.ExcelWriter(xlsx_path)
        self.df.to_excel(writer, sheet_name='total_%s' % self.date, index=False, encoding='utf-8')
        if self.campaings:
            self.camp_df.to_excel(writer, sheet_name='campaigns_%s' % self.date, index=False, encoding='utf-8')

        writer.save()

        self.xlsx_path = xlsx_path

    def paste_result(self):
        logger.info('{}: paste result'.format(self.issue.key))

        if self.lost_logins:
            lost_logins = [u'* {}'.format(login) for login in self.lost_logins]
            comment_text = '\n'.join([
                u'<{Нет данных по некоторым клиентам из задания:\n%s\n}>' % '\n'.join(lost_logins),
                u'Возможно, нет статистики за %s или клиенты указаны некорректно.\n' % self.date,
                u'Выгрузка и презентация по настройкам в Директе готовы.'
            ])
        else:
            comment_text = u'Выгрузка и презентация по настройкам в Директе готовы.'

        self.stop_progress('close', resolution='fixed', comment={
            'text': comment_text, 'attachments': [self.xlsx_path, self.pptx_path],
            'summonees': [self.issue.createdBy.login]
        })

        if not self.test_mode:
            for path in [self.xlsx_path, self.pptx_path]:
                remove(path)

    def get_issue(self):
        st_client = Startrek(useragent='', token=SECRET['token'])
        issue = st_client.issues[self.issue.key]
        return issue

    def stop_progress(self, transition='stop_progress', resolution=None, comment=None):
        logger.info('%s: stop_progress (transition %s)', self.issue.key, transition)
        if not self.test_mode:
            if comment:
                for i in range(21):
                    try:
                        logger.info(', '.join(comment.get('attachments', [])))
                        self.issue.comments.create(
                            text=comment.get('text', ''), attachments=comment.get('attachments', []),
                            summonees=comment.get('summonees', []))
                        break
                    except Exception as exc:
                        if i == 20:
                            raise exc
                        sleep(10)

            for i in range(4):
                try:
                    issue = self.get_issue()
                    if resolution:
                        issue.transitions[transition].execute(resolution=resolution)
                    else:
                        issue.transitions[transition].execute()
                    break

                except Exception as exc:
                    if getattr(exc, 'reason') == 'Conflict':
                        sleep(10)
                    else:
                        raise exc

    def get_public_url(self):
        share_req = YqlOperationShareIdRequest(self.result.operation_id)
        share_req.run()
        return u'https://yql.yandex-team.ru/Operations/{}'.format(share_req.json)


def yql_worker(kwargs):
    test_mode, issue = kwargs.get('test_mode'), kwargs.get('issue')
    try:
        worker = YQLWorker(test_mode, issue)

        worker.run()
        worker.pptx_path = make_ppt(worker)
        worker.paste_result()

    except TaskError:
        return issue

    except (YQLError, EmptyResultError):
        pass

    except Exception:
        logger.exception('{}: Oops!'.format(issue.key))

        if not test_mode:
            issue.comments.create(text='Что-то сломалось...<{Подробнее:'
                                       '\n%%%%\n%s\n%%%%'
                                       '}>' % tb.format_exc(),
                                  summonees=['vbatraev'])
            issue.transitions['need_info'].execute()

    return issue


def prepare_options(ppt_options):
    columns_df = read_columns()

    result = {}
    for opt_name in ['ad_options', 'campaign_options']:
        options = ppt_options.get(opt_name)
        options = None if options in [None, 'None', ''] else options
        if options:
            options = [opt.strip() for opt in options.split(',')]

        if options:
            if u'Все' in options:
                df = columns_df[columns_df['type'].isin([opt_name])]
            else:
                df = columns_df[columns_df['form_name'].isin(options) & columns_df['type'].isin([opt_name])]

        else:
            df = columns_df[columns_df['default'] == 1 & columns_df['type'].isin([opt_name])]

        result[opt_name] = df.set_index('data_name')

    return result['ad_options'], result['campaign_options']


def read_columns(sheet_name=None):
    path = join(dirname(__file__), 'columns.xlsx')

    df = pd.DataFrame()
    if not sheet_name:
        for sheet_name in ['ad_options', 'campaign_options', 'ya_columns']:
            table_df = pd.read_excel(path, sheetname=sheet_name)
            table_df['type'] = sheet_name
            df = pd.concat([df, table_df])

    else:
        table_df = pd.read_excel(path, sheetname=sheet_name)
        table_df['type'] = sheet_name
        df = pd.concat([df, table_df])

    return df


def get_client_type(client_type):
    if not client_type:
        client_type = u'логины/ClientID'

    if client_type == u'логины/ClientID':
        return 'Login'
    elif client_type == u'бренды':
        return 'Brand'
    elif client_type == u'домены':
        return 'Domain'


def get_time_detail(time_detail):
    if not time_detail:
        time_detail = u'недели'

    if time_detail == u'дни':
        return 'daily'
    elif time_detail == u'недели':
        return 'weekly'
