# -*- coding: utf-8 -*-
"""
Created on Dec 9, 2013

@author: noob
"""
import calendar
import collections
import logging
from collections import defaultdict, OrderedDict


import pytz
from django.core.exceptions import ObjectDoesNotExist

from common.models import Job, Component, JobTrail, RegressionComment, KPI
from common.util.meta import count_percentage, format_job_dates
from common.util.clients import ClickhouseClient
from common.util.decorators import memoized_property
from regression.views.sla_processor import kpi_sla_processor_map


class KpiPlot(object):
    def __init__(self, kpi_obj, job_limit=30, preview=False):
        """

        :param kpi_obj: KPI OBJECT
        :param job_limit: limits jobs queryset
        """
        self.kpi_obj = kpi_obj
        self.job_limit = job_limit
        self.preview = preview

        self.ch_client = ClickhouseClient()

    @property
    def data(self):
        """
        main dict for plot configuration;
        """
        try:
            data = {'title': self.title,
                    'subtitle': self.subtitle,
                    'series': self.series_data,
                    'events_series': {
                        'enabled': True if self.events_series else False,
                        'data': self.events_series[0]['data'],
                    },
                    'sla_pass': self.sla_pass,
                    'overrides': self.overrides,
                    'yaxis_label': self.yaxis_label,
                    'xaxis': list(self.xaxis_points.values())}
            if self.component_obj.job_order == 'timeline':
                xaxis_job_ids = ('{} |{}'.format(job_obj.n, str(job_obj.ver).strip()) for job_obj in self.jobs)
                xaxis_job_ids = dict(zip(data['xaxis'], xaxis_job_ids))
                data['xaxis_type'] = 'datetime'
                data['xaxis_job_ids'] = xaxis_job_ids  # to click plot points
                data['tickInterval'] = len(self.jobs) // 5 or 1
            else:
                xaxis_job_ids = [
                    '{} |{} |{}'.format(
                        str(job_obj.n),
                        str(job_obj.ver).strip(),
                        job_obj.fd.strftime('%Y-%m-%d %H:%M:%S')
                    )
                    for job_obj in self.jobs
                ]
                xaxis_job_ids = dict(zip(data['xaxis'], xaxis_job_ids))
                data['xaxis_type'] = 'linear'
                data['xaxis_job_ids'] = xaxis_job_ids
                data['tickInterval'] = len(self.jobs) // 15 or 1
        except:
            logging.exception('')
            data = None
        return data

    @property
    def sla_pass(self):
        kpi_sla_processor = kpi_sla_processor_map[self.kpi_obj.ktype.split('__')[0]](self.kpi_obj)
        sla_pass = kpi_sla_processor.check()
        if type(sla_pass) == bool:
            return sla_pass
        else:
            return True

    @property
    def series_data(self):
        """
        dict with parameters for each serie (or graph) configuration;

        """
        series_data = []
        sorted_graphs = sorted(int(g) for g in self.kpi_obj.params['graphs'] if g.isdigit())
        sorted_graphs += [g for g in self.kpi_obj.params['graphs'] if not g.isdigit()]
        for graph in (str(g) for g in sorted_graphs):
            serie = {
                'marker': {
                    'symbol': 'circle',
                    'lineColor': None,
                },
                'id': graph,
                'name': graph,
                'color': self.color_mapping(graph),
                'type': self.yaxis_type,
            }
            try:
                serie['data'] = self.graphs_values[graph]
            except KeyError:
                logging.error('KPI KEYERROR')
                serie['data'] = [0] * len(self.jobs)
            series_data.append(serie)

        series_data.extend(self.events_series)

        return series_data

    @property
    def graphs_values(self):
        raise NotImplementedError()

    @property
    def title(self):
        """
        plot title based on kpi description and parameters;
        """
        kpi_plot_title = KPI.kpitype_map.get(self.kpi_obj.ktype, '').split(':')[0]
        return kpi_plot_title

    @property
    def subtitle(self):
        """
        plot subtitle based on kpi description and parameters;
        parameters separated by '|'
        the last '|' is deleted
        """
        kpi_plot_subtitle = ''
        try:
            for key, value in self.kpi_obj.params.items():
                if key == 'target' and value:
                    kpi_plot_subtitle += ' @ {} | '.format(value.host)
                elif key == 'metric' and value:
                    kpi_plot_subtitle += value.code
                elif key == 'case' and value:
                    kpi_plot_subtitle += 'Гильза: {} | '.format(value)
            if self.kpi_obj.params['sla']:
                kpi_plot_subtitle += 'SLA: {}'.format(' | '.join(
                    '{} {} {}'.format(
                        key, self.kpi_obj.params['sla'][key]['operator'], self.kpi_obj.params['sla'][key]['threshold']
                    )
                    for key in list(self.kpi_obj.params['sla'].keys())
                ))
        except:
            logging.exception('')
        if kpi_plot_subtitle.endswith(' | '):
            kpi_plot_subtitle = kpi_plot_subtitle[:-3]
        return kpi_plot_subtitle

    @property
    def component_obj(self):
        try:
            return Component.objects.get(n=self.kpi_obj.component_id)
        except:
            logging.exception('Could not get component for kpi {} due to:'.format(self.kpi_obj.n))
            return None

    @memoized_property
    def jobs(self):
        """
        returns list of Job OBJECTS
        """
        try:
            include_qs = []
            exclude_qs = []
            for quit_status in self.component_obj.include_qs.split(','):
                try:
                    include_qs.append(int(quit_status))
                except ValueError:
                    pass
            for quit_status in self.component_obj.exclude_qs.split(','):
                try:
                    exclude_qs.append(int(quit_status))
                except ValueError:
                    pass

            jobs = Job.objects.filter(component=self.component_obj.n,
                                      td__isnull=False).order_by('n')

            # limit jobs by quit_status
            if include_qs:
                jobs = jobs.filter(quit_status__in=include_qs)
            elif exclude_qs:
                jobs = jobs.exclude(quit_status__in=exclude_qs)

            # limit jobs by time (not used)
            # if self.job_limit == 'month':
            # current_month = datetime.now().month
            # if current_month > 1:
            #     month_ago = datetime.now() - \
            #         timedelta(days=calendar.monthrange(datetime.now().year, current_month - 1)[1])
            # else:
            #     month_ago = datetime.now() - timedelta(days=31)
            #     jobs = jobs.filter(fd__gt=month_ago)
            # elif self.job_limit == 'halfyear':
            #     halfyear_ago = datetime.now() - timedelta(days=183)
            #     jobs = jobs.filter(fd__gt=halfyear_ago)
            # elif self.job_limit == 'year':
            #     year_ago = datetime.now() - timedelta(days=365)
            #     jobs = jobs.filter(fd__gt=year_ago)
            # else:
            if self.component_obj.job_order == 'ver_ordered':
                jobs = jobs.order_by('ver', 'n')
            if str(self.job_limit).isdigit():
                jobs = [j for j in jobs][-int(self.job_limit):]
            return [j for j in jobs]
        except:
            logging.exception('Could not get job objects for project {}, component {} due to:'.format(
                              self.component_obj.tag, self.component_obj.name))
            return []

    @memoized_property
    def xaxis_points(self):
        return OrderedDict(
            (
                (job_obj.n,
                 int(calendar.timegm(pytz.timezone('Europe/Moscow').localize(
                     job_obj.fd).timetuple())) * 1000 if self.component_obj.job_order == 'timeline' else job_obj.n)
                for job_obj in self.jobs
            )
        )

    @memoized_property
    def comments(self):
        try:
            comments = RegressionComment.objects.filter(job_id__in=[j.n for j in self.jobs])
            comments_dict = defaultdict(str)  # задел на будущее, сейчас пока на стрельбу всегда один коммент.
            for comment in comments:
                comments_dict[comment.job_id] += '  {} \n-- {}  '.format(comment.text, comment.author)
            if self.component_obj.job_order == 'timeline':
                comments_serie_data = [{'x': self.xaxis_points[job_obj.n],
                                        'text': comments_dict[job_obj.n],
                                        'title': comments_dict[job_obj.n]}
                                       for job_obj in self.jobs if comments_dict.get(job_obj.n, '')]
            else:
                comments_serie_data = [{
                    'x': self.jobs.index(job_obj),
                    'text': comments_dict[job_obj.n],
                    'title': comments_dict[job_obj.n]}
                    for job_obj in self.jobs if comments_dict.get(job_obj.n, '')]
            return comments_serie_data
        except Exception as exc:
            logging.exception('Could not get comments for PROJECT {}, COMPONENT {} due to: {}'.format(self.component_obj.tag,
                              self.component_obj.name, exc.__class__.__name__))

    @memoized_property
    def comments_dict(self):
        """
        Shows if a job has a comment
        """
        comments_dict = dict(zip((j.n for j in self.jobs), (c['text'] if c else None for c in self.comments)))
        return comments_dict

    @staticmethod
    def color_mapping(graph):
        """
        default is none no matter what
        :param graph:
        """
        color_map = {
            'minimum': 'RoyalBlue',
            'average': 'Gold',
            'median': 'Chocolate',
            'maximum': 'FireBrick',
            'stddev': 'DarkGreen',
        }
        try:
            color = color_map[graph]
            return color
        except KeyError:
            logging.warning('Unexpected graph: {}'.format(graph))
            return None

    @property
    def overrides(self):
        """
        returns dict with parameters to override default plot config
        """
        return {}

    @property
    def yaxis_label(self):
        return ''

    @property
    def yaxis_type(self):
        """
        line by default
        """
        return 'line'

    @property
    def flagged_marker(self):
        return {
            'symbol': 'diamond',
            'radius': 4,
            'fillColor': '#666',
        }

    @property
    def events_series(self):
        events_series = [{
            'marker': {
                'enabled': False,
            },
            'name': 'comments',
            'type': 'spline',
            'showInLegend': False,
            'yAxis': 1,
            'lineWidth': 0,
            'data': self.comments
        }]
        return events_series


