import copy
import time
import yt.wrapper as yt_wrapper
from collections import Counter
from datacloud.dev_utils.logging.logger import get_basic_logger
from datacloud.dev_utils.solomon.solomon_utils import post_schema_to_solomon, str2ts
from datacloud.dev_utils.status_db.task import Status
from datacloud.dev_utils.yt import yt_utils
from datacloud.config.yt import YT_PROXY


logger = get_basic_logger(__name__)
INPUT_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'


# Typical scheme to publish on Solomon
SCHEMA_EXAMPLE = {
    "commonLabels": {
        "project": "bar",
        "cluster": "datacloud",
        "service": "scoreapi"
    },
    "sensors": [
        {
            "labels": {"status": "has_score", "type": "response_one_by_one"},
            "ts": "2014-11-10T13:14:15Z",
            "value": 456
        },
        {
            "labels": {"status": "no_score", "type": "response_one_by_one"},
            "ts": "2014-11-10T13:14:15Z",
            "value": 456
        },
        {
            "labels": {"status": "timeout", "type": "response_one_by_one"},
            "ts": "2014-11-10T13:14:15Z",
            "value": 456
        }
    ]
}

LOG_STREAM_DIR = '//home/x-products/production/services/logs/datacloud_score_api/production/events/stream/5min'
LOG_HISTORY_DIR = '//home/x-products/production/services/logs/datacloud_score_api/production/events/history'


def add_row_to_sensors(schema_dict, labels, ts, value=1):
    """
    Function adds sensor with 'labels' field to current schema
    """
    schema_dict[(labels, ts)] += 1
    return schema_dict


def post_schema_dict(schema_dict, schema, labels_keys, batches=5000):
    counter = 0
    total_sensors = len(schema_dict)
    for key, value in schema_dict.items():
        schema['sensors'].append({
            'labels': dict((key, value) for (key, value) in zip(labels_keys, key[0])),
            'ts': key[1],
            'value': value
        })
        counter += 1
        if counter % batches == 0:
            post_schema_to_solomon(schema)
            logger.info('Uploaded {}/{}'.format(counter, total_sensors))
            schema['sensors'] = []
            time.sleep(1)

    if len(schema['sensors']) != 0:
        logger.info('Uploaded {}/{}'.format(counter, total_sensors))
        post_schema_to_solomon(schema)


def check_items(row, ids_name):
    """
    Function checks if there is any specified ids for matching in input query
    """
    items = row['context']['query']['user_ids'].get(ids_name, [])
    if not items:
        return False
    item = items[0]
    return len(item) > 0 and item.values()[0] not in ['', 'null', None]


def extract_labels(row):
    """
    Function extracts 'lables' fields for sensor
    """
    labels_list = []
    status_dict = {None: 'timeout', True: 'has_score', False: 'no_score'}
    if row.get('context', {}).get('query') and isinstance(row['context']['query'], dict) and 'scores' in row['context']['query']:
        for score in row['context']['query']['scores']:
            score_name = score['score_name']
            labels = {}
            labels['type'] = 'response_one_by_one'
            labels['partner'] = row['partner_id']
            labels['score_name'] = score_name
            labels['maching_by_phone'] = check_items(row, 'phones')
            labels['matching_by_email'] = check_items(row, 'emails')
            labels['matching_by_cookie'] = check_items(row, 'cookies')
            response = row['context'].get('response')
            if response is None:
                labels['type'] = status_dict[response]
            else:
                for score_response in response['scores']:
                    if score_response['score_name'] == score_name:
                        labels['type'] = status_dict[score_response['has_score']]
            labels_list.append(labels)
    else:
        labels_list = []
    return labels_list


def post_with_time_aggregation(schema_dict, row, cur_ts, counter):
    """
    Functions digest new row from table.
    If timestamp is the same as it was on previous timestamp it just add
    more sensors to current schema ( value += 1 if there is a sensor like new one in the sensors,
    or new sensor to sensors).
    If timestamp is different from the previous, function posts current schema to solomon,
    make new sensors empty and add new sensor as first one in new schema
    """
    # logger.info(row)
    labels_list = extract_labels(row)
