# -*- coding: utf-8 -*-
import re
import datetime
from datacloud.dev_utils.logging.logger import get_basic_logger
from datacloud.dev_utils.yt import yt_utils
from datacloud.dev_utils.solomon import solomon_utils
from collections import defaultdict, Counter
from datacloud.dev_utils.time.patterns import RE_MONTH_LOG_FORMAT, RE_DAILY_LOG_FORMAT, FMT_DATE, FMT_DATE_YM
from datacloud.config.yt import YT_PROXY

logger = get_basic_logger(__name__)
# Value of Value-added tax
VAT = 0.20

PRICE_POLICY_TABLE = '//home/x-products/production/datacloud/money/price_policy'
MONEY_LOG_TABLE = '//home/x-products/production/datacloud/money/money_log'
RESPONSE_LOG_TABLE = '//home/x-products/production/services/logs/datacloud_score_api/production/responses'

PRICE_POLICY_TABLE_SCHEMA = [
    {'name': 'partner_id', 'type': 'string', 'sort_order': 'ascending'},
    {'name': 'score_list', 'type': 'string', 'sort_order': 'ascending'},
    {'name': 'is_active', 'type': 'boolean'},
    {'name': 'policy', 'type': 'any'}
]

MONEY_LOG_TABLE_SCHEMA = [
    {'name': 'partner_id', 'type': 'string', 'sort_order': 'ascending'},
    {'name': 'month', 'type': 'string', 'sort_order': 'ascending'},
    {'name': 'score_list', 'type': 'string', 'sort_order': 'ascending'},
    {'name': 'monthly_income', 'type': 'double'},
    {'name': 'SLA_achieved', 'type': 'boolean'},
    {'name': 'updated', 'type': 'string'},
    {'name': 'statistics', 'type': 'any'},
    {'name': 'finalized', 'type': 'boolean'}
]


TCS_PRICE_POLICY = {
    'partner_id': 'tcs',
    'score_list': 'tcs_xprod_987_20181212_&_tcs_xprod_868_20180330',
    'is_active': True,
    'policy': {
        'system': {
            'has_score': 'buckets'
        },
        'buckets': {
            'bins': [5e5, 1e6, 2e6, 3e6],
            'prices': {
                'has_score': [5.0, 4.0, 3.0, 2.5, 2.0],
            }
        },
        'min_monthly_price': 5e5,
        'SLA': {
            'type': 'default',
            'value': 0.05
        },
        'start_from': '2018-07',
        'VAT_included': True
    }
}


OKB_PRICE_POLICY = {
    'partner_id': 'okb',
    'score_list': 'okb_xprod_948-m1_raiff_credit_scoring_v1_20180903_&_okb_xprod_900-m1_migCredit_D1_v1_20180514_&_okb_generic-m2_credit_scoring_v1_20181029',
    'is_active': True,
    'policy': {
        'system': {
            'has_score': 'simple',
            'no_score': 'simple'
        },
        'buckets': {
            'prices': {
                'has_score': 2,
                'no_score': 1.5
            }
        },
        'min_monthly_price': 0,
        'SLA': {
            'type': 'default',
            'value': 0.05
        },
        'start_from': '2018-09',
        'VAT_included': False
    }
}


RAIFAML_PRICE_POLICY = {
    'partner_id': 'raiff',
    'score_list': 'raiff_xprod_931-ip_20180806_&_raiff_xprod_931-corp_20180806',
    'is_active': True,
    'policy': {
        'system': {
            'answers': 'buckets'
        },
        'buckets': {
            'bins': [6e3, 12e3, 40e3],
            'prices': {
                'answers': [500.0 / 6, 65, 40, 15],
            }
        },
        'min_monthly_price': 5e5,
        'SLA': {
            'type': 'default',
            'value': 0.05
        },
        'start_from': '2018-08',
        'VAT_included': True
    }
}


HCB_PRICE_POLICY = {
    'partner_id': 'homecredit',
    'score_list': 'yandex_hcfb_risk_mix_201711_&_hcb_xprod_889_20180508',
    'is_active': True,
    'policy': {
        'system': {
            'hit': 'hcb',
        },
        'buckets': {
            'bins': [0.4, 0.5, 0.6, 0.7, 0.8, 0.9],
            'prices': {
                'hit': [4e5, 5e5, 6e5, 7e5, 8e5, 9e5, 10e5],
            }
        },
        'min_monthly_price': 0,
        'SLA': {
            'type': 'default',
            'value': 0.05
        },
        'start_from': '2018-01',
        'VAT_included': True
    }
}


