# -*- coding: utf-8 -*-

import saas.tools.ssm.ssm_quota_management as ssm_quota_management
import math


def append_quotas(resources_data, quotas_data):
    for service in resources_data:
        for quota in quotas_data:
            if service == quota['quota_abc_name']:
                resources_data[service]['quota'] = {}
                resources_data[service]['quota_total'] = {}
                for resource in ['CPU', 'RAM', 'HDD', 'SSD']:
                    for loc in quota[resource]:
                        if loc not in resources_data[service]['quota']:
                            resources_data[service]['quota'][loc] = {}
                        resources_data[service]['quota'][loc][resource] = quota[resource][loc]
    return resources_data


def append_resources(src_data, dst_data, exclude_resource=None):
    for loc in dst_data:
        src_data[loc] = src_data[loc] if loc in src_data.keys() else {}
        for res, cnt in dst_data[loc].items():
            if (exclude_resource and res == exclude_resource) or not cnt:
                continue
            src_data[loc][res] = (src_data[loc][res] + int(cnt)) if src_data[loc].get(res) else int(cnt)
    return src_data


def _append_total(services_list, service_name, service):
    services_list.append((int(service['resources_total'].get('CPU', 0)), service_name, service))


def _sorted_by_total(services_list):
    return sorted(services_list, key=lambda x: x[0], reverse=True)


def calculate_service_quotas(services_data, quotas_data):
    service_quotas_data = {}
    cur_q = None
    cur_s = None
    prep_s_backbone = False
    for service in sorted(services_data):
        name = service['service_name']
        service_abc = service['service_abc']
        quota_abc = service['quota_abc']

        if cur_q != quota_abc['name']:
            cur_q = quota_abc['name']

            service_quotas_data[cur_q] = prepare_service_data(quota_abc['id'], quota_abc['hr_name'])
            service_quotas_data[cur_q]['projects'] = {}
            if cur_s == service_abc['name']:
                prep_s_backbone = True

        if cur_s != service_abc['name'] or prep_s_backbone:
            cur_s = service_abc['name']

            service_quotas_data[cur_q]['projects'][cur_s] = prepare_service_data(
                quota_abc['id'], service_abc['hr_name']
            )
            service_quotas_data[cur_q]['projects'][cur_s]['services'] = {}
            prep_s_backbone = False

        current_project = service_quotas_data[cur_q]['projects'][cur_s]

        # Fill service data
        if name not in current_project['services']:
            current_project['services'][name] = {
                'resources': service['usage'],
                'resources_total': calc_total_res_service({}, service['usage']),
                'ctype': service['service_ctype'],
            }
        else:
            current_project['services'][name]['resources'] = append_resources(
                current_project['services'][name]['resources'],
                service['usage']
            )
            current_project['services'][name]['resources_total'] = calc_total_res_service(
                current_project['services'][name]['resources_total'],
                service['usage']
            )

        service_quotas_data[cur_q]['resources'] = append_resources(
            service_quotas_data[cur_q]['resources'],
            service['usage'],
            exclude_resource='CPU_LIMIT',
        )

        current_project['resources'] = append_resources(
            current_project['resources'],
            service['usage'],
            exclude_resource='CPU_LIMIT',
        )
    # Append quotas to resources table
    append_quotas(service_quotas_data, quotas_data)
    # Calc total res for metaprj and prj
    calc_total_res_project(service_quotas_data)
    # Calc available resources
    calc_quotas_left(service_quotas_data)
    # Convert resources to human-readable format
    make_hr_resources(service_quotas_data)

    sorted_mp = []
    for mp, service in service_quotas_data.iteritems():

        sorted_projects = []
        for project_name, project in service['projects'].iteritems():

            sorted_saas_services = []
            for saas_service_name, saas_service in project['services'].iteritems():
                _append_total(sorted_saas_services, saas_service_name, saas_service)

            project['sorted_services'] = _sorted_by_total(sorted_saas_services)
            _append_total(sorted_projects, project_name, project)

        service['sorted_projects'] = _sorted_by_total(sorted_projects)
        _append_total(sorted_mp, mp, service)

    service_quotas_data['sorted_mp'] = _sorted_by_total(sorted_mp)
    return service_quotas_data


def calc_total_res_service(resources_before, resources_data):
    for loc in resources_data:
        for res, val in resources_data[loc].items():
            if val:
                if res not in resources_before.keys() or not resources_before[res]:
                    resources_before[res] = 0
                resources_before[res] += val
    return resources_before


def calc_total_res_project(res_data):
    for q in res_data:
        if 'quota' in res_data[q]:
            res_data[q]['quota_total'] = calc_total_res_service(res_data[q]['quota_total'], res_data[q]['quota'])
        res_data[q]['resources_total'] = calc_total_res_service(
            res_data[q]['resources_total'], res_data[q]['resources']
        )
        for p in res_data[q]['projects']:
            res_data[q]['projects'][p]['resources_total'] = calc_total_res_service(
                res_data[q]['projects'][p]['resources_total'],
                res_data[q]['projects'][p]['resources']
            )

    return res_data


