# -*- coding: utf-8 -*-
import argparse
from copy import deepcopy
from datetime import datetime
from dateutil.relativedelta import relativedelta
from exceller import ExcelDocument
from glob import glob
from itertools import product
from openpyxl import load_workbook
from os import mkdir, remove
from os.path import exists
from StringIO import StringIO
import sqlite3
import sys
from time import time
import traceback as tb

from const import LOG_FILE, TASK_NAME, DST_DIR
from report_maker.make_tables import (
    clear_unused_sheets,
    fill_competitors_table,
    check_competitors,
    fill_dyn_slide,
    fill_info,
    fill_period_slide,
)
import resources

WB_CNT = 0


def _make_report(conn, client, category, task, table_name, dest_dir):
    stopwatch = Timer(quiet=not exists(LOG_FILE))
    stopwatch("\n## %s, %s" % (client, category))
    meta = deepcopy(task)

    global WB_CNT
    # необходимо путь /Users/leroy/Desktop/Builder-new/CastomsZA/Result/26символов.xlsx уместить в 81 символ.
    # workbook_fullName = 'Result/' + transliterate(client) + '_' + transliterate(category) + '.xlsx'
    max_wbname = 30
    workbook_shortname = '{dst_dir}/{client}_{category}_{cnt}.xlsx'.format(
        dst_dir=dest_dir,
        client=transliterate(client)[:max_wbname / 2],
        category=transliterate(category)[:max_wbname / 2],
        cnt=str(WB_CNT + 1)
    )
    report = ExcelDocument(StringIO(resources.template))
    WB_CNT += 1

    curs = conn.cursor()
    meta['client'], meta['category'] = client, category
    meta = get_competitors(curs, table_name, meta)

    fill_null(conn, meta['months'], [category], [client] + meta['competitors'], meta['regions'], src_tbl=table_name)

    ########
    # Info #
    ########
    fill_info(report, meta)
    stopwatch("Info")

    # Очистка листов, которые не заполняются
    clear_unused_sheets(report)

    # Листы с динамикой по месяцам/кварталам
    for key in [
        'slide_2',
        'slide_3',
        'slide_4',
        'slide_6A',
        'slide_10',
        'slide_18',
        'slide_18_q',
        'slide_23'
    ]:
        fill_dyn_slide(report, curs, table_name, key, meta)
        stopwatch(key)

    # Листы с таблицами за период
    for key in [
        'slide_5',
        'slide_6B',
        'slide_7',
        'slide_8',
        'slide_11_rating', 'slide_11_bench_rating', 'slide_11_sum',
        'slide_16',
        'slide_19',
        'slide_21',
        'slide_24',
        'slide_25'
    ]:
        fill_period_slide(report, curs, table_name, key, meta)
        stopwatch(key)

    ##################
    # Бенчовые листы #
    ##################
    # Заполнение динамики по месяцам/кварталам/доле лидера
    for key in ['slide_9_abs', 'slide_9_rel', 'slide_9_leader']:
        fill_dyn_slide(report, curs, table_name, key, meta)
        stopwatch(key)
    # Заполнение таблицы с конкурентами
    fill_competitors_table(report, curs, table_name, 'slide_9_competitors', meta)
    stopwatch('slide_9_competitors')

    report.save(workbook_shortname)

    if not stopwatch.is_quiet:
        print u'\n>>TOP-5 вкладок<<'
        for label, call_s, tot_s in stopwatch.top(5):
            print label, call_s, tot_s

    bad_sheets = check_competitors(curs, table_name, 'slide_9_check', meta)
    if bad_sheets:
        with open(LOG_FILE, 'a') as fd:
            fd.write(u'\n#########\n{}, {} ({})\n'.format(client, category, workbook_shortname).encode('utf-8'))
            fd.write(u'\nНа некоторых листах отчета недостаточно конкурентов:\n'.encode('utf-8'))
            for sheet in bad_sheets:
                fd.write(u'* {}\n'.format(sheet).encode('utf-8'))
            fd.write('\n')