UBRR_PRICE_POLICY = {
    'is_active': True,
    'partner_id': 'ubrr',
    'policy': {
        'SLA': {'type': 'default', 'value': 0.05},
        'VAT_included': False,
        'buckets': {
            'bins': [15000.0, 100000.0, 250000.0, 500000.0, 1000000.0, 2000000.0, 3000000.0],
            'prices': {
                'has_score': [20.0, 15.0, 14.0, 13.0, 11.5, 10.0, 8.0, 6.0]}
        },
        'min_monthly_price': 300000.0,
        'start_from': '2019-03',
        'system': {'has_score': 'buckets'}
    },
    'score_list': 'ubrr_xprod_1158_socdef_&_ubrr_xprod_936_fraud'
}


def create_price_policy_dtable(yt_client):
    """ Creates dtable at yt with proper schemas (according to constants above)
    """
    dtable_object = yt_utils.DynTable()
    dtable_object.create_table(PRICE_POLICY_TABLE, PRICE_POLICY_TABLE_SCHEMA, yt_client)


def create_money_log_dtable(yt_client):
    """ Creates dtable at yt with proper schemas (according to constants above)
    """
    dtable_object = yt_utils.DynTable()
    dtable_object.create_table(MONEY_LOG_TABLE, MONEY_LOG_TABLE_SCHEMA, yt_client)


def add_price_policy(policy, yt_client):
    """ This function adds price policy to dynamic table at yt
    """
    dtable_object = yt_utils.DynTable()
    dtable_object.insert_row(PRICE_POLICY_TABLE, yt_client, [policy])


def add_money_log_row(partner_id, score_list, month, SLA_status, monthly_income, monthly_stats, finalized, yt_client):
    """ This function adds price policy to dynamic table at yt
    """
    if len(month) == len('2018-07'):
        row = dict()
        row['partner_id'] = partner_id
        row['score_list'] = score_list
        row['month'] = month
        row['SLA_achieved'] = SLA_status
        row['monthly_income'] = monthly_income
        row['statistics'] = monthly_stats
        row['updated'] = datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ')
        row['finalized'] = finalized
        dtable_object = yt_utils.DynTable()
        dtable_object.insert_row(MONEY_LOG_TABLE, yt_client, [row])
    return row


def calc_money(results, price_policy, month, use_min_price=True):
    """
    Function calculates money according to data from score API logs
    and price policy of the client
    """
    money = 0
    func_dict = {
        'simple': simple_apply,
        'buckets': bucket_apply,
        'hcb': hcb_bucket_apply
    }

    if month >= price_policy['start_from']:
        for key, val in price_policy['system'].items():
            money += func_dict[val](results, price_policy['buckets'], key)
        if use_min_price:
            money = max(money, price_policy['min_monthly_price'])
    else:
        money = 0.0
    if price_policy['VAT_included']:
        money = money / (1 + VAT)
    return money


def bucket_apply(result, buckets, count_by):
    """ Function perform 'bucket' type price calculation
    """
    money = 0.0
    prev_bucket = 0.0
    for i in range(len(buckets['bins'])):
        if result[count_by] - prev_bucket < buckets['bins'][i] - prev_bucket:
            money += (result[count_by] - prev_bucket) * buckets['prices'][count_by][i]
            break
        else:
            money += (buckets['bins'][i] - prev_bucket) * buckets['prices'][count_by][i]
            prev_bucket = buckets['bins'][i]

    if prev_bucket == buckets['bins'][-1]:
        money += (result[count_by] - prev_bucket) * buckets['prices'][count_by][-1]
    return money


def hcb_bucket_apply(result, buckets, count_by):
    """ Function perform 'bucket' type price calculation
    """
    money = 0.0
    price_found = False
    hit = 1.0 * result['has_score'] / (result['has_score'] + result['no_score'])
    for i in range(len(buckets['bins'])):
        if hit > buckets['bins'][i]:
            money = buckets['prices'][count_by][i + 1]
            price_found = True
            break

    if not price_found and hit < buckets['bins'][0]:
        money = buckets['prices'][count_by][0]

    return money


def simple_apply(result, buckets, count_by):
    """ Function perform 'simple' type price calculation
    """
    return result[count_by] * buckets['prices'][count_by]


def parse_schema_dict(schema_dict, labels_keys):
    """
    Function takes schema_dict which is basically aggregated
    dict of all requests and answers from the log table parsed
    and returns statistics of hits, not hits and timeouts
    """
    results = defaultdict(lambda: defaultdict(lambda: Counter()))
    pid_index = labels_keys.index('partner')
    type_index = labels_keys.index('type')
    score_name_index = labels_keys.index('score_name')
    for key, val in schema_dict.items():
        pid = key[0][pid_index]
        response_type = key[0][type_index]
        score_name = key[0][score_name_index]
        results[pid][score_name]['requests'] += val
        results[pid][score_name][response_type] += val
    return results


