# -*- coding: utf-8 -*-
"""
Created on Mar 27, 2014

@author: noob
"""
from common.util.meta import escape_string
from common.util.decorators import memoized_property
from .plots_base import TimelineComparePlot
from collections import OrderedDict
from http.client import responses
from os import strerror
import logging


class QuantilesTimelineComparePlot(TimelineComparePlot):
    default_selector = 'q98'
    yaxis_label = ' ms'
    selectors = (
        ('q50', '50%'),
        ('q75', '75%'),
        ('q80', '80%'),
        ('q85', '85%'),
        ('q90', '90%'),
        ('q95', '95%'),
        ('q98', '98%'),
        ('q99', '99%'),
        ('q100', '100%'),
    )

    @property
    def title(self):
        if not self.selector:
            self.selector = self.default_selector
        try:
            return 'Квантили времен ответа [{}]'\
                .format([sel[1] for sel in self.selectors if sel[0] == self.selector][0])
        except:
            return 'Квантили времен ответа'

    def fetch_data(self, job_obj):

        sql = '''
            select data from 
            (
            select intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.rt_microsecond_details_buffer
            where job_id={job}
            and job_date=toDate({job_date}) 
            and tag = ''
            and time >= toDateTime({start})
            and time <= toDateTime({end})
            group by t
            order by t
            ) all left join
            (
            select 
            round(max({quantile}), 3) as data,
            intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.rt_quantiles_buffer
            where job_id={job}
            and job_date=toDate({job_date}) 
            '''
        if job_obj.multitag and self.tag:
            sql += 'and tag in ({cases_with_tag}) '
        else:
            sql += '''and tag='{tag}'  '''
        sql += '''
            and time >= toDateTime({start}) 
            and time <= toDateTime({end})
            group by t
            order by t
            ) 
            using t
            '''
        query_params = job_obj.basic_query_params.copy()
        query_params.update({
            'start': self.intervals[job_obj.n]['start'],
            'end': self.intervals[job_obj.n]['end'],
            'compress_ratio': self.compress_ratio,
            'quantile': self.selector,
            'cases_with_tag': ','.join(
                ["'{}'".format(escape_string(case))
                 for case in job_obj.cases if self.tag in case.split('|')]),
            'tag': self.tag,
        })
        return [v[0] for v in self.ch_client.select(sql, query_params=query_params)]