class ImbalanceKpiPlot(KpiPlot):
    @property
    def graphs(self):
        return ['imbalance']

    @property
    def yaxis_label(self):
        return ' rps'

    @memoized_property
    def graphs_values(self):

        graphs_values = {'imbalance': []}

        if self.component_obj.job_order == 'timeline':
            for job_obj in self.jobs:
                value = {'y': job_obj.imbalance, 'x': self.xaxis_points[job_obj.n]}
                if job_obj.flag:
                    value['marker'] = self.flagged_marker
                graphs_values['imbalance'].append(value)
        else:
            for job_obj in self.jobs:
                value = {
                    'y': job_obj.imbalance,
                }
                if job_obj.flag:
                    value['marker'] = self.flagged_marker
                graphs_values['imbalance'].append(value)

        return graphs_values


class QuantilesKpiPlot(KpiPlot):
    @property
    def series_data(self):
        """
        dict with parameters for each serie (or graph) configuration;

        """
        series_data = []
        for graph in self.kpi_obj.params['graphs']:
            serie = {
                'marker': {
                    'symbol': 'circle',
                    'lineColor': None,
                },
                'id': graph,
                'name': graph,
                'color': self.color_mapping(graph),
                'type': 'area' if not graph == 'q99' else 'line',
            }
            try:
                serie['data'] = self.graphs_values[graph]
            except KeyError:
                logging.exception('KEYERROR')
                serie['data'] = [None] * self.jobs.count()
            series_data.append(serie)

        series_data.extend(self.events_series)

        return series_data

    @property
    def yaxis_label(self):
        return ' ms'

    @property
    def yaxis_type(self):
        """
        line by default
        """
        return 'area'

    @staticmethod
    def color_mapping(quantile):
        color_map = {
            'q50': '#f85750',
            'q75': 'Coral',
            'q80': 'DarkOrange',
            'q85': 'Orange',
            'q90': 'gold',
            'q95': '#b8e13d',
            'q98': '#66af5f',
            'q99': '#b2b8c8',
        }
        try:
            color = color_map[quantile]
            return color
        except KeyError:
            logging.warning('Unexpected quantile: {}'.format(quantile))
            return None

    @property
    def graphs(self):
        return 'q50', 'q75', 'q80', 'q85', 'q90', 'q95', 'q98', 'q99'

    @memoized_property
    def graphs_values(self):
        data = {graph: [] for graph in self.kpi_obj.params['graphs']}
        for job_obj in self.jobs:
            try:
                assert not self.kpi_obj.params.get('case', '')
                jobtrail = JobTrail.objects.get(up=job_obj.n)
                if self.component_obj.job_order == 'timeline':
                    for graph in self.kpi_obj.params['graphs']:
                        value = {'y': jobtrail.__dict__[graph],
                                 'x': self.xaxis_points[job_obj.n],
                                 }
                        if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                            value['marker'] = self.flagged_marker
                        data[graph].append(value)
                else:
                    for graph in self.kpi_obj.params['graphs']:
                        value = {'y': jobtrail.__dict__[graph],
                                 }
                        if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                            value['marker'] = self.flagged_marker
                        data[graph].append(value)
            except (AssertionError, ObjectDoesNotExist):
                sql = '''
                    select
                    quantilesExactWeighted(0.50, 0.75, 0.80, 0.85, 0.90, 0.95, 0.98, 0.99)(bin, cnt)
                    from loaddb.rt_microsecond_histograms_buffer
                    where job_id={job}
                    and job_date=toDate({job_date})
                    and tag='{tag}'
                '''
                query_params = job_obj.basic_query_params.copy()
                query_params['tag'] = self.kpi_obj.params.get('case', '')
                quantiles_data = self.ch_client.select(sql, query_params=query_params)
                quantiles_data = [float(q) / 1000 for q in quantiles_data[0][0]] \
                    if quantiles_data \
                    else [0] * len(self.graphs)
                quantiles_data = dict(zip(self.graphs, quantiles_data))
                if self.component_obj.job_order == 'timeline':
                    for graph in self.kpi_obj.params['graphs']:
                        value = {
                            'x': self.xaxis_points[job_obj.n] if self.component_obj.job_order == 'timeline' else None,
                            'y': quantiles_data.get(graph, 0)
                        }
                        if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                            value['marker'] = self.flagged_marker
                        data[graph].append(value)
                else:
                    for graph in self.kpi_obj.params['graphs']:
                        value = {'y': quantiles_data.get(graph, 0)}
                        if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                            value['marker'] = self.flagged_marker
                        data[graph].append(value)
        return data


