# -*- coding: utf-8 -*-
from datetime import datetime
import logging
from os.path import dirname, join
import pandas as pd
from threading import Thread
import traceback as tb
from time import sleep
from yql.client.operation import YqlOperationType
from yt import wrapper as yt

from data_worker import CONFIG, SECRET
from data_worker.yql_worker import YQLWorker
from data_worker.make_forecast import auto_daily_cost_forecast, get_last_series
from utils import get_query_text, send_mail, write_table

logger = logging.getLogger(__name__)


def update_manual_data():
    logger.info('update manual data from xlsx')
    for k, config in CONFIG['manual_data'].iteritems():
        df = pd.read_excel(join(dirname(dirname(__file__)), 'manual_weekly_update.xlsx'), sheetname=config['ws_name'],
                           encoding='utf-8').dropna(how='all')
        for col_name in df.columns:
            df[col_name] = df[col_name].astype(pd.np.str) if col_name in ['date', 'type', 'period'] \
                else df[col_name].astype(pd.np.float)
        write_table(config['target_path'], config['schema'], df)
    logger.info('update manual data from xlsx: completed')


class SearchThread(Thread):
    def __init__(self, name=None):
        super(SearchThread, self).__init__()
        self.name = name
        self.last_update_time = None
        self.status, self.df = None, pd.DataFrame()
        logger.info('%sThread initialized', self.name)

    def run(self):
        while True:
            try:
                if self.status is None or self._update_table():
                    df = pd.DataFrame()
                    for from_dt, to_dt in zip(['2016-01-01', '2018-01-01'], ['2018-01-01',
                                                                             datetime.today().strftime('%Y-%m-%d')]):
                        yql_worker = YQLWorker(query_name='YQL: %s_fact' % self.name,
                                               query=get_query_text(self.name).format(from_dt=from_dt, to_dt=to_dt),
                                               operation_type=YqlOperationType.CLICKHOUSE)
                        yql_worker.run()
                        df = pd.concat([df, yql_worker.df])
                    self.df = df
                    self.status = 'ok'

            except Exception as exc:
                logger.exception('%sThread error' % self.name)
                send_mail('%sThread error' % self.name,
                          message='<code>{tb}</code>'.format(tb=tb.format_exc().replace('\n', '<br>')))

            sleep(60 * 60)

    def _update_table(self):
        update_time = datetime.now()
        if self.last_update_time is None:
            self.last_update_time = update_time
            return True

        if (update_time - self.last_update_time).seconds / 60. > 60.:
            logger.info('%s: update_time - %s, last_update_time - %s', self.name, update_time, self.last_update_time)
            self.last_update_time = update_time
            return True
        else:
            return False

    def to_def_status(self):
        logger.info('%sThread is empty', self.name)
        self.status, self.df = None, pd.DataFrame()


class NetworksThread(Thread):
    def __init__(self, name=None):
        super(NetworksThread, self).__init__()
        self.name = name
        self.last_modification_time = None
        self.status, self.df = None, pd.DataFrame()
        logger.info('%sThread initialized', self.name)

    def run(self):
        while True:
            try:
                if self.status is None or self._src_table_modified():
                    yql_worker = YQLWorker(query_name='YQL: %s_fact' % self.name,
                                           query=get_query_text(self.name).format(
                                               to_dt=datetime.today().strftime('%Y-%m-%d'),
                                               kub_path=SECRET['kub_path']
                                           ))
                    yql_worker.run()
                    self.df = yql_worker.df
                    self.status = 'ok'

            except Exception as exc:
                logger.exception('%sThread error' % self.name)
                send_mail('%sThread error' % self.name,
                          message='<code>{tb}</code>'.format(tb=tb.format_exc().replace('\n', '<br>')))

            sleep(60 * 10)

    def _src_table_modified(self):
        yt_client = yt.YtClient(token=SECRET['token'], proxy='hahn')
        modification_time = yt_client.get_attribute('//home/comdep-analytics/YAN/cubes/yan_new/v2.1.2/cooked_yan_cube',
                                                    'modification_time')
        if modification_time > self.last_modification_time or self.last_modification_time is None:
            logger.info('%s: modification_time - %s, last_modification_time - %s', self.name, modification_time,
                        self.last_modification_time)
            self.last_modification_time = modification_time
            return True
        else:
            return False

    def to_def_status(self):
        logger.info('%sThread is empty', self.name)
        self.status, self.df = None, pd.DataFrame()


def validate_series(df):
    def get_diff(row):
        try:
            return abs(row['%s_current' % col_name] / float(row['%s_last' % col_name]) - 1)
        except ZeroDivisionError:
            if int(row['%s_current' % col_name]) != 0:
                return 1
            else:
                return 0

    logger.info('validate series')

    last_df = get_last_series()
    last_df = last_df.join(df, lsuffix='_last', rsuffix='_current')
    df = df[df['date'] >= last_df['date_last'][last_df['type'] == 'fact'].max()]  # Для проверки на нули
    last_df = last_df.reset_index(drop=True)

    problems = []
    for col_name in [col_name for col_name in df.columns if col_name not in ['date', 'type']]:
        col_problems = []

        null_days = df[df[col_name] == 0]
        if null_days.shape[0] != 0:
            col_problems.append('<b>{}</b><br>null:<br>{}'.format(col_name, ', '.join(null_days.date.values)))

        last_df['%s_diff' % col_name] = last_df.apply(lambda row: get_diff(row), axis=1)
        diff_days = last_df[(last_df['%s_diff' % col_name] > 0.01)]
        if diff_days.shape[0] != 0:
            col_problems.append('<b>{}</b><br>>1%:<br>{}<br>'.format(col_name, ', '.join(diff_days.date_last.values)))

        problems += col_problems

    if problems:
        logger.info('problems in series found')
        send_mail('Problems in series', message='<br>'.join(problems))
    else:
        logger.info('good series')


def collect_fact():
    search_thread, networks_thread = SearchThread('search'), NetworksThread('networks')
    search_thread.start()
    networks_thread.start()

    while True:
        if search_thread.status == 'ok' and networks_thread.status == 'ok':
            try:
                fact_df = search_thread.df.join(networks_thread.df, how='outer').reset_index().fillna(0.)
                validate_series(fact_df)
                write_table(CONFIG['series']['target_path'], CONFIG['series']['schema'], fact_df)

                search_thread.to_def_status()
                networks_thread.to_def_status()

                auto_daily_cost_forecast(fact_df)

            except Exception as exc:
                logger.exception('Oops in collect_fact!')
                send_mail('Oops in collect_fact!',
                          message='<code>{tb}</code>'.format(tb=tb.format_exc().replace('\n', '<br>')))

        sleep(5)