class TimesDistTimelineComparePlot(TimelineComparePlot):
    @property
    def title(self):
        if not self.selector:
            self.selector = self.default_selector
        try:
            return 'Распределение времен ответа [{}]'\
                .format([sel[1] for sel in self.selectors if sel[0] == self.selector][0])
        except:
            return 'Распределение времен ответа'

    def fetch_data(self, job_obj):
        """
        retrieves data for specified job and case
        """

        sql = '''
            select data from 
            (
            select intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.rt_microsecond_details_buffer
            where job_id={job}
            and job_date=toDate({job_date}) 
            and tag = ''
            and time >= toDateTime({start})
            and time <= toDateTime({end})
            group by t
            order by t
            ) all left join
            (
            select 
            round(toUInt32(sum(cnt))/{compress_ratio}, 3) as data, 
            intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.rt_microsecond_histograms_buffer
            where job_id={job}
            and job_date=toDate({job_date}) 
            '''
        if job_obj.multitag and self.tag:
            sql += 'and tag in ({cases_with_tag}) '
        else:
            sql += '''and tag='{tag}'  '''
        sql += '''
            and time >= toDateTime({start}) 
            and time <= toDateTime({end})
            and {length}
            group by t
            order by t
            )
            using t'''
        query_params = job_obj.basic_query_params.copy()
        query_params.update({
            'start': self.intervals[job_obj.n]['start'],
            'end': self.intervals[job_obj.n]['end'],
            'compress_ratio': self.compress_ratio,
            'cases_with_tag': ','.join(
                ["'{}'".format(escape_string(case))
                 for case in job_obj.cases if self.tag in case.split('|')]),
            'tag': self.tag,
        })
        if int(self.selector) == 4:
            query_params['length'] = 'toInt8(length(toString(bin)))>=7'
        elif int(self.selector) == 0:
            query_params['length'] = 'toInt8(length(toString(bin)))<=3'
        else:
            query_params['length'] = 'toInt8(length(toString(bin)))={}'.format(int(self.selector) + 3)  # microseconds
        return [v[0] for v in self.ch_client.select(sql, query_params)]

    @memoized_property
    def series_data(self):
        series_data = []
        if not self.selector:
            self.selector = self.default_selector
        if self.selector is None:
            return None
        database_data = []
        for job_obj in self.job_objects:
            serie_data = self.fetch_data(job_obj)
            database_data.append(bool(serie_data))
            serie = {
                'marker': {
                    'enabled': False,
                    'states': {
                        'hover': {
                            'enabled': False,
                        },
                    },
                },
                'color': self.color_mapping(self.job_objects.index(job_obj)),
                'name': '{} {}'.format(str(job_obj.n), str(job_obj.name)),
                'data': serie_data,
                'label': self.yaxis_label,
            }
            series_data.append(serie)
            scheme_serie = {
                'marker': {
                    'enabled': False,
                    'states': {
                        'hover': {
                            'enabled': False,
                        },
                    },
                },
                'connectNulls': self.scheme_types[job_obj.n] == 'threads',
                'color': self.color_mapping(self.job_objects.index(job_obj)),
                'name': self.scheme_types[job_obj.n],
                'data': self.scheme_data[job_obj.n],
                'dashStyle': 'ShortDot',
                'label': '',
            }
            series_data.append(scheme_serie)
        if not any(database_data):
            return None
        return series_data

    @property
    def yaxis(self):
        return [{
            'plotLines': [{
                'color': '#808080',
                'value': 0,
                'width': 1
            }],
            'showFirstLabel': False,
            'showLastLabel': False,
            'labels': {
                'align': 'left',
                'x': 12,
                'y': -4,
            },
            'title': {
                'text': '',
            },
            'min': 0,
            'allowDecimals': False,
            'label': self.yaxis_label,
        }]

    @memoized_property
    def selectors(self):
        selectors_map = {
            0: '0-0.\u2089\u2089\u2089 ms',  # подстрочные девятки
            1: '1-9 ms',
            2: '10-99 ms',
            3: '100-999 ms',
            4: '1000+ ms',
        }
        lengths = []
        for job_obj in self.job_objects:
            query_params = job_obj.basic_query_params.copy()
            query_params.update({
                'start': self.intervals[job_obj.n]['start'],
                'end': self.intervals[job_obj.n]['end'],
            })
            sql = '''select toInt8(length(toString(bin))) as len
                    from loaddb.rt_microsecond_histograms_buffer
                    where job_id={job} 
                    and job_date=toDate({job_date}) 
                    '''
            if job_obj.multitag and self.case:
                tag = self.case
                cases_with_tag = ["'{}'".format(escape_string(str(case)))
                                  for case in job_obj.cases if
                                  tag in case.split("|")]
                sql += 'and tag in ({cases_with_tag}) '
                query_params['cases_with_tag'] = ",".join(cases_with_tag)
            else:
                sql += '''and tag='{tag}'  '''
                query_params['tag'] = self.case
            sql += '''and time >= toDateTime({start})
                    and time <= toDateTime({end}) 
                    group by len
                    order by len
                    '''
            fetched_data = self.ch_client.select(sql, query_params=query_params)
            lengths.extend(value[0] - 3 for value in fetched_data)
        selectors = OrderedDict()
        for sel in sorted(set(lengths)):
            if int(sel) > 4:
                sel = 4
            elif int(sel) < 1:
                sel = 0
            selectors[str(sel)] = selectors_map[int(sel)]
        return list(selectors.items())


