# -*- coding: utf-8 -*-
from datetime import datetime as dt
import logging
from os import remove
from os.path import basename, dirname, join
import pandas as pd
from time import sleep
from xlrd import XLRDError
import yaml

from data_cooker import prepare_data_df
from exceller import ExcelDocument
from make_requests import get_region_names, get_staff_data
from report_maker.calculations.utils import filter_by_date, prepare_category_df
from robot import FORM_URL
from templates import PATTERN, read_yml_config
from utils import (
    convert_name,
    equal_periods,
    get_competitors,
    get_datetime_period,
    overlapping_periods,
    proceed_form_value
)


logger = logging.getLogger(__name__)
DICTS = read_yml_config('dicts.yml')


class CPAReportTask(object):
    def __init__(self, test_mode=None, type_=None, issue=None):
        super(CPAReportTask, self).__init__()
        logger.info('{}: init {} task'.format(issue.key, type_))
        if not test_mode:
            issue.transitions['start_progress'].execute()
        self.test_mode, self.type_, self.issue = test_mode, type_, issue
        self.config = read_yml_config('%s_config.yml' % self.type_)

        self._load_xlsx_data()
        self._parse_issue_description()
        self.created_by = get_staff_data(self.issue, self.lang)
        self.data_filters = {}

        self.comment = []
        self.few_competitors = []
        self.results = {}

    def _run_task_workers(self):
        logger.debug('%s: _get_specific_options', self.issue.key)
        self._get_specific_options()  # Определено внутри класса type_

        self.df = prepare_data_df(self)
        self._get_categories_by_regions()

    def _load_xlsx_data(self):
        attachment = [file_ for file_ in self.issue.attachments.get_all() if file_.name.endswith('.xlsx')]
        if not attachment:
            if not self.test_mode:
                self.issue.comments.create(text=u'Выгрузка CPA не найдена.Пожалуйста, прикрепите xlsx-файл к тикету.',
                                           summonees=[self.issue.createdBy.login])
                self.issue.transitions['need_info'].execute()
            raise TaskError('%s: xlsx data not found' % self.issue.key)

        else:
            attachment = attachment.pop()

        logger.debug(u'{}: load {}'.format(self.issue.key, attachment.name))
        attachment.download_to(dirname(dirname(__file__)))
        data_path = join(dirname(dirname(__file__)), attachment.name)

        self.counters_df = self._read_counters_df(data_path)

        data_wb = ExcelDocument(data_path)
        self.time_detail = data_wb['Info']['B20'].text
        self.attribution = data_wb['Info']['B31'].text
        self.currency, self.vat = data_wb['Info']['B36'].text, data_wb['Info']['B41'].text

        try:
            df = pd.read_excel(data_path, sheet_name='Data', encoding='utf-8').dropna(how='all')
        except XLRDError:
            if not self.test_mode:
                self.issue.comments.create(text=u'В {} не найден лист Data.\nПожалуйста, убедитесь, что к тикету '
                                                u'прикреплена актуальная выгрузка CPA.'.format(attachment.name),
                                           summonees=[self.issue.createdBy.login])
                self.issue.transitions['need_info'].execute()
            raise TaskError(u'{}: Task sheet not found in {}'.format(self.issue.key, attachment.name))

        remove(data_path)

        df = df.rename(columns={value: key for key, value in DICTS['column_names'].iteritems()})
        try:
            df['Date'] = df.Date.apply(
                lambda cell: dt.strptime(cell.strftime('%Y-%m-%d'), '%Y-%m-%d') if not isinstance(cell, (str, unicode))
                else dt.strptime(cell, '%Y-%m-%d'))
        except (AttributeError, ValueError):
            if not self.test_mode:
                self.issue.comments.create(
                    text=u'Проверьте формат дат на листе Data в прикрепленной выгрузке CPA.\n'
                         u'Если выгрузка собрана вручную, убедитесь, что в данных нет пустых строк, '
                         u'а формат дат соответствует %%YYYY-MM-DD%%.',
                    summonees=[self.issue.createdBy.login])
                self.issue.transitions['need_info'].execute()
                raise TaskError(u'{}: Bad date format in {}'.format(self.issue.key, basename(data_path)))

        except Exception as exc:
            raise exc

        self.df = df

    def _read_counters_df(self, data_path):
        try:
            df = pd.read_excel(data_path, sheet_name='Info', usecols=[12, 13, 14, 15, 18],
                               encoding='utf-8').dropna(how='all').fillna('')
        except XLRDError:
            if not self.test_mode:
                self.issue.comments.create(text=u'В {} не найден лист Info.\nПожалуйста, убедитесь, что к тикету '
                                                u'прикреплена актуальная выгрузка CPA.'.format(basename(data_path)),
                                           summonees=[self.issue.createdBy.login])
                self.issue.transitions['need_info'].execute()
            raise TaskError(u'{}: Task sheet not found in {}'.format(self.issue.key, basename(data_path)))

        df = df.rename(columns={u'Бренд/Домен': 'Client', u'Категория': 'Category', u'Гео': 'Regions'})
        df['Category'] = df['Category'].replace(to_replace='', value=u'Без учёта категоризации')
        df['Regions'] = df['Regions'].replace(to_replace='', value=u'Весь мир')
        for col_name in ['CounterID', 'GoalID', 'Regions']:
            df[col_name] = df[col_name].apply(
                lambda cell: str(int(cell)) if isinstance(cell, int) or isinstance(cell, float) else cell)

        return df

    def get_client_metrica(self, client, category=None):
        counters, goals = [], []
        for idx, row in self.counters_df.iterrows():
            if row['Client'] == client and (category is None or row['Category'] == category):
                counters.append(row['CounterID'])
                goals.append(row['GoalID'])

        counters = set(u','.join(counters).split(','))
        counters = [u'№{}'.format(counter.strip()) for counter in counters if counter.strip()]

        goals = set(u','.join(goals).split(','))
        goals = [u'№{}'.format(goal.strip()) for goal in goals if goal.strip()]

        return {'counters': u', '.join(counters), 'goals': u', '.join(goals)}

    def _parse_issue_description(self):
        logger.debug('%s: parse issue description', self.issue.key)
        description = PATTERN.match(self.issue.description)
        if not description:
            if not self.test_mode:
                self.issue.comments.create(text=u'С заданием что-то не так!',
                                           summonees=[self.issue.createdBy.login, 'leroy'])
                self.issue.transitions['need_info'].execute()

            raise TaskError('%s: issue doesn\'t match the mask' % self.issue.key)

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

        self.client = proceed_form_value(params.get('client'))
        self.competitors = get_competitors(
            proceed_form_value(description.get('competitors'))
        )

        self.target = proceed_form_value(params.get('target'))
        self.target = convert_name(self.target, self.config['Targets'])
        self.lang = 'ru' if proceed_form_value(params.get('lang'), default=u'русский') == u'русский' else 'en'

        self.format_charts = True if proceed_form_value(params.get('format_charts'), default=u'Нет') == u'Да' else False

        try:
            self.period = get_datetime_period(self.time_detail, params.get('period'))
            self.periods = {
                'period_1': get_datetime_period(self.time_detail, params.get('period_1')),
                'period_2': get_datetime_period(self.time_detail, params.get('period_2'))
            }
        except ValueError:
            if not self.test_mode:
                self.issue.comments.create(
                    text=u'Периоды для сравнения и/или период динамики включают несуществующую дату. \n'
                         u'Чтобы перезапустить задачу, поправьте ошибку в теле тикета и переоткройте его или '
                         u'закажите отчёт заново (({} тут)).'.format(FORM_URL),
                    summonees=[self.issue.createdBy.login]
                )
                self.issue.transitions['need_info'].execute()

            raise TaskError('%s: periods include existant day' % self.issue.key)

        except Exception as exc:
            raise exc

        if not self.period:
            self.period = {'first_date': self.df['Date'].min(), 'last_date': self.df['Date'].max()}
        else:
            logger.debug('{}: filter by {:%Y-%m-%d} - {:%Y-%m-%d}'.format(
                self.issue.key, self.period['first_date'], self.period['last_date']))
            self.df = filter_by_date(self.df, self.period)

    def make_reports(self):
        logger.info('%s: make xlsx-reports', self.issue.key)
        for f_num, region in enumerate(self.categories, 1):
            region_reports = []
            for category in region['categories']:
                df = prepare_category_df(self, region, category)
                if df.empty:
                    logger.warning('%s: empty report', self.issue.key)
                    if hasattr(self, 'empty_reports'):
                        self.empty_reports += [u'* !!(grey){}, {}!!'.format(region['region_name'], category)]

                else:
                    df = df.reset_index()
                    # Определено внутри класса type_
                    report_path = self.report_func(self, df, region, category, f_num=f_num)
                    region_reports.append(report_path)

            if region_reports:
                self.results[region['region_ids']] = region_reports

        logger.info('%s: xlsx-reports done', self.issue.key)

        logger.info('%s: make pptx-reports', self.issue.key)
        self.make_presentations()  # Определено внутри класса type_
        logger.info('%s: pptx-reports done', self.issue.key)

    def paste_results(self):
        if self.results:
            if not self.test_mode:
                logger.debug('%s: zip results', self.issue.key)
                self.results = self.zip_results()  # Определено внутри класса type_
                logger.info('%s: paste results', self.issue.key)
                if hasattr(self, 'empty_reports'):
                    if self.empty_reports:
                        self.empty_reports.insert(0, u'Нет данных:')
                        self.empty_reports += ['']

                if self.few_competitors:
                    self.few_competitors.insert(0, u'В некоторых категориях недостаточно конкурентов:')
                    self.few_competitors += ['']

                for i in range(21):
                    try:
                        self.issue.comments.create(text=u'\n'.join([com for com in
                                                                    getattr(self, 'empty_reports', []) +
                                                                    self.comment +
                                                                    self.few_competitors +
                                                                    [u'CPA-отчёты готовы.']
                                                                    ]),
                                                   attachments=self.results,
                                                   summonees=[self.issue.createdBy.login])
                        break
                    except Exception as exc:
                        if i == 20:
                            raise exc
                        sleep(10)
                for res_path in self.results:
                    remove(res_path)
        else:
            logger.info('%s: empty results', self.issue.key)
            if not self.test_mode:
                self.issue.comments.create(text='Нельзя построить отчет по указанным параметрам.',
                                           summonees=[self.issue.createdBy.login, 'leroy'])

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

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

        errs = []
        errs += self._validate_clients()
        errs += self._validate_periods()

        if errs:
            if not self.test_mode:
                errs.insert(0, u'В задании найдены ошибки:')
                errs += [u'Задание должно соответствовать прикрепленной выгрузке CPA.',
                         '',
                         u'Чтобы перезапустить задачу, поправьте ошибки в теле тикета и переоткройте его или '
                         u'закажите отчёт заново (({} тут)).'.format(FORM_URL)]

                self.issue.comments.create(text=u'\n'.join(errs))
                self.issue.transitions['need_info'].execute()

            raise TaskError('%s: task isn\'t validated' % self.issue.key)

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

    def _validate_clients(self):
        errs = []
        clients = self.df.Client.unique().tolist()
        if self.client and self.client not in clients:
            logger.error('{}: client not in data'.format(self.issue.key))
            errs += [u'* клиент !!{}!! не найден в исходных данных'.format(self.client)]

        if self.competitors:
            lost_competitors = [comp for comp in self.competitors if comp not in clients]
            if lost_competitors:
                logger.error('{}: some competitors not in data', self.issue.key)
                lost_competitors = u', '.join([u'!!{}!!'.format(comp) for comp in lost_competitors])
                lost_competitors = u'* некоторые конкуренты не найдены в исходных данных ({})'.format(lost_competitors)

                errs += [lost_competitors]

        return errs

    def _validate_periods(self):
        errs = []

        if self.period['first_date'] < self.df.Date.min() or self.period['last_date'] > self.df.Date.max():
            logger.error('%s: unavailable dynamic period', self.issue.key)
            errs += [u'* Период !!{:%Y-%m-%d} - {:%Y-%m-%d}!! не найден в исходных данных'.format(
                self.period['first_date'], self.period['last_date'])]

        for k, period in self.periods.iteritems():
            if period['first_date'] < self.df.Date.min() or period['last_date'] > self.df.Date.max():
                logger.error('%s: unavailable %s for comparison' % (self.issue.key, k))
                errs += [
                    u'* Период для сравнения !!{:%Y-%m-%d} - {:%Y-%m-%d}!! не найден в исходных данных или '
                    u'не входит в выбранный период динамики'.format(period['first_date'], period['last_date'])
                ]

        if (self.periods['period_1']['last_date'] > self.periods['period_2']['first_date'] or
                overlapping_periods(self.periods['period_1'], self.periods['period_2'])) \
                and not equal_periods(self.periods['period_1'], self.periods['period_2']):
            logger.error('%s: period_1 later than period_2 or in', self.issue.key)
            errs += [u'* Период 1 должен быть раньше или равен Периоду 2']

        return errs

    def _get_categories_by_regions(self):
        logger.debug('%s: get categories by regions', self.issue.key)

        tmp_df = self.df.copy()
        indexes = tmp_df.groupby(['Regions', 'Category'], as_index=True).sum().index.tolist()
        indexes = [{'region_ids': idx[0], 'category': idx[1]} for idx in indexes]

        categories = []
        for reg_ids in tmp_df.Regions.unique():
            reg_dict = {'region_ids': reg_ids,
                        'region_name': get_region_names(reg_ids, self.lang),
                        'categories': []}

            if self.data_filters.get('Category'):
                [reg_dict['categories'].append(category) for category in self.data_filters['Category']]
            else:
                [reg_dict['categories'].append(idx['category']) for idx in indexes if idx['region_ids'] == reg_ids]

            if len(reg_dict['categories']) > 1 and (self.type_ == 'retail' or self.type_ == 'finance'):
                reg_dict['categories'].insert(0, u'Суммарные данные')

            categories.append(reg_dict)

        self.categories = categories


class TaskError(Exception):
    def __init__(self, message=None):
        self.message = message
    pass