class TrailKpiPlot(KpiPlot):
    @memoized_property
    def graphs_values(self):

        # FIXME: Вилка

        to_select = ''
        sql_func_map = {
            'maximum': 'max',
            'average': 'avg',
            'minimum': 'min',
            'stddev': 'stddevPop',
            'median': 'median',
        }
        for graph in self.kpi_obj.params['graphs']:
            if self.kpi_obj.ktype.split('__')[1] in ('resps', 'threads', 'input', 'output'):
                to_select += 'round({}({}),3),'.format(
                    sql_func_map[graph], self.params_sql_mapping[self.kpi_obj.ktype.split('__')[1]])
            else:
                to_select += 'round({}({})/1000,3),'.format(
                    sql_func_map[graph], self.params_sql_mapping[self.kpi_obj.ktype.split('__')[1]])
        if to_select:
            to_select = to_select[:-1]

        sql = '''
            select {param}, job_id
            from loaddb.rt_microsecond_details_buffer
            where job_id in ({jobs})
            and job_date in ({job_dates})
            and tag = '{tag}'
            and resps != 0
            group by job_id
            '''
        query_params = {
            'jobs': ','.join(str(job_obj.n) for job_obj in self.jobs),
            'job_dates': format_job_dates(self.jobs),
            'tag': self.kpi_obj.params.get('case', ''),
            'param': to_select,
        }
        raw_data = []
        raw_data.extend(self.ch_client.select(sql, query_params=query_params))
        data = {graph: [] for graph in self.kpi_obj.params['graphs']}
        for job_obj in self.jobs:
            values = [v for v in raw_data if v[-1] == job_obj.n] or [[0] * len(self.kpi_obj.params['graphs'])]
            values = values[0]
            if self.component_obj.job_order == 'timeline':
                for graph in self.kpi_obj.params['graphs']:
                    value = {'x': self.xaxis_points[job_obj.n]}
                    try:
                        value['y'] = values[self.kpi_obj.params['graphs'].index(graph)]
                    except (KeyError, ValueError):
                        value['y'] = 0
                    if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                        value['marker'] = self.flagged_marker
                    data[graph].append(value)
            else:
                for graph in self.kpi_obj.params['graphs']:
                    value = {}
                    try:
                        value['y'] = values[self.kpi_obj.params['graphs'].index(graph)]
                    except (KeyError, ValueError):
                        value['y'] = 0
                    if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                        value['marker'] = self.flagged_marker
                    data[graph].append(value)
        return data

    @property
    def params_sql_mapping(self):
        return {
            'resps': 'resps',
            'expect': '(connect_time_sum + send_time_sum + latency_sum + receive_time_sum)/resps',
            'connect_time': 'connect_time_sum/resps',
            'send_time': 'send_time_sum/resps',
            'latency': 'latency_sum/resps',
            'receive_time': 'receive_time_sum/resps',
            'threads': 'threads',
            'input': 'igress',
            'output': 'egress',
        }

    @property
    def yaxis_label(self):
        labels = {
            'resps': '',
            'input': ' bytes',
            'output': ' bytes',
            'expect': ' ms',
            'connect_time': ' ms',
            'send_time': ' ms',
            'latency': ' ms',
            'receive_time ': ' ms',
            'threads': '',
        }
        try:
            yaxis_label = labels[self.kpi_obj.ktype.split('__')[1]]
        except:
            logging.exception('')
            yaxis_label = ''
        return yaxis_label