class HTTPCodesTimelineComparePlot(TimelineComparePlot):
    @property
    def title(self):
        if not self.selector:
            self.selector = self.default_selector
        try:
            return 'Распределение HTTP кодов [{}]'\
                   .format([sel[1] for sel in self.selectors if sel[0] == int(self.selector)][0])
        except:
            return 'Распределение HTTP кодов'

    def fetch_data(self, job_obj):
        sql = '''
            select data from 
            (
            select intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.rt_microsecond_details_buffer
            where job_id={job}
            and job_date=toDate({job_date}) 
            and tag = ''
            and time >= toDateTime({start})
            and time <= toDateTime({end})
            group by t
            order by t
            ) all left join
            (
            select 
            round(avg(cnt), 3) as data,
            intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.proto_codes_buffer
            where job_id={job}
            and job_date=toDate({job_date}) 
            '''
        if job_obj.multitag and self.tag:
            sql += 'and tag in ({cases_with_tag}) '
        else:
            sql += '''and tag='{tag}'  '''
        sql += '''
            and code={code}
            and time >= toDateTime({start}) 
            and time <= toDateTime({end})
            group by t
            order by t
            ) 
            using t
            '''
        query_params = job_obj.basic_query_params.copy()
        query_params.update({
            'start': self.intervals[job_obj.n]['start'],
            'end': self.intervals[job_obj.n]['end'],
            'compress_ratio': self.compress_ratio,
            'code': self.selector,
            'cases_with_tag': ','.join(
                ["'{}'".format(escape_string(case)) for case in job_obj.cases if self.tag in case.split('|')]
            ),
            'tag': self.tag,
        })
        return [v[0] for v in self.ch_client.select(sql, query_params=query_params)]

    @memoized_property
    def series_data(self):
        series_data = []
        if not self.selector:
            self.selector = self.default_selector
        if self.selector is None:
            return None
        database_data = []
        for job_obj in self.job_objects:
            serie_data = self.fetch_data(job_obj)
            database_data.append(bool(serie_data))
            serie = {
                'yAxis': 0,
                'marker': {
                    'enabled': False,
                    'states': {
                        'hover': {
                            'enabled': False,
                        },
                    },
                },
                'color': self.color_mapping(self.job_objects.index(job_obj)),
                'name': '{} {}'.format(str(job_obj.n), str(job_obj.name)),
                'data': serie_data,
                'label': self.yaxis_label,
            }
            series_data.append(serie)
            scheme_serie = {
                'yAxis': 1 if len(self.selectors) > 1 else 0,
                'marker': {
                    'enabled': False,
                    'states': {
                        'hover': {
                            'enabled': False,
                        },
                    },
                },
                'connectNulls': self.scheme_types[job_obj.n] == 'threads',
                'color': self.color_mapping(self.job_objects.index(job_obj)),
                'name': self.scheme_types[job_obj.n],
                'data': self.scheme_data[job_obj.n],
                'dashStyle': 'ShortDot',
                'label': '',
            }
            series_data.append(scheme_serie)
        if not any(database_data):
            return None
        return series_data

    @property
    def yaxis(self):
        yaxis = [{
            'plotLines': [{
                'color': '#808080',
                'value': 0,
                'width': 1
            }],
            'showFirstLabel': False,
            'showLastLabel': False,
            'labels': {
                'align': 'left',
                'x': 12,
                'y': -4,
            },
            'title': {
                'text': '',
            },
            'min': 0,
            'allowDecimals': False,
        }]
        if len(self.selectors) > 1:
            yaxis.append({
                'plotLines': [{
                    'color': '#808080',
                    'value': 0,
                    'width': 1
                }],
                'showFirstLabel': False,
                'showLastLabel': False,
                'labels': {
                    'align': 'right',
                    'x': -12,
                    'y': -4,
                },
                'gridLineWidth': 0,
                'opposite': True,
                'title': {
                    'text': '',
                },
                'min': 0,
                'allowDecimals': False,
                'label': 'rps' if len(list(set(self.scheme_types.values()))) == 1 and
                list(set(self.scheme_types.values()))[0] == 'reqps' else '',
            })

        return yaxis

    @memoized_property
    def selectors(self):
        try:
            http_codes = []
            for job_obj in self.job_objects:
                query_params = job_obj.basic_query_params.copy()
                query_params.update({
                    'start': self.intervals[job_obj.n]['start'],
                    'end': self.intervals[job_obj.n]['end']
                })
                sql = '''
                    select distinct code
                    from loaddb.proto_codes_buffer
                    where job_id = {job} 
                    and job_date=toDate({job_date}) 
                '''
                if job_obj.multitag and self.case:
                    tag = self.case
                    cases_with_tag = ["'{}'".format(escape_string(str(case))) for case in job_obj.cases if
                                      tag in case.split("|")]
                    sql += 'and tag in ({cases_with_tag}) '
                    query_params['cases_with_tag'] = ','.join(cases_with_tag)
                else:
                    sql += '''and tag='{tag}'  '''
                    query_params['tag'] = self.case
                sql += '''
                        and time >= toDateTime({start})
                        and time <= toDateTime({end}) 
                        '''
                fetched_data = self.ch_client.select(sql, query_params=query_params)
                http_codes.extend(value[0] for value in fetched_data)
            http_codes = sorted(set(http_codes))
            code_titles = []
            for code in http_codes:
                try:
                    code_titles.append('{}: {}'.format(code, responses[int(code)]))
                except (KeyError, ValueError):
                    logging.exception('')
                    code_titles.append(str(code))
            selectors = list(zip(http_codes, code_titles))
            return selectors
        except:
            logging.exception('Could not get http_codes for {} for jobs {} due to:'.format(self.__class__.__name__,
                              [job_obj.n for job_obj in self.job_objects]))
            return []