def get_competitors(curs, table_name, meta):
    curs.execute(u"""
        SELECT
            Client,
            SUM(Cost) AS Cost_Sum
        FROM {table_name}
        WHERE
            CustomCategory = '{category}'
            AND Client != "{client}"
            AND Month >= {first_date} AND Month <= {last_date}
        GROUP BY Client
        HAVING Cost_Sum > 0
        ORDER BY Cost_Sum DESC
        LIMIT 30
    """.format(table_name=table_name, category=meta['category'], client=meta['client'],
               first_date=meta['period_2']['first_date'], last_date=meta['period_2']['last_date']))
    top_clients = [comp[0] for comp in curs.fetchall()]

    meta['top_clients'] = top_clients

    if not meta['competitors']:
        meta['def_competitors'] = True
        meta['competitors'] = top_clients[:5]
    else:
        meta['def_competitors'] = False

    return meta


class Timer:
    def __init__(self, quiet=True):
        self.begin_time_s = time()
        self._last_stop_s = self.begin_time_s
        self.is_quiet = quiet
        self.eventlog = []

    def __call__(self, label=''):
        current_time_s = time()
        last_call_elapsed = current_time_s - self._last_stop_s
        begin_elapsed = current_time_s - self.begin_time_s
        self._last_stop_s = current_time_s

        if not self.is_quiet:
            print "%s -- call: %.2f; total: %.2f" % (label, last_call_elapsed, begin_elapsed)

        event = label, last_call_elapsed, begin_elapsed
        self.eventlog.append(event)

        return event

    def top(self, n=5):
        return sorted(self.eventlog, key=lambda e: e[1], reverse=True)[:n]


def transliterate(string):
    if not isinstance(string, unicode):
        string = unicode(string)

    capital_letters = {
        u'А': u'A',
        u'Б': u'B',
        u'В': u'V',
        u'Г': u'G',
        u'Д': u'D',
        u'Е': u'E',
        u'Ё': u'E',
        u'Ж': u'Zh',
        u'З': u'Z',
        u'И': u'I',
        u'Й': u'Y',
        u'К': u'K',
        u'Л': u'L',
        u'М': u'M',
        u'Н': u'N',
        u'О': u'O',
        u'П': u'P',
        u'Р': u'R',
        u'С': u'S',
        u'Т': u'T',
        u'У': u'U',
        u'Ф': u'F',
        u'Х': u'H',
        u'Ц': u'Ts',
        u'Ч': u'Ch',
        u'Ш': u'Sh',
        u'Щ': u'Sch',
        u'Ъ': u'',
        u'Ы': u'Y',
        u'Ь': u'',
        u'Э': u'E',
        u'Ю': u'Yu',
        u'Я': u'Ya',
    }

    lower_case_letters = {
        u'а': u'a',
        u'б': u'b',
        u'в': u'v',
        u'г': u'g',
        u'д': u'd',
        u'е': u'e',
        u'ё': u'e',
        u'ж': u'zh',
        u'з': u'z',
        u'и': u'i',
        u'й': u'y',
        u'к': u'k',
        u'л': u'l',
        u'м': u'm',
        u'н': u'n',
        u'о': u'o',
        u'п': u'p',
        u'р': u'r',
        u'с': u's',
        u'т': u't',
        u'у': u'u',
        u'ф': u'f',
        u'х': u'h',
        u'ц': u'ts',
        u'ч': u'ch',
        u'ш': u'sh',
        u'щ': u'sch',
        u'ъ': u'',
        u'ы': u'y',
        u'ь': u'',
        u'э': u'e',
        u'ю': u'yu',
        u'я': u'ya',
    }

    translit_string = ""

    for index, char in enumerate(string):
        if char in lower_case_letters.keys():
            char = lower_case_letters[char]
        elif char in capital_letters.keys():
            char = capital_letters[char]
            if len(string) > index + 1:
                if string[index + 1] not in lower_case_letters.keys():
                    char = char.upper()
            else:
                char = char.upper()
        translit_string += char

    return translit_string