class NetCodesKpiPlot(KpiPlot):
    @property
    def series_data(self):
        """
        dict with parameters for each serie (or graph) configuration;

        """
        series_data = []
        sorted_graphs = sorted((graph for graph in list(self.graphs_values.keys()) if not str(graph).isdigit()), reverse=True)
        sorted_graphs += sorted((str(graph) for graph in list(self.graphs_values.keys()) if str(graph).isdigit()),
                                reverse=True)
        for graph in sorted_graphs:
            serie = {
                'marker': {
                    'symbol': 'circle',
                    'lineColor': None,
                },
                'id': graph if not str(graph) == '0' else 'Ok',
                'name': graph if not str(graph) == '0' else 'Ok',
                'color': self.color_mapping(graph),
                'type': 'line',
            }
            try:
                serie['data'] = self.graphs_values[graph]
            except KeyError:
                logging.exception('')
                serie['data'] = [0] * len(self.jobs)
            series_data.append(serie)

        series_data.extend(self.events_series)

        return series_data

    @staticmethod
    def color_mapping(graph):
        """
        default is none no matter what
        """
        return None

    @property
    def yaxis_label(self):
        labels = {
            'count': '',
            'percent': ' %',
        }
        try:
            yaxis_label = labels[self.kpi_obj.ktype.split('__')[1]]
        except (AttributeError, KeyError, IndexError):
            logging.exception('')
            yaxis_label = ''
        return yaxis_label

    def get_net_codes(self):
        net_codes = []
        for graph in self.kpi_obj.params['graphs']:
            if str(graph).isdigit():
                net_codes.append([int(graph)])
            elif all(symbol == 'x' for symbol in str(graph)):
                sql = '''select distinct code
                        from loaddb.net_codes_buffer
                        where job_id in ({jobs})
                        and job_date in ({job_dates})
                        order by code
                        '''
                query_params = {
                    'jobs': ','.join(str(int(job_obj.n)) for job_obj in self.jobs),
                    'job_dates': format_job_dates(self.jobs)
                }
                present_net_codes = self.ch_client.select(sql, query_params=query_params)
                net_codes.extend(present_net_codes or [[graph]])
            elif str(graph).find('x') != -1:
                sql = '''select distinct code
                        from loaddb.net_codes_buffer
                        where job_id in ({jobs})
                        and job_date in ({job_dates})
                        and match(toString(code), '{code_regexp}')
                        order by code
                        '''
                graph_regexp = r'^{}$'.format(r''.join('[0-9]' if symbol == 'x' else symbol for symbol in graph))
                query_params = {
                    'jobs': ','.join(str(int(job_obj.n)) for job_obj in self.jobs),
                    'job_dates': format_job_dates(self.jobs),
                    'code_regexp': graph_regexp,
                }
                present_net_codes = self.ch_client.select(sql, query_params=query_params)
                net_codes.extend(present_net_codes or [[graph]])
        return sorted(set(nc[0] for nc in net_codes))

    @memoized_property
    def graphs_values(self):
        net_codes = self.get_net_codes()
        data = {str(net_code): [] for net_code in net_codes}
        sql = '''
            select job_id, code, toUInt32(sum(cnt))
            from loaddb.net_codes_buffer
            where job_id in ({jobs})
            and job_date in ({job_dates})
            and tag = '{tag}'
            group by job_id, code
            order by job_id
            '''  # this way is better for percentage calculation
        query_params = {
            'jobs': ','.join(str(int(job_obj.n)) for job_obj in self.jobs),
            'job_dates': format_job_dates(self.jobs),
            'tag': self.kpi_obj.params.get('case', ''),
        }
        raw_data = self.ch_client.select(sql, query_params=query_params)
        jobs_codes = collections.defaultdict(dict)
        for value in raw_data:
            jobs_codes[str(value[0])][str(value[1])] = value[2]
        if self.kpi_obj.ktype.split('__')[1] == 'percent':
            for i in jobs_codes.items():
                jobs_codes[i[0]] = dict(zip(list(i[1].keys()), count_percentage(list(i[1].values()))))
        for job_obj in self.jobs:  # correctly sorted jobs
            for net_code in net_codes:
                value = {}
                if self.component_obj.job_order == 'timeline':
                    value['x'] = self.xaxis_points[job_obj.n]
                value['y'] = jobs_codes.get(str(job_obj.n), {}).get(str(net_code), 0)
                if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                    value['marker'] = self.flagged_marker
                data[str(net_code)].append(value)
        return data