def add_hit_to_result(result):
    """
    Function takes current results
    and calculates hit based on it
    then hit added to the result dict
    """
    if result.get('requests') and result['has_score'] + result['no_score'] > 0:
        result['hit'] = 1.0 * result['has_score'] / (result['has_score'] + result['no_score'])
    else:
        result['hit'] = None
    return result


def add_answers_to_result(result):
    """ Adds amounts of 'answers' to results
    """
    has_score = result.get('has_score', 0)
    no_score = result.get('no_score', 0)
    result['answers'] = no_score + has_score
    return result


def get_price_policy(partner_id, score_name, yt_client):
    """
    Function takes price policy from table
    TODO: rewrite using 'get_rows_from_table'
    method of DynTable()
    """
    is_active = False
    price_policy = None
    request = '* FROM [{}] WHERE partner_id = "{}" and score_name = "{}"'.format(
        PRICE_POLICY_TABLE,
        partner_id,
        score_name
    )

    dtable_object = yt_utils.DynTable()
    for row in dtable_object.list_rows(yt_client, request):
        assert not(row['is_active'] & is_active), "Several active price policies for pair {}-{}".format(partner_id, score_name)
        if not is_active:
            is_active = row['is_active']

        if row['is_active']:
            price_policy = row['policy']
    return is_active, price_policy


def check_SLA(result, policy):
    """ Function takes statistics and check if SLA is achieved
    """
    if policy['SLA']['type'] == 'default':
        if result['requests'] > 0:
            return 1.0 * (result['has_score'] + result['no_score']) / result['requests'] > 1 - policy['SLA']['value']
        else:
            return True
    else:
        raise ValueError('Unknown SLA policy')
    # return


def money_and_SLA_by_results(results, month, yt_client, finalized=True, use_min_price=True):
    """
    Function takes result dict from the log table and applies
    money calculation function for each partner and set of score which
    are marked as active in the PRICE_POLICY_TABLE
    """
    active_rows = []
    money_dict = defaultdict(lambda: dict())
    SLA_dict = defaultdict(lambda: dict())
    result_by_contract = defaultdict(lambda: dict())
    for row in yt_client.read_table(PRICE_POLICY_TABLE):
        if row['is_active']:
            active_rows.append(row)
    for row in active_rows:
        pid = row['partner_id']
        score_list = row['score_list']
        result_by_contract[pid][score_list] = sum([results[pid][score_name] for score_name in score_list.split('_&_')], Counter())
        if not finalized:
            result_by_contract[pid][score_list] = add_intermediate_results(
                pid,
                score_list,
                month,
                result_by_contract[pid][score_list],
                yt_client
            )
        result_by_contract[pid][score_list] = add_hit_to_result(result_by_contract[pid][score_list])
        result_by_contract[pid][score_list] = add_answers_to_result(result_by_contract[pid][score_list])
        money_dict[pid][score_list] = calc_money(result_by_contract[pid][score_list], row['policy'], month, use_min_price)
        SLA_dict[pid][score_list] = check_SLA(result_by_contract[pid][score_list], row['policy'])
    return money_dict, SLA_dict, result_by_contract


def add_intermediate_results(partner_id, score_list, month, new_results, yt_client):
    """
    Function takes results from tables and add to them result
    from this month which already was processed in this month
    from MONEY_LOG_TABLE
    """
    dtable_object = yt_utils.DynTable()
    existed_rows = list(
        dtable_object.get_rows_from_table(
            MONEY_LOG_TABLE,
            {'partner_id': partner_id, 'score_list': score_list, 'month': month},
            yt_client
        )
    )
    if len(existed_rows) == 0:
        return new_results
    elif len(existed_rows) == 1:
        for key in set(new_results.keys() + existed_rows[0]['statistics'].keys()):
            prev_val = existed_rows[0]['statistics'].get(key, 0)
            new_val = new_results.get(key, 0)
            new_results[key] = prev_val + new_val
        return new_results
    else:
        assert len(existed_rows) < 2, 'Something wrong with log table!'