def fill_null(conn, months, categories, client, regions, src_tbl='data'):
    u"""Заполнитель сегментов дефолтными значения метрик.

    Note:
    При многократном запуске может сильно увеличить объем базы.

    #todo:
    - записывать только реально отсутствующее
    """
    list_of_null_vals = []
    if regions:
        for row in product(months, categories, client, regions, ['Desktop', 'Mobile'], [''], [''], [1, 0]):
            list_of_null_vals.append(row)
    else:
        for row in product(months, categories, client, ['Desktop', 'Mobile'], [''], [''], [1, 0]):
            list_of_null_vals.append(row)

    if regions:
        loader = u'INSERT INTO {table} (Month, CustomCategory, Client, Region, Device, CampaignType, BannerType, IsMobile) VALUES ({values})'.format(
            table=src_tbl, values=u", ".join(u'?' * 8)
        )
    else:
        loader = u'INSERT INTO {table} ("Month", "CustomCategory", "Client", "Device", "CampaignType", "BannerType", "IsMobile") VALUES ({values})'.format(
            table=src_tbl, values=u", ".join(u'?' * 7)
        )
    curs = conn.cursor()
    curs.executemany(loader, list_of_null_vals)

    curs.execute(
        u"""
        UPDATE {table}
        SET
            Qvartal = ("Q"||((CAST(SUBSTR(Month, 5, 2) AS INTEGER) + 2) / 3 )||"'"||SUBSTR(Month, 3, 2)),
            HalfYear = ("H"||((CAST(SUBSTR(Month, 5, 2) AS INTEGER) + 5) / 6 )||"'"||SUBSTR(Month, 3, 2))
        """.format(table=src_tbl)
    )
    conn.commit()


def make_tmp_table(conn, months, competitors, clients, regions, categories, tmp_tbl, src_tbl='data'):
    u"""Подготовка общих таблиц для последующего анализа.
    Note:
    tmp_tbl        -- отфильтровано по заданию, добавлен квартал, полугодие
    """

    curs = conn.cursor()
    curs.execute(u'DROP TABLE IF EXISTS %s' % tmp_tbl)
    conn.commit()

    # отфильтрованное
    elem = u"""
            "Month" TEXT,
            "Client" TEXT,
            "CustomCategory" TEXT,
            "Region" TEXT,
            "Device" TEXT,
            "CampaignType" TEXT,
            "BannerType" TEXT,
            "IsMobile" TEXT,
            "Direct_Shows" INTEGER,
            "Direct_Clicks" INTEGER,
            "Direct_Cost" REAL,
            "RSYA_Shows" INTEGER,
            "RSYA_Clicks" INTEGER,
            "RSYA_Cost" REAL,
            "Shows" INTEGER,
            "Clicks" INTEGER,
            "Cost" REAL,

            "Qvartal" TEXT,
            "HalfYear" TEXT
            """
    curs.execute(
        u'CREATE TEMPORARY TABLE {table} ({fields_type})'.format(
            table=tmp_tbl, fields_type=elem
        )
    )
    fields = u"""
                "Month",
                "Client",
                "CustomCategory",
                "Region",
                "Device",
                "CampaignType",
                "BannerType",
                "IsMobile",
                "Direct_Shows", "Direct_Clicks", "Direct_Cost",
                "RSYA_Shows", "RSYA_Clicks", "RSYA_Cost",
                "Shows", "Clicks", "Cost"
            """

    months = join4filter(months)
    competitors = join4filter(set(competitors + clients))
    categories = join4filter(categories)
    if regions:
        regions = join4filter(regions)

    curs.execute(
        u"""INSERT INTO {TMP_TBL} ({FIELDS}, "Qvartal", "HalfYear")
            SELECT
                {FIELDS},
                ("Q"||((CAST(SUBSTR(Month, 5, 2) AS INTEGER) + 2) / 3 )||"'"||SUBSTR(Month, 3, 2)),
                ("H"||((CAST(SUBSTR(Month, 5, 2) AS INTEGER) + 5) / 6 )||"'"||SUBSTR(Month, 3, 2))
            FROM {SRC_TBL}
            WHERE
                Month IN ({MONTHS})
                AND Client IN ({COMPETITORS})
                AND CustomCategory IN ({CATEGORIES})
                {REGION_FILTER}
        """.format(TMP_TBL=tmp_tbl, FIELDS=fields, SRC_TBL=src_tbl, MONTHS=months, COMPETITORS=competitors,
                   CATEGORIES=categories,
                   REGION_FILTER=u'AND Region IN ({REGIONS})'.format(REGIONS=regions) if regions else u'')
    )
    conn.commit()