class HTTPCodesKpiPlot(KpiPlot):
    @property
    def series_data(self):
        """
        dict with parameters for each serie (or graph) configuration;

        """
        series_data = []
        for graph in list(self.graphs_values.keys()):
            serie = {
                'marker': {
                    'symbol': 'circle',
                    'lineColor': None,
                },
                'id': graph,
                'name': graph,
                'color': self.color_mapping(graph),
                'type': 'line',
            }
            try:
                serie['data'] = self.graphs_values[graph]
            except KeyError:
                logging.exception('KEYERROR')
                serie['data'] = [0] * len(self.jobs)
            series_data.append(serie)

        series_data.extend(self.events_series)

        return series_data

    @staticmethod
    def color_mapping(http_code):
        color_map = {2: '#66af5f',
                     3: '#60becc',
                     4: 'Gold',  # FFE600',
                     5: '#ff3f3f', }
        try:
            color = color_map[int(str(http_code)[0])]
            return color
        except KeyError:
            logging.warning('Unexpected http_code: {}'.format(http_code))
            return None

    def get_http_codes(self):
        http_codes = []
        for graph in self.kpi_obj.params['graphs']:
            if str(graph).isdigit():
                http_codes.append([int(graph)])
            elif all(symbol == 'x' for symbol in str(graph)):
                sql = '''select distinct code
                        from loaddb.proto_codes_buffer
                        where job_id in ({jobs})
                        and job_date in ({job_dates})
                        order by code
                        '''
                query_params = {
                    'jobs': ','.join(str(int(job_obj.n)) for job_obj in self.jobs),
                    'job_dates': format_job_dates(self.jobs),
                }
                present_net_codes = self.ch_client.select(sql, query_params=query_params)
                http_codes.extend(present_net_codes or [[graph]])
            elif str(graph).find('x') != -1:
                sql = '''select distinct code
                        from loaddb.proto_codes_buffer
                        where job_id in ({jobs})
                        and job_date in ({job_dates})
                        and match(toString(code), '{code_regexp}')
                        order by code
                        '''
                graph_regexp = r'^{}$'.format(r''.join('[0-9]' if symbol == 'x' else symbol for symbol in graph))
                query_params = {
                    'jobs': ','.join(str(int(job_obj.n)) for job_obj in self.jobs),
                    'job_dates': format_job_dates(self.jobs),
                    'code_regexp': graph_regexp,
                }
                present_net_codes = self.ch_client.select(sql, query_params=query_params)
                http_codes.extend(present_net_codes or [[graph]])
        return sorted(set(str(hc[0]) for hc in http_codes))

    @property
    def yaxis_label(self):
        labels = {
            'count': '',
            'percent': ' %',
        }
        try:
            yaxis_label = labels[self.kpi_obj.ktype.split('__')[1]]
        except:
            logging.exception('')
            yaxis_label = ''
        return yaxis_label

    @memoized_property
    def graphs_values(self):
        http_codes = self.get_http_codes()
        data = {str(http_code): [] for http_code in http_codes}
        sql = '''
            select job_id, code, toUInt32(sum(cnt))
            from loaddb.proto_codes_buffer
            where job_id in ({jobs})
            and job_date in ({job_dates})
            and tag = '{tag}'
            group by job_id, code
            order by job_id
            '''  # this way is better for percentage calculation
        query_params = {
            'jobs': ','.join(str(int(job_obj.n)) for job_obj in self.jobs),
            'job_dates': format_job_dates(self.jobs),
            'tag': self.kpi_obj.params.get('case', ''),
        }
        raw_data = self.ch_client.select(sql, query_params=query_params)
        jobs_codes = collections.defaultdict(dict)

        for value in raw_data:
            jobs_codes[str(value[0])][str(value[1])] = value[2]

        if self.kpi_obj.ktype.split('__')[1] == 'percent':
            for i in jobs_codes.items():
                jobs_codes[i[0]] = dict(zip(list(i[1].keys()), count_percentage(list(i[1].values()))))
        for job_obj in self.jobs:  # correctly sorted jobs
            for http_code in http_codes:
                value = {}
                if self.component_obj.job_order == 'timeline':
                    value['x'] = self.xaxis_points[job_obj.n]
                value['y'] = jobs_codes.get(str(job_obj.n), {}).get(str(http_code), 0)

                if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                    value['marker'] = self.flagged_marker
                data[str(http_code)].append(value)
        return data