def calc_money_monthly_table(table, yt_client, schema_dict=None, labels_keys=None):
    """
    Perform money calculation for
    month log table. In the  MONEY_LOG_TABLE it is marked
    as finalized, so these results could be used for billing
    of our partners
    """

    schema_dict, labels_keys = read_router_qloud_table_into_schema_dict(table, yt_client)
    finalized = True
    month = table.split('/')[-1]
    results = parse_schema_dict(schema_dict, labels_keys)
    money_dict, SLA_dict, result_by_contract = money_and_SLA_by_results(results, month, yt_client, finalized)
    for pid, score_money in money_dict.iteritems():
        for score_list, money in score_money.iteritems():
            sent_row = add_money_log_row(
                pid,
                score_list,
                month,
                SLA_dict[pid][score_list],
                money,
                result_by_contract[pid][score_list],
                finalized,
                yt_client
            )
            load_monthly_income_to_solomon(sent_row)


def calc_money_daily_table(table, yt_client, schema_dict=None, labels_keys=None):
    """
    Read dayly log and add it to the the MONEY_LOG_TABLE
    in the current month. Results are marked as not finalized,
    and eventually will be replaced with monthly results
    """
    schema_dict, labels_keys = read_router_qloud_table_into_schema_dict(table, yt_client)
    finalized = False
    parsed_date = datetime.datetime.strptime(table.split('/')[-1], FMT_DATE)
    month = parsed_date.strftime(FMT_DATE_YM)
    results = parse_schema_dict(schema_dict, labels_keys)
    money_dict, SLA_dict, result_by_contract = money_and_SLA_by_results(
        results,
        month,
        yt_client,
        finalized=False,
        use_min_price=False
    )
    for pid, score_money in money_dict.iteritems():
        for score_list, money in score_money.iteritems():
            if money != 0:
                sent_row = add_money_log_row(
                    pid,
                    score_list,
                    month,
                    SLA_dict[pid][score_list],
                    money,
                    result_by_contract[pid][score_list],
                    finalized,
                    yt_client
                )
                load_intermediate_income_to_solomon(sent_row, ts=solomon_utils.str2ts(table.split('/')[-1]))


def make_income_sensor(partner_id, score_list, month, column, value, ts=None):
    """ Makes sensor for data to sent it to solomon
    """
    if ts is None:
        ts = int(datetime.datetime.strptime(month, FMT_DATE_YM).strftime('%s'))

    return {
        'labels': {
            'partner_id': partner_id,
            'score_list': score_list,
            'column': column,
            'period': month
        },
        'ts': ts,
        'value': value
    }


def load_intermediate_income_to_solomon(row, ts):
    """ Sends intermediate income to solomon
    """
    sensors = [make_income_sensor(
        row['partner_id'],
        row['score_list'],
        row['month'],
        'monthly_income',
        row['monthly_income'],
        ts
    )]
    logger.info(solomon_utils.post_sensors_to_solomon('datacloud', 'money', 'online-logs', sensors))


def load_monthly_income_to_solomon(row):
    """
    Sends monthly results to solomon including total income, status of SLA
    and amount hit/not_hit/timeout
    """
    sensors = [
        make_income_sensor(row['partner_id'], row['score_list'], row['month'],
                           'monthly_income', row['monthly_income']),  # Add money sensor
        make_income_sensor(row['partner_id'], row['score_list'], row['month'],
                           'SLA_achieved', int(row['SLA_achieved']))  # Add SLA sensor
    ]

    # Add statistics: hit, timeouts, hits, no_hits and etc
    for key, val in row['statistics'].iteritems():
        if val is not None:
            sensors.append(make_income_sensor(
                row['partner_id'], row['score_list'], row['month'], key, val))
    logger.info(solomon_utils.post_sensors_to_solomon('datacloud', 'money', 'cumulative-monthly', sensors))


def check_has_score_and_error(rec):
    if rec['status'] in {'200'} and rec['has_score']:
        return 'has_score'
    elif rec['status'] in {'200'} and rec['has_score'] is False:
        return 'no_score'
    else:
        return 'timeout'


def read_router_qloud_table_into_schema_dict(table, yt_client):
    schema_dict = Counter()
    for rec in yt_client.read_table(table):
        labels = {
            'partner': rec['partner_id'],
            'score_name': rec['score_name'],
            'type': check_has_score_and_error(rec)
        }
        ts = rec.get('ts')
        if ts is not None:
            ts = int(ts)
        schema_dict[(tuple(labels.values()), ts)] += 1
    return schema_dict, labels.keys()


def run_calc_money_table(task):
    yt_client = yt_utils.get_yt_client()
    table = task.data['table_path']
    date = task.data['date_str']
    if re.match(RE_DAILY_LOG_FORMAT, date):
        calc_money_daily_table(table, yt_client)
    elif re.match(RE_MONTH_LOG_FORMAT, date):
        calc_money_monthly_table(table, yt_client)
    return [task.make_done()]