#        post_schema_to_solomon(schema)
#        schema['sensors'] = []
    for labels in labels_list:
        schema_dict = add_row_to_sensors(schema_dict, tuple(labels.values()), cur_ts, value=1)
        counter += 1
    return schema_dict, counter


def upload_table_to_solomon(table, yt_client, send_to_solomon=True):
    """
    Takes table and pushes each row of the table
    to post_with_time_aggregation
    """
    counter = 0
    schema = copy.copy(SCHEMA_EXAMPLE)
    schema['sensors'] = []
    schema_dict = Counter()
    labels_keys = None
    for row in yt_client.read_table(table):
        if labels_keys is None:
            labels = extract_labels(row)
            if labels:
                labels_keys = labels[0].keys()
        cur_ts = str2ts(row['timestamp'], INPUT_TIME_FORMAT)
        schema_dict, counter = post_with_time_aggregation(
            schema_dict,
            row,
            cur_ts,
            counter
        )

    assert labels_keys is not None, 'Not found labels_keys!'
    if send_to_solomon:
        post_schema_dict(schema_dict, schema, labels_keys, batches=10000)
    return schema_dict, counter, labels_keys


def run_upload_table_to_solomon(task):
    logger.info('[SOLOMON] Upload table to solomon: {}'.format(task))
    yt_client = yt_utils.get_yt_client(YT_PROXY)
    table = task.data['table_path']
    logger.info(' Start upload table: {}'.format(table))
    if not yt_client.exists(table):
        logger.warn('There is no input table on YT {}'.format(table))
        return [task.make_done(new_status=Status.SKIPPED)]
    sort_yt_table(table, yt_client=yt_client)
    upload_table_to_solomon(table, yt_client)
    return [task.make_done()]


def run_upload_history_table_to_solomon(task):
    logger.info('[SOLOMON] Upload history table to solomon: {}'.format(task))
    table = task.data['table_path']
    yt_client = yt_utils.get_yt_client(YT_PROXY)
    logger.info(' Start upload table: {}'.format(table))
    if not yt_client.exists(table):
        logger.warn('There is no input table on YT {}'.format(table))
        return [task.make_done(new_status=Status.SKIPPED)]
    # sort_yt_table(table, yt_client=yt_client)
    schema_dict, _, labels_keys = upload_table_to_solomon(table, yt_client)
    return [task.make_done()]


def sort_yt_table(table, yt_client):
    if str(yt_client.get_attribute(table, 'sorted')) == 'false' or \
       list(yt_client.get_attribute(table, 'sorted_by')) != ['timestamp']:
        yt_client.run_sort(table, sort_by='timestamp')


def detect_ready_api_stream_logs(date_time, days=None):
    for table in yt_wrapper.list(LOG_STREAM_DIR, absolute=True):
        yield table, {'table_path': table}


def detect_ready_api_history_logs_monthly(date_time, days=None):
    for table in yt_wrapper.list(LOG_HISTORY_DIR, absolute=True):
        if len(table.split('/')[-1]) > 4:
            yield table, {'table_path': table}


def example():
    """
    Example of uploading all the tables from LOG_STREAM_DIR to solomon
    """
    import yt.wrapper as yt_client
    yt_client.config.set_proxy(YT_PROXY)
    added_tables = set()
    is_infinite = True
    counter = 0
    while is_infinite:
        table_list = yt_client.list(LOG_STREAM_DIR, absolute=True)[::-1]
        if len(table_list) > 0:
            for table in table_list:
                sort_yt_table(table, yt_client=yt_client)
                if table not in added_tables:
                    counter += upload_table_to_solomon(table, yt_client=yt_client)
                    logger.info('Added sensors %d' % counter)
                    logger.info('%s sent to solomon' % table.split('/')[-1])
                    added_tables.add(table)
        is_infinite = True
    time.sleep(300)