class TimeDistKpiPlot(KpiPlot):
    @property
    def yaxis_label(self):
        labels = {
            'count': '',
            'percent': ' %',
            'quantile': ' %',
        }
        try:
            yaxis_label = labels[self.kpi_obj.ktype.split('__')[1]]
        except:
            logging.exception('')
            yaxis_label = ''
        return yaxis_label

    @property
    def series_data(self):
        """
        dict with parameters for each serie (or graph) configuration;

        """
        series_data = []
        sorted_graphs = sorted((int(graph) for graph in self.kpi_obj.params['graphs'] if graph.isdigit()), reverse=True)
        sorted_graphs = (str(graph) for graph in sorted_graphs)
        for graph in sorted_graphs:
            serie = {
                'marker': {
                    'symbol': 'circle',
                    'lineColor': None,
                },
                'id': graph,
                'name': graph,
                'color': None,
                'type': self.yaxis_type,
            }
            try:
                serie['data'] = self.graphs_values[graph]
            except KeyError:
                serie['data'] = [0] * len(self.jobs)
            series_data.append(serie)

        series_data.extend(self.events_series)

        return series_data

    @memoized_property
    def graphs_values(self):
        bins = [str(b) for b in self.kpi_obj.params['graphs'] if str(b).isdigit()]
        data = {graph: [] for graph in bins}
        if self.kpi_obj.ktype.split('__')[1] == 'quantile':
            sql = '''select job_id, toUInt32(sum(cnt))
                        from loaddb.rt_microsecond_histograms_buffer
                        where job_id in ({jobs})
                        and job_date in ({job_dates})
                        group by job_id
                        order by job_id
                        '''
            query_params = {
                'jobs': ','.join(str(int(job_obj.n)) for job_obj in self.jobs),
                'job_dates': format_job_dates(self.jobs),
            }
            if self.jobs:
                raw_sum_counts = dict(self.ch_client.select(sql, query_params=query_params))
            else:
                raw_sum_counts = {}
            sum_counts = {job_obj.n: raw_sum_counts.get(job_obj.n, 1) for job_obj in
                          sorted(self.jobs, key=lambda j: j.n)}
            sql_for_bin = '''select job_id, toUInt32(sum(cnt))
                        from loaddb.rt_microsecond_histograms_buffer
                        where job_id in ({jobs})
                        and job_date in ({job_dates})
                        and bin <= {bin}
                        group by job_id
                        order by job_id
                        '''

            for b in bins:  # usually there are few bins in kpi params
                # Здесь корзинка как параметр, поэтому умножаем, а не делим
                query_params['bin'] = int(float(b) * 1000)
                bin_count = dict(self.ch_client.select(sql_for_bin, query_params=query_params)) \
                    if self.jobs else {}
                bin_count = {j.n: bin_count.get(j.n, 0)
                             for j in sorted(self.jobs, key=lambda j: j.n)}

                if self.component_obj.job_order == 'timeline':
                    for job_obj in self.jobs:
                        value = {'x': self.xaxis_points[job_obj.n],
                                 'y': round(float(bin_count[job_obj.n] * 100) / sum_counts[job_obj.n], 3)}
                        if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                            value['marker'] = self.flagged_marker
                        data[b].append(value)
                else:
                    for job_obj in self.jobs:
                        value = {'y': round(float(bin_count[job_obj.n] * 100) / sum_counts[job_obj.n], 3)}
                        if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                            value['marker'] = self.flagged_marker
                        data[b].append(value)
        else:
            sql = '''
                select job_id, toString(bin/1000), toUInt32(sum(cnt))
                from loaddb.rt_microsecond_histograms_buffer
                where job_id in ({jobs})
                and job_date in ({job_dates})
                and bin in ({bins})
                and tag = '{tag}'
                group by job_id, bin
                order by job_id
                '''  # this way is better for percentage calculation

            query_params = {
                'jobs': ','.join(str(int(job_obj.n)) for job_obj in self.jobs),
                'job_dates': format_job_dates(self.jobs),
                'tag': self.kpi_obj.params.get('case', ''),
                # Здесь корзинка как параметр, поэтому умножаем, а не делим
                'bins': ','.join(str(int(float(b) * 1000)) for b in bins),
            }

            raw_data = []
            if self.jobs:
                raw_data += self.ch_client.select(sql, query_params=query_params)

            if self.kpi_obj.ktype.split('__')[1] == 'percent':
                sql = '''select job_id, toUInt32(sum(cnt))
                        from loaddb.rt_microsecond_histograms_buffer
                        where job_id in ({job})
                        and job_date in ({job_dates})
                        group by job_id
                        order by job_id
                        '''
                query_params = {
                    'jobs': ','.join(str(int(job_obj.n)) for job_obj in self.jobs),
                    'job_dates': format_job_dates(self.jobs),
                }
                raw_sum_counts = dict(self.ch_client.select(sql, query_params=query_params))
                sum_counts = {job_obj.n: raw_sum_counts.get(job_obj.n, 0) for job_obj in
                              sorted(self.jobs, key=lambda j: j.n)}
            i = 0
            jobs_bins = {}
            for job_obj in sorted(self.jobs, key=lambda j: j.n):
                job_bins = {b: 0 for b in bins}
                while i < len(raw_data) and raw_data[i][0] == job_obj.n:  # thats why we have to sort jobs directly;
                    job_bins[raw_data[i][1]] = raw_data[i][2]
                    i += 1
                if self.kpi_obj.ktype.split('__')[1] == 'percent':
                    job_bins = {k: round(float(v * 100) / sum_counts[job_obj.n], 3) if sum_counts[job_obj.n] else 0 for
                                k, v in job_bins.items()}
                jobs_bins[job_obj.n] = job_bins
            for job_obj in self.jobs:  # correctly sorted jobs
                if self.component_obj.job_order == 'timeline':
                    for b in bins:
                        value = {'x': self.xaxis_points[job_obj.n], 'y': jobs_bins[job_obj.n][b]}
                        if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                            value['marker'] = self.flagged_marker
                        data[b].append(value)
                else:
                    for b in bins:
                        value = {'y': jobs_bins[job_obj.n][b]}
                        if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                            value['marker'] = self.flagged_marker
                        data[b].append(value)
        return data