def detect_ready_response_table_for_money_calc(date_time, days=None):
    yt_client = yt_utils.get_yt_client()
    for table in yt_client.list(RESPONSE_LOG_TABLE, absolute=True):
        date = table.split('/')[-1]
        if (re.match(RE_DAILY_LOG_FORMAT, date) or re.match(RE_MONTH_LOG_FORMAT, date)):
            yield table, {'table_path': table, 'date_str': date}


if __name__ == '__main__':
    yt_client = yt_utils.get_yt_client()
    # yt_utils.remove_table(PRICE_POLICY_TABLE, yt_client)
    # create_price_policy_dtable(yt_client)
    # yt_utils.remove_table(MONEY_LOG_TABLE, yt_client)
    # create_money_log_dtable(yt_client)
    # add_price_policy(TCS_PRICE_POLICY, yt_client)
    # add_price_policy(HCB_PRICE_POLICY, yt_client)
    # add_price_policy(RAIFAML_PRICE_POLICY, yt_client)
    # add_price_policy(OKB_PRICE_POLICY, yt_client)
    # # table_list = [
    #     # '//home/x-products/production/services/logs/datacloud_score_api/production/events/history/2018-11-01',
    #     '//home/x-products/production/services/logs/datacloud_score_api/production/events/history/2018-11-02',
    #     '//home/x-products/production/services/logs/datacloud_score_api/production/events/history/2018-11-03',
    #     '//home/x-products/production/services/logs/datacloud_score_api/production/events/history/2018-11-04',
    # ]

    # LOG_HISTORY_DIR = '//home/x-products/production/services/logs/datacloud_score_api/production/events/history'
    # import yt.wrapper as yt_wrapper
    # yt_wrapper.config.set_proxy(YT_PROXY)
    # for table in table_list:#yt_wrapper.list(LOG_HISTORY_DIR, absolute=True):
    #     if len(table.split('/')[-1]) == len('2018-09-01'):
    #         print table
    #         calc_money_daily_table(table, yt_client)
    # # table = '//home/x-products/production/services/logs/datacloud_score_api/production/events/history/2018-07'
    # # calc_money_monthly_table(table, yt_client)
    # # schema_dict, counter, labels_keys = upload_table_to_solomon(table, yt_client, send_to_solomon=False)
    # # results = parse_schema_dict(schema_dict, labels_keys)
    # # print results
    # # # print results
    # print 'Drone'

    # #### NEW TABLE #####
    # test_table = '//home/x-products/production/services/logs/datacloud_score_api/production/responses/2018-12-16'
    # test_table2 = '//home/x-products/production/services/logs/datacloud_score_api/production/responses/2018-11'
    # calc_money_monthly_table(test_table2, yt_client)
    # a, b = read_router_qloud_table_into_schema_dict(test_table2, yt_client)
    # results = parse_schema_dict(a, b)
    # money_dict, SLA_dict, result_by_contract = money_and_SLA_by_results(
    #     results,
    #     '2018-11',
    #     yt_client,
    #     finalized=True,
    #     use_min_price=True
    # )
    # print money_dict, SLA_dict, result_by_contract
    # print parse_schema_dict(a, b)

    for table in yt_client.list('//home/x-products/production/services/logs/datacloud_score_api/production/responses', absolute=True):
        date = table.split('/')[-1]
        if len(date) > 7:
            print table
            calc_money_daily_table(table, yt_client)
            print 'Done!'
    table_list = [
        # '//home/x-products/production/services/logs/datacloud_score_api/production/events/history/2018-11-01',
        '//home/x-products/production/services/logs/datacloud_score_api/production/events/history/2018-11-02',
        '//home/x-products/production/services/logs/datacloud_score_api/production/events/history/2018-11-03',
        '//home/x-products/production/services/logs/datacloud_score_api/production/events/history/2018-11-04',
    ]

    LOG_HISTORY_DIR = '//home/x-products/production/services/logs/datacloud_score_api/production/events/history'
    import yt.wrapper as yt_wrapper
    yt_wrapper.config.set_proxy(YT_PROXY)
    for table in table_list:  # yt_wrapper.list(LOG_HISTORY_DIR, absolute=True):
        if len(table.split('/')[-1]) == len('2018-09-01'):
            print table
            calc_money_daily_table(table, yt_client)
    # table = '//home/x-products/production/services/logs/datacloud_score_api/production/events/history/2018-07'
    # calc_money_monthly_table(table, yt_client)
    # schema_dict, counter, labels_keys = upload_table_to_solomon(table, yt_client, send_to_solomon=False)
    # results = parse_schema_dict(schema_dict, labels_keys)
    # print results
    # # print results
    print('Drone')