def get_full_periods(conn, tmp_tbl, meta):
    curs = conn.cursor()

    curs.execute(u"""
        SELECT Qvartal
        FROM {tmp_tbl}
        GROUP BY Qvartal
        HAVING COUNT(DISTINCT Month) = 3
        ORDER BY Month ASC
    """.format(tmp_tbl=tmp_tbl))
    list_full_qvart = [q[0] for q in curs.fetchall()]
    for q_num, q_name in enumerate(list_full_qvart[-4:], 1):
        curs.execute(u"""
            SELECT
                MIN(Month) AS First_Date,
                MAX(Month) AS Last_Date
            FROM {tmp_tbl}
            WHERE Qvartal = \"{q_name}\"
        """.format(tmp_tbl=tmp_tbl, q_name=q_name))
        dates = curs.fetchall()[0]
        meta['q_%i' % q_num] = {'first_date': dates[0], 'last_date': dates[1],
                                'name': q_name.replace("'", ' 20')}

    curs.execute(u"""
        SELECT HalfYear
        FROM {tmp_tbl}
        GROUP BY HalfYear
        HAVING COUNT(DISTINCT Month) = 6
        ORDER BY Month ASC
    """.format(tmp_tbl=tmp_tbl))
    list_full_half = [h[0] for h in curs.fetchall()]
    for h_num, h_name in enumerate(list_full_half[-2:], 1):
        curs.execute(u"""
            SELECT
                MIN(Month) AS First_Date,
                MAX(Month) AS Last_Date
            FROM {tmp_tbl}
            WHERE HalfYear = \"{h_name}\"
        """.format(tmp_tbl=tmp_tbl, h_name=h_name))
        dates = curs.fetchall()[0]
        meta['h_%i' % h_num] = {'first_date': dates[0], 'last_date': dates[1],
                                'name': h_name.replace("'", ' 20')}

    return meta


def join4filter(xs):
    return '"' + '", "'.join(xs) + '"'


def make_reports(conn, task, dst_dir):

    # условия для фильтрации
    info_wb = load_workbook(task, read_only=True)
    info_ws = info_wb["Info"]

    task = dict()
    task["months"] = prepare_months([str(int(x)) for x in _read_column(info_ws, col_idx=1, skip_nrows=1)])
    task["data_competitors"] = [x.strip() for x in _read_column(info_ws, col_idx=2, skip_nrows=1)]
    task["regions"] = [x for x in _read_column(info_ws, col_idx=4, skip_nrows=1)]
    task["cats"] = [x.strip() for x in _read_column(info_ws, col_idx=5, skip_nrows=1)]

    # целевые клиенты для отчетов
    task["clients"] = [x.strip() for x in _read_column(info_ws, col_idx=7, skip_nrows=1)]
    if not task["clients"]:
        task["clients"] = ["None", ]
    # для вкладки про использование регионов
    task["segments"] = prepare_segments([x.strip() for x in _read_column(info_ws, col_idx=8, skip_nrows=1)])

    task["period_1"] = split_period(info_ws['I2'].value, min(task["months"])) or ''
    task["period_2"] = split_period(info_ws['J2'].value, max(task["months"])) or ''
    task["filter_client"] = True if info_ws['K2'].value.strip().lower() == u'нет' else False
    task["lang"] = info_ws['L2'].value.strip()
    task["cur"] = info_ws['M2'].value or ''

    if len(task["clients"]) == 1:
        task["competitors"] = [x.strip() for x in _read_column(info_ws, col_idx=14, skip_nrows=1)]
        task["competitors"] = [comp for comp in task["competitors"] if comp not in task["clients"]]
    else:
        task["competitors"] = []

    if exists(LOG_FILE):
        with open(LOG_FILE, "a") as fd:
            fd.write("\nPARSED TASK\n:%r\n" % task)

    if exists(dst_dir):
        old_results = glob("%s/*" % dst_dir)
        for x in old_results:
            remove(x)
    else:
        mkdir(dst_dir)

    for cl in task["clients"]:
        for cat in task["cats"]:
            c_time = str(int(time()))[:]  # Префикс для создания временных таблиц
            tmp_tbl = 'tmpdata_%s' % c_time
            make_tmp_table(conn,
                           task["months"], task["data_competitors"], task["clients"], task["regions"], [cat],
                           tmp_tbl)
            task = get_full_periods(conn, tmp_tbl, task)

            _make_report(conn, cl, cat, task, tmp_tbl, dst_dir)

    unavailable_slides = [
        u'Распределение по позициям (id_26)',
        u'Распределение по позициям (CPC by ad blocks) (id_27)',
        u'CTR клиента по типам блоков (id_104)',
        u'CTR по типам блоков (id_105)'
    ]
    with open(LOG_FILE, 'a') as fd:
        fd.write(u'\n#########\nСписок слайдов, недоступных для кастомного отчета в april:\n'.encode('utf-8'))
        for slide in unavailable_slides:
            fd.write(u'* {}\n'.format(slide).encode('utf-8'))
        fd.write('\n')


