# -*- coding: utf-8 -*-
from datetime import datetime
from dateutil.relativedelta import relativedelta
from itertools import product
import logging
from os import remove
from os.path import dirname, join
import pandas as pd
from startrek_client import Startrek
from xlrd import XLRDError
import yaml

from cpa_data_task import DATA_URL, PATTERN, PPTX_URL
from make_data.load_data import load_cpa_data
from make_data.make_xlsx import make_data_xlsx
from robot import SECRET
from robot.errs import TaskError
from xlsx_task_worker import parse_rules, prepare_task_df
from utils import (
    bad_value,
    get_attribution_prefix,
    get_cur_coef,
    get_date_func,
    get_list_from_tf,
    get_period,
    get_task_type,
    proceed_form_value
)

logger = logging.getLogger(__name__)


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

        self.test_mode, self.issue = test_mode, issue
        self.comment = []
        self._parse_issue_description()

        self.data_included, self.result_df = None, None
        logger.info('%s: CPATask initialized', issue.key)

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

    def break_task(self):
        if not self.test_mode and self.get_issue_status() == 'closed':
            logger.warning('%s: issue was closed', self.issue.key)
            self.issue.comments.create(text=u'Задача была закрыта, выгрузка будет неполной.')
            return True
        else:
            return False

    def _parse_issue_description(self):
        logger.info('%s: parse issue description', 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)
            if not self.test_mode:
                self.issue.comments.create(text=u'С заданием что-то не так.',
                                           summonees=[self.issue.createdBy.login])
                self.issue.transitions['need_info'].execute()
            raise TaskError(msg)

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

        self.type_ = get_task_type(proceed_form_value(params.get('type')))
        if self.type_ is None:
            msg = '{}: unknown task type'.format(self.issue.key)
            logger.error(msg)
            if not self.test_mode:
                self.issue.comments.create(text=u'Неизвестный тип задания.\nПопробуйте заказать выгрузку '
                                                u'заново по (({} ссылке)).'.format(DATA_URL),
                                           summonees=[self.issue.createdBy.login])
                self.issue.transitions['need_info'].execute()
            raise TaskError(msg)

        self.categorization = {
            'use_domain_sets':
                True if proceed_form_value(params.get('use_domain_sets'), u'Нет') in (u'Да', u'Yes') else False,
            'use_mediagroups':
                True if proceed_form_value(params.get('use_mediagroups'), u'Нет') in (u'Да', u'Yes') else False
        }
        self.domain_filters = True \
            if proceed_form_value(params.get('domain_filters'), u'Нет') in (u'Да', u'Yes') else False
        self.calls = True \
            if proceed_form_value(params.get('calls'), u'Нет') in (u'Да', u'Yes') else False
        self.separate_data = True \
            if proceed_form_value(params.get('separate_data'), u'Нет') in (u'Да', u'Yes') else False
        self.replace_categories = True if proceed_form_value(
            params.get('replace_categories'), u'Нет') in (u'Да', u'Yes') else False

        self.time_detail = proceed_form_value(params.get('time_detail'), u'Месяцы')
        self.date_func = get_date_func(self.time_detail)
        self.period = get_period(self.time_detail, proceed_form_value(params.get('period')))

        self.currency = proceed_form_value(params.get('currency'), u'рубль')
        self.vat = True if proceed_form_value(params.get('vat'), u'Нет') in (u'Да', u'Yes') else False
        self.cur_coef = get_cur_coef(self.currency)

        if self.type_ == 'yt_registry':
            self._make_clients_df(
                get_list_from_tf(description.get('clients', '')),
                get_list_from_tf(description.get('categories', ''))
            )
        else:
            self._load_xlsx_task()

        self.regions = get_list_from_tf(description.get('regions', ''))
        self._validate()
        self.task_df, self.rules = prepare_task_df(self, self.task_df), parse_rules(self.rules)

        self.attribution, self.attr_prefix = get_attribution_prefix(
            proceed_form_value(params.get('attribution'), u'Первый переход')
        )

        if max(1, len(self.regions)) * len(self.task_df.index) > 300:
            msg = '%s: too many rows' % self.issue.key
            logger.error(msg)
            if not self.test_mode:
                self.issue.comments.create(text=u'Задание на выгрузку слишком большое.\n'
                                                u'Количество строк в задании * количество регионов должно быть <= 300.',
                                           summonees=[self.issue.createdBy.login])
                self.issue.transitions['close'].execute(resolution='won\'tFix')
            raise TaskError(msg)

    def _load_xlsx_task(self):
        attachments = [
            file_ for file_ in self.issue.attachments.get_all()
            if file_.name.endswith('.xlsx') and self.issue.key not in file_.name
        ]
        if not attachments:
            msg = '%s: xlsx data not found' % self.issue.key
            logger.error(msg)
            if not self.test_mode:
                self.issue.comments.create(text=u'Задание на выгрузку не найдено.\n'
                                                u'Пожалуйста, прикрепите заполненный xlsx-шаблон к тикету и '
                                                u'переоткройте его.',
                                           summonees=[self.issue.createdBy.login])
                self.issue.transitions['need_info'].execute()
            raise TaskError(msg)

        else:
            attachment = attachments.pop()
            logger.debug(u'{}: loading {}'.format(self.issue.key, attachment.name))

        attachment.download_to(dirname(dirname(__file__)))
        task_path = join(dirname(dirname(__file__)), attachment.name)

        try:
            task_df = pd.read_excel(task_path, sheetname='Task', usecols=range(0, 6),
                                    encoding='utf-8').dropna(how='all').fillna('')
            task_df.columns = ['Client', 'Category', 'CounterID', 'GoalID', 'ClientID', 'CampaignID']

        except XLRDError:
            msg = u'{}: Task sheet not found in {}'.format(self.issue.key, attachment.name)
            logger.error(msg)
            if not self.test_mode:
                self.issue.comments.create(text=u'В {} не найден лист Task.\n'
                                                u'Пожалуйста, прикрепите заполненный xlsx-шаблон к тикету и '
                                                u'переоткройте его.'.format(attachment.name),
                                           summonees=[self.issue.createdBy.login])
                self.issue.transitions['need_info'].execute()
            raise TaskError(msg)

        try:
            if self.type_ == 'rules':
                rules_df = pd.read_excel(task_path, sheetname='Rules', usecols=range(0, 3),
                                         encoding='utf-8').dropna(how='all').fillna('')
                rules_df.columns = ['Client', 'Rule', 'Category']
            else:
                rules_df = pd.DataFrame()

        except XLRDError:
            rules_df = pd.DataFrame()

        remove(task_path)
        self.task_df, self.rules = task_df, rules_df

    def _make_clients_df(self, clients, categories):
        task_df = pd.DataFrame(columns=['Client', 'Category', 'CounterID', 'GoalID', 'ClientID', 'CampaignID'])
        for client, category in product(clients, categories):
            task_df = pd.concat([task_df, pd.DataFrame({'Client': [client], 'Category': [category]})])

        self.task_df, self.rules = task_df.reset_index(drop=True).fillna(''), pd.DataFrame()

    def _validate(self):
        logger.info('%s: validate task', self.issue.key)

        invalid_regions = [region for region in self.regions if bad_value(region)]
        if invalid_regions:
            logger.error('%s: invalid chars in regions', self.issue.key)
            invalid_regions = u'* в регионах присутствуют недопустимые символы (разрешены цифры, запятые и пробелы)'

        last_date = datetime.strptime(self.period['last_date'], '%Y-%m-%d')
        if last_date >= datetime.today():
            logger.warning('%s: unavailable last_date' % self.issue.key)
            self.comment += [u'Для выбранной временной детализации указан неполный период.\n']

            if self.time_detail == u'Дни':
                last_date -= relativedelta(days=1)
            elif self.time_detail == u'Недели':
                last_date -= relativedelta(days=7)
            elif self.time_detail == u'Месяцы':
                last_date -= relativedelta(months=1)
            self.period['last_date'] = last_date.strftime('%Y-%m-%d')

        invalid_period = []
        if last_date <= datetime.strptime(self.period['first_date'], '%Y-%m-%d'):
            logger.error('%s: first_date later or equal last_date' % self.issue.key)
            invalid_period = u'* дата начала !!{}!! периода позже или равна дате окончания !!{}!!'.format(
                self.period['first_date'], self.period['last_date'])

        invalid_rows = []
        invalid_rows_bd = []
        if self.type_ in ['standard', 'rules']:
            for idx, row in self.task_df.iterrows():
                if self.type_ == 'standard':
                    if not row['Client'] or not row['CounterID']:
                        invalid_rows.append(idx + 1)
                    if not self.domain_filters and not (row['ClientID'] or row['CampaignID']):
                        invalid_rows_bd.append(idx + 1)

                else:
                    if not row['Client'] or not row['CounterID'] or not (row['ClientID'] or row['CampaignID']):
                        invalid_rows.append(idx + 1)

            if invalid_rows:
                invalid_rows = [str(row) for row in invalid_rows]
                if self.type_ == 'standard':
                    logger.error('%s: missed Client or CounterID', self.issue.key)
                    invalid_rows = u'* в некоторых строках задания не указан Бренд/Домен и/или CounterID ' \
                                   u'(номера строк с ошибками - {})'.format(u', '.join(invalid_rows))
                else:
                    logger.error('%s: missed Client or CounterID or ClientID/CampaignID', self.issue.key)
                    invalid_rows = u'* в некоторых строках задания не указан Бренд/Домен и/или CounterID ' \
                                   u'и/или ClientID/CampaignID ' \
                                   u'(номера строк с ошибками - {})'.format(u', '.join(invalid_rows))
            if invalid_rows_bd:
                invalid_rows_bd = [str(row) for row in invalid_rows_bd]
                logger.error('%s: missed ClientID/CampaignID without domain_filters', self.issue.key)
                invalid_rows_bd = u'* в некоторых строках задания не указан ни ClientID, ни CampaignID. ' \
                                  u'Укажите ClientID и/или CampaignID или используйте опцию ' \
                                  u'**Фильтровать данные Директа доменами** ' \
                                  u'(номера строк с ошибками - {})'.format(u', '.join(invalid_rows_bd))

        invalid_columns = []
        if self.type_ in ['standard', 'rules']:
            for col_name in ['CounterID', 'GoalID', 'ClientID', 'CampaignID']:
                if sum(self.task_df[col_name].apply(lambda value: bad_value(value))) != 0:
                    invalid_columns.append(col_name)
            if invalid_columns:
                logger.error('%s: invalid chars in task_df', self.issue.key)
                invalid_columns = u'* в колонках {} присутствуют недопустимые символы ' \
                                  u'(разрешены цифры, запятые и пробелы)'.format(u', '.join(invalid_columns))

        errs = [err for err in [invalid_regions, invalid_period, invalid_rows, invalid_rows_bd, invalid_columns] if err]
        if errs:
            msg = '%s: task isn\'t validated' % self.issue.key
            logger.error(msg)
            if not self.test_mode:
                errs.insert(0, u'В задании найдены ошибки:')
                self.issue.comments.create(text=u'\n'.join(errs), summonees=[self.issue.createdBy.login])
                self.issue.transitions['need_info'].execute()

            raise TaskError(msg)

        else:
            logger.info('%s: task validated' % self.issue.key)

    def load_data(self):
        if not self.test_mode:
            self.issue.comments.create(text=u'Выгрузка CPA готовится.')

        logger.info('%s: loading data', self.issue.key)
        self.data_included, self.result_df = load_cpa_data(self)

    def paste_result(self):
        if not self.result_df.empty:
            logger.info('%s: make xlsx', self.issue.key)
            result_path = make_data_xlsx(self)
            if not self.test_mode:
                logger.info('%s: paste result', self.issue.key)

                text = u'\n'.join([
                    row for row in self.comment + [u'Выгрузка CPA готова.'] +
                    [u'Заказать презентацию на основе выгрузки можно в April по (({} ссылке)).'.format(PPTX_URL)] if row
                ])
                self.issue.comments.create(
                    text=text, attachments=[result_path], summonees=[self.issue.createdBy.login])
                remove(result_path)

                if self.get_issue_status() != 'closed':
                    self.issue.transitions['close'].execute(resolution='fixed')

        else:
            logger.info('%s: empty result', self.issue.key)
            if not self.test_mode:
                text = u'\n'.join([row for row in self.comment + [u'Нет статистики по указанным параметрам.'] if row])
                self.issue.comments.create(text=text, summonees=[self.issue.createdBy.login])
                if self.get_issue_status() != 'closed':
                    self.issue.transitions['close'].execute(resolution='fixed')

        logger.info('%s: completed', self.issue.key)