class MonitoringKpiPlot(KpiPlot):
    @property
    def yaxis_label(self):
        labels = {
            'cpu': ' %',
            'memory': ' bytes',
            'disk': ' bytes',
            'system': '',
            'net': ' bytes',
            'custom': '',
        }
        try:
            yaxis_label = labels[self.kpi_obj.ktype.split('__')[1]]
        except:
            logging.exception('')
            yaxis_label = ''
        return yaxis_label

    @memoized_property
    def graphs_values(self):
        to_select = ''
        sql_func_map = {'maximum': 'max',
                        'average': 'avg',
                        'minimum': 'min',
                        'stddev': 'stddevPop',
                        'median': 'median',
                        }
        for graph in self.kpi_obj.params['graphs']:
            to_select += 'round({}(value),3),'.format(sql_func_map[graph])
        assert to_select

        # Наверное, я чем-то руководствовался, когда записывал в параметры KPI айдюшники, вместо хостеймов...
        try:
            target = self.kpi_obj.params['target'].host
        except KeyError:
            logging.exception('')
            target = ''

        try:
            metric = self.kpi_obj.params['metric'].code
        except KeyError:
            logging.exception('')
            metric = ''

        sql = '''
            select {param}
            job_id
            from loaddb.monitoring_verbose_data_buffer
            where job_id in ({jobs})
            and job_date in ({job_dates})
            and target_host='{target}'
            and metric_name='{metric}'
            group by job_id
        '''
        query_params = {
            'jobs': ','.join(str(int(job_obj.n)) for job_obj in self.jobs),
            'job_dates': format_job_dates(self.jobs),
            'target': target,
            'metric': metric,
            'param': to_select,
        }
        raw_data = self.ch_client.select(sql, query_params=query_params)
        data = {graph: [] for graph in self.kpi_obj.params['graphs']}

        for job_obj in self.jobs:
            values = [v for v in raw_data if v[-1] == job_obj.n] or [[0] * len(self.kpi_obj.params['graphs'])]
            values = values[0]
            if self.component_obj.job_order == 'timeline':
                for graph in self.kpi_obj.params['graphs']:
                    value = {'x': self.xaxis_points[job_obj.n], 'y': values[self.kpi_obj.params['graphs'].index(graph)]}
                    if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                        value['marker'] = self.flagged_marker
                    data[graph].append(value)
            else:
                for graph in self.kpi_obj.params['graphs']:
                    value = {'y': values[self.kpi_obj.params['graphs'].index(graph)]}
                    if job_obj.flag or self.comments_dict.get(job_obj.n, ''):
                        value['marker'] = self.flagged_marker
                    data[graph].append(value)
        return data