def calc_quotas_left(service_quotas_data):
    for service in service_quotas_data:
        if 'quota' not in service_quotas_data[service].keys():
            continue
        available_resources = {'by_dc': {}, 'total': {}}
        for loc in service_quotas_data[service]['resources']:
            if loc in service_quotas_data[service]['quota']:
                if loc not in available_resources['by_dc']:
                    available_resources['by_dc'][loc] = {}
                for res in service_quotas_data[service]['resources'][loc]:
                    available_resources['by_dc'][loc][res] = (
                        service_quotas_data[service]['quota'][loc].get(res, 0)
                        - service_quotas_data[service]['resources'][loc][res]
                    )
        for res in service_quotas_data[service]['resources_total']:
            available_resources['total'][res] = (
                service_quotas_data[service]['quota_total'].get(res, 0)
                - service_quotas_data[service]['resources_total'][res]
            )
        service_quotas_data[service]['resources_avail'] = available_resources['by_dc']
        service_quotas_data[service]['resources_avail_total'] = available_resources['total']


def convert_all_locations(res_data):
    if len(res_data) > 0:
        for loc in res_data:
            res_data[loc] = convert_resources_to_hr(res_data[loc])
    return res_data


def convert_resources_to_hr(resources):
    resources_to_delete = []  # Clear resources with None: CPU_LIMIT etc.

    for resource in resources:
        value = resources[resource]
        if value is None:
            resources_to_delete.append(resource)
        if 'CPU' in resource:
            resources[resource] = str(value / 1000 if value else 0)
        else:
            resources[resource] = str(value / 1024 / 1024 / 1024)

    for resource in resources_to_delete:
        resources.pop(resource)
    return resources


def make_hr_resources(res_data):
    for q in res_data:
        prepare_resources_to_hr(res_data[q])
        for p in res_data[q]['projects']:
            prepare_resources_to_hr(res_data[q]['projects'][p])
            for s in res_data[q]['projects'][p]['services']:
                prepare_resources_to_hr(res_data[q]['projects'][p]['services'][s])


def prepare_resources_to_hr(resources):
    for key in resources:
        if 'resources' in key or 'quota' in key:
            if 'total' in key:
                convert_resources_to_hr(resources[key])
            else:
                convert_all_locations(resources[key])


def prepare_service_data(abc_id, hr_name):
    return {
        'id': abc_id,
        'hr_name': hr_name,
        'resources': {},
        'resources_total': {}
    }


def get_quotas_data():
    # Get table from YT
    services_data = ssm_quota_management.yt_get_table()
    quotas_data = ssm_quota_management.get_quotas_table()
    # Services quota backbone
    return calculate_service_quotas(services_data, quotas_data)


def calc_resources_for_kv(rps, index_size, index_type):
    """
    Calculate resources for KV services
    :param rps: type int
    :param index_size: type int (bytes)
    :param index_type: type str
    :return: type dict
    """
    # Defines
    memory_count = 10.0
    rps_per_core = 2000.0
    rps_per_location = rps * 0.5
    index_size_per_slot = 107374182400.0  # 100Gb
    min_replicas_count = 2.0
    min_shards_count = 1.0
    max_cpu_cores = 3.0

    # Calculation
    shards_count = max(math.ceil(index_size / index_size_per_slot), min_shards_count)
    cpu_count = min(round(math.sqrt(math.ceil(rps_per_location / shards_count / rps_per_core))), max_cpu_cores)
    replicas_count = max(
        math.ceil(rps_per_location / float(shards_count * cpu_count * rps_per_core)),
        min_replicas_count
    ) + 1
    cpu_count += 1  # +1 core for merge
    if index_type in ['ferryman_mapreduce', 'logbroker']:
        cpu_count += 1  # + One core for ytpull or logbroker
    return {
        'memory': memory_count,
        'shards_count': shards_count,
        'replicas_count': replicas_count,
        'cpu': cpu_count
    }


def calc_resources_for_search(rps, index_size, index_type):
    """
    Calculate resources for "search" services
    :param rps: type int
    :param index_size: type int (bytes)
    :param index_type: type str
    :return: type dict
    """
    memory_count = 20.0
    rps_per_core = 20.0
    rps_per_location = rps * 0.5
    index_size_per_slot = 26843545600.0  # 25Gb
    min_replicas_count = 2.0
    min_shards_count = 1.0
    max_cpu_cores = 10.0

    # Calculation
    shards_count = max(math.ceil(index_size / index_size_per_slot), min_shards_count)
    cpu_count = min(round(math.sqrt(math.ceil(rps_per_location / shards_count / rps_per_core))), max_cpu_cores)
    replicas_count = max(
        math.ceil(rps_per_location / float(shards_count * cpu_count * rps_per_core)),
        min_replicas_count
    ) + 1
    cpu_count += 1  # +1 core for merge
    if index_type in ['ferryman_mapreduce', 'logbroker']:
        cpu_count += 1  # + 1 core for ytpull or logbroker
    return {
        'memory': memory_count,
        'shards_count': shards_count,
        'replicas_count': replicas_count,
        'cpu': cpu_count
    }