def _read_column(sheet, col_idx, skip_nrows=0):
    assert 1 <= col_idx <= sheet.max_column
    # в excel нумерация с 1
    vals = [x[0].value for x in sheet.iter_rows(min_col=col_idx, min_row=skip_nrows + 1,
                                                max_col=col_idx, max_row=sheet.max_row)
            if x[0].value is not None]

    return vals


def prepare_months(task_months):
    first_date = datetime.strptime(str(min(task_months)), '%Y%m')
    last_date = datetime.strptime(str(max(task_months)), '%Y%m')

    months = [first_date, ]
    m_cnt = 1
    while months[-1] != last_date:
        months.append(first_date + relativedelta(months=m_cnt))
        m_cnt += 1

    return [m.strftime('%Y%m') for m in months]


def split_period(period, def_month):
    if not period:
        return {'first_date': def_month, 'last_date': def_month}

    try:
        period = unicode(period)
        dates = [date.strip() for date in period.split('-')]
        return {'first_date': dates[0], 'last_date': dates[1]}
    except IndexError:
        return {'first_date': period.strip(), 'last_date': period.strip()}


def prepare_segments(segments):
    if not segments:
        return {'segment_1': u'Сегмент 1', 'segment_2': u'Сегмент 2'}
    elif len(segments) == 1:
        return {'segment_1': segments[0], 'segment_2': u'Сегмент 2'}

    return {'segment_1': segments[0], 'segment_2': segments[1]}


def pars_argv():
    parser = argparse.ArgumentParser(
        description=u'Модуль для автоматического создания xlsx-отчетов.'
    )
    parser.add_argument(
        '--base',
        default='source.db', metavar='',
        help=u'Имя используемой базы данных. (default: %(default)s). '
        u'В базе дожна быть таблица data. '
    )
    return parser.parse_args(sys.argv[1:])


class MaxByFunc(object):
    def __init__(self):
        self.result = None
        self.max_value = float('-inf')

    def step(self, value, max_by_value):
        if max_by_value is not None and self.max_value < max_by_value:
            self.result = value
            self.max_value = max_by_value

    def finalize(self):
        return self.result


def main(src, task, dst_dir):
    with open(LOG_FILE, 'a') as f:
        f.write('start avtograf\n')

    try:
        if not exists(src):
            raise ValueError("There are no file with data: %s" % src)
        if not exists(task):
            raise ValueError("There are no file with task: %s" % task)

        disk_conn = sqlite3.connect(src)
        disk_conn.create_aggregate('max_by', 2, MaxByFunc)

        make_reports(disk_conn, task, dst_dir)
        disk_conn.close()

    except Exception:
        with open(LOG_FILE, 'a') as f:
            tb.print_exc(file=f)

    else:
        with open(LOG_FILE, 'a') as f:
            f.write('done avtograf\n')


if __name__ == '__main__':
    opt = pars_argv()
    main(opt.base, TASK_NAME, DST_DIR)