class NetCodesTimelineComparePlot(HTTPCodesTimelineComparePlot):
    """
    Derived from HTTPCodesTimelineComparePlot
    """

    @property
    def title(self):
        if not self.selector:
            self.selector = self.default_selector
        try:
            return 'Распределение сетевых кодов [{}]'\
                   .format([sel[1] for sel in self.selectors if sel[0] == int(self.selector)][0])
        except:
            return 'Распределение сетевых кодов'

    def fetch_data(self, job_obj):
        sql = '''
            select data from 
            (
            select intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.rt_microsecond_details_buffer
            where job_id={job}
            and job_date=toDate({job_date}) 
            and tag = ''
            and time >= toDateTime({start})
            and time <= toDateTime({end})
            group by t
            order by t
            ) all left join
            (
            select 
            round(avg(cnt), 3) as data,
            intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.net_codes_buffer
            where job_id={job}
            and job_date=toDate({job_date}) 
            '''
        if job_obj.multitag and self.tag:
            sql += 'and tag in ({cases_with_tag}) '
        else:
            sql += '''and tag='{tag}'  '''
        sql += '''
            and code={code}
            and time >= toDateTime({start}) 
            and time <= toDateTime({end})
            group by t
            order by t
            ) 
            using t
            '''
        query_params = job_obj.basic_query_params.copy()
        query_params.update({
            'start': self.intervals[job_obj.n]['start'],
            'end': self.intervals[job_obj.n]['end'],
            'compress_ratio': self.compress_ratio,
            'code': self.selector,
            'cases_with_tag': ','.join(
                ["'{}'".format(escape_string(case)) for case in job_obj.cases if self.tag in case.split('|')]
            ),
            'tag': self.tag,
        })
        return [v[0] for v in self.ch_client.select(sql, query_params=query_params)]

    @memoized_property
    def selectors(self):
        try:
            net_codes = []
            for job_obj in self.job_objects:
                query_params = job_obj.basic_query_params.copy()
                query_params.update({
                    'start': self.intervals[job_obj.n]['start'],
                    'end': self.intervals[job_obj.n]['end']
                })
                sql = '''
                        select distinct code
                        from loaddb.net_codes_buffer
                        where job_id = {job} 
                        and job_date=toDate({job_date}) 
                        '''
                if job_obj.multitag and self.case:
                    tag = self.case
                    cases_with_tag = ["'{}'".format(escape_string(str(case))) for case in job_obj.cases if
                                      tag in case.split("|")]
                    sql += 'and tag in ({cases_with_tag}) '
                    query_params['cases_with_tag'] = ",".join(cases_with_tag)
                else:
                    sql += '''and tag='{tag}'  '''
                    query_params['tag'] = self.case
                sql += '''
                        and time >= toDateTime({start})
                        and time <= toDateTime({end}) 
                        '''
                fetched_data = self.ch_client.select(sql, query_params=query_params)
                net_codes.extend(value[0] for value in fetched_data)
            net_codes = list(set(net_codes))
            code_titles = []
            for code in set(net_codes):
                try:
                    code_titles.append('{}: {}'.format(code, strerror(int(code))))
                except ValueError:
                    logging.exception('')
                    code_titles.append(str(code))
            selectors = list(zip(net_codes, code_titles))
            return selectors
        except:
            logging.exception('Could not get net_codes for {} for jobs {} due to:'.format(self.__class__.__name__,
                              [job_obj.n for job_obj in self.job_objects]))
            return [[]]


class AvgTimesTimelinePlot(TimelineComparePlot):
    selectors = (
        ('expect', 'общее'),
        ('connect_time', 'соединение'),
        ('send_time', 'отправка'),
        ('latency', 'отклик'),
        ('receive_time', 'получение'),
    )
    # FIXME: Вилка
    _selectors_sql_map = dict((
            ('expect',
             'avg(toFloat64(connect_time_sum + send_time_sum + latency_sum + receive_time_sum)/1000/resps)'
             ),
            ('connect_time', 'avg(toFloat64(connect_time_sum)/1000/resps)'),
            ('send_time', 'avg(toFloat64(send_time_sum)/1000/resps)'),
            ('latency', 'avg(toFloat64(latency_sum)/1000/resps)'),
            ('receive_time', 'avg(toFloat64(receive_time_sum)/1000/resps)'),
        ))

    @property
    def title(self):
        if not self.selector:
            self.selector = self.default_selector
        try:
            return 'Среднее время ответа [{}]'.format([sel[1] for sel in self.selectors if sel[0] == self.selector][0])
        except:
            return 'Среднее время ответа'

    def fetch_data(self, job_obj):
        sql = '''
            select data from 
            (
            select intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.rt_microsecond_details_buffer
            where job_id={job}
            and job_date=toDate({job_date}) 
            and tag = ''
            and time >= toDateTime({start})
            and time <= toDateTime({end})
            group by t
            order by t
            ) all left join
            (
            select 
            round({fraction}, 3) as data, 
            intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.rt_microsecond_details_buffer
            where job_id={job}
            and job_date=toDate({job_date}) 
        '''
        if job_obj.multitag and self.tag:
            sql += 'and tag in ({cases_with_tag}) '
        else:
            sql += '''and tag='{tag}'  '''
        sql += '''
            and time >= toDateTime({start}) 
            and time <= toDateTime({end})
            group by t
            order by t
            )
            using t'''
        query_params = job_obj.basic_query_params.copy()
        query_params.update({
            'start': self.intervals[job_obj.n]['start'],
            'end': self.intervals[job_obj.n]['end'],
            'fraction': self._selectors_sql_map[self.selector],
            'compress_ratio': self.compress_ratio,
            'cases_with_tag': ','.join(
                ["'{}'".format(escape_string(case))
                 for case in job_obj.cases if self.tag in case.split('|')]),
            'tag': self.tag,
        })
        return [v[0] for v in self.ch_client.select(sql, query_params=query_params)]


class InstancesTimelinePlot(TimelineComparePlot):
    title = 'Тестирующие потоки'

    def fetch_data(self, job_obj):
        sql = '''
            select 
            round(avg(threads), 3)
            from loaddb.rt_microsecond_details_buffer
            where job_id={job}
            and job_date=toDate({job_date}) 
            and tag='' 
            and time >= toDateTime({start}) 
            and time <= toDateTime({end})
            group by intDiv(toUInt32(time), {compress_ratio})*{compress_ratio}
            order by intDiv(toUInt32(time), {compress_ratio})*{compress_ratio};
        '''
        query_params = job_obj.basic_query_params.copy()
        query_params.update({
            'start': self.intervals[job_obj.n]['start'],
            'end': self.intervals[job_obj.n]['end'],
            'compress_ratio': self.compress_ratio,
        })
        return [v[0] for v in self.ch_client.select(sql, query_params=query_params)]


class SentReceivedPlot(TimelineComparePlot):
    yaxis_label = ' bytes'
    selectors = (('igress', 'igress'),
                 ('egress', 'egress'))

    @property
    def title(self):
        if not self.selector:
            self.selector = self.default_selector
        try:
            return 'Данные (из танка) [{}]'.format([sel[1] for sel in self.selectors if sel[0] == self.selector][0])
        except:
            return 'Данные (из танка)'

    def fetch_data(self, job_obj):
        sql = '''select 
            round(avg({data_type}), 3)
            from loaddb.rt_microsecond_details_buffer
            where job_id={job}
            and job_date=toDate({job_date}) 
            and tag=''
            and time >= toDateTime({start}) 
            and time <= toDateTime({end})
            group by intDiv(toUInt32(time), {compress_ratio})*{compress_ratio}
            order by intDiv(toUInt32(time), {compress_ratio})*{compress_ratio};'''
        query_params = job_obj.basic_query_params.copy()
        query_params.update({
            'start': self.intervals[job_obj.n]['start'],
            'end': self.intervals[job_obj.n]['end'],
            'data_type': self.selector,
            'compress_ratio': self.compress_ratio,
        })
        return [v[0] for v in self.ch_client.select(sql, query_params=query_params)]
