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

@author: noob
"""
from common.util.meta import escape_string
from common.util.decorators import memoized_property
from .plots_base import TimelinePlot
from common.models import Server
from django.core.exceptions import ObjectDoesNotExist
from monitoring.models import Metric
import logging
import time


class MonitoringPlot(TimelinePlot):
    @property
    def machine(self):
        return self.raw_case

    @property
    def target_name(self):
        try:
            target = Server.objects.get(n=self.machine)
            return target.dsc or target.host
        except ObjectDoesNotExist:
            return ''

    def series_types_mapping(self, serie_name):
        return 'line'

    @memoized_property
    def first_second(self):
        min_time = self.ch_client.select('''
            select toUInt32(min(time)) 
            from loaddb.monitoring_verbose_data_buffer
            where job_id={job}
            and job_date = toDate({job_date})
            ''', query_params=self.job_obj.basic_query_params)
        return int(min_time[0][0]) if min_time else int(time.time())

    @memoized_property
    def last_second(self):
        max_time = self.ch_client.select('''
            select toUInt32(max(time)) 
            from loaddb.monitoring_verbose_data_buffer
            where job_id={job}
            and job_date = toDate({job_date})
            ''', query_params=self.job_obj.basic_query_params)
        return int(max_time[0][0]) if max_time else int(time.time())

    @memoized_property
    def metrics(self):
        """
        returns Metric queryset
        """
        sql = '''
            select distinct metric_name
            from loaddb.monitoring_verbose_data_buffer
            where job_id = {job}
            and job_date = toDate({job_date})
            '''
        if self.machine != '-1':
            sql += " and target_host='{target}'"
        query_params = self.job_obj.basic_query_params.copy()
        query_params['target'] = Server.objects.get(n=self.machine).host
        metric_objects = Metric.objects.filter(
            code__in=[m[0] for m in self.ch_client.select(sql, query_params=query_params)])
        return metric_objects

    @memoized_property
    def metrics_dict(self):
        return {m.code: str(m.id) for m in self.metrics}

    @staticmethod
    def color_mapping(metric):
        """
        returns html color for specified metric
        returns random color for new, unknown or custom metrics_dict
        :param metric: metric code string
        """
        blue = '#335bad'
        yellow = 'Gold'
        red = '#ff3333'
        color_map = {
            'Memory_swap': blue,
            'Memory_free': '#73a833',
            'Memory_buff': '#956533',
            'Memory_used': red,
            'Memory_cached': yellow,
            'Disk_read': blue,
            'Disk_write': red,
            'CPU_iowait': blue,
            'CPU_user': yellow,
            'CPU_system': red,
            'CPU_idle': 'rgb(160,160,160)',
            'System_numproc': '#00ff00',
            'System_int': '#0000ff',
            'System_numthreads': '#7b3f00',
            'System_csw': red,
        }
        try:
            color = color_map[metric]
            return color
        except:
            return None

    def get_data(self):
        try:
            self.get_times_and_scheme()
            data = {'point': self.times}
            for graph in self.graphs:
                data[graph] = self.data.get(graph, [0] * len(self.times))
            return data
        except:
            logging.exception('Could not get data for case {} for job {} due to:'.format(self.tag, self.job))
            return None

    def get_times(self):
        sql = '''
            select intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.monitoring_verbose_data_buffer
            where job_id={job}
            and job_date = toDate({job_date})
            and time > toDateTime({latest_second}) 
            group by t
            order by t
            '''
        query_params = self.job_obj.basic_query_params.copy()
        query_params.update({
            'latest_second': self.latest_second,
            'compress_ratio': self.compress_ratio,
        })
        fetched_data = self.ch_client.select(sql, query_params=query_params)
        self.times = [int(value[0]) for value in fetched_data]

    def get_times_and_scheme(self):
        """
        called from self.get_data in TimelinePlot class
        sets self.times and self.scheme_data
        """
        sql = '''
            select t, scheme from 
            (
            select intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.monitoring_verbose_data_buffer
            where job_id={job}
            and job_date = toDate({job_date})
            and time > toDateTime({latest_second}) 
            group by t
            order by t
            ) all left join
            (
            select intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t,
            any({ls_type}) as scheme
            from loaddb.rt_microsecond_details_buffer
            where job_id={job}
            and job_date = toDate({job_date})
            and time > toDateTime({latest_second}) 
            group by t
            order by t
            ) 
            using t
            '''
        query_params = self.job_obj.basic_query_params.copy()
        query_params.update({
            'latest_second': self.latest_second,
            'compress_ratio': self.compress_ratio,
            'ls_type': 'reqps' if self.scheme_type['type'] == 'rps' else 'threads',
        })
        fetched_data = self.ch_client.select(sql, query_params=query_params)
        self.times = [int(value[0]) for value in fetched_data]

    @memoized_property
    def chunk(self):
        """
        retrieves monitoring data from database
        """
        sql = '''
            select m, groupArray(t), groupArray(data) 
            from  
            ( select metric_name as m, intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
                from loaddb.monitoring_verbose_data_buffer
                where job_id={job}  
                and job_date = toDate({job_date})
                and time > toDateTime({latest_second}) 
                '''
        if self.machine != '-1':
            sql += ''' 
                and target_host='{target}'
            '''
        sql += '''
                group by m, t 
                order by t ) 
            any left join 
            ( select metric_name as m, round(avg(value), 3) as data,
                intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
                from loaddb.monitoring_verbose_data_buffer
                where job_id={job}
                and job_date = toDate({job_date})
                and time > toDateTime({latest_second}) 
                '''
        if self.machine != '-1':
            sql += ''' 
                and target_host='{target}'
            '''
        sql += '''
            group by m, t 
            order by t  ) 
            using  m, t group by m
        '''
        query_params = self.job_obj.basic_query_params.copy()
        query_params.update({
            'latest_second': self.latest_second,
            'compress_ratio': self.compress_ratio,
        })
        query_params['target'] = Server.objects.get(n=self.machine).host
        db_data = self.ch_client.select(sql, query_params=query_params)
        metrics = {m.code: m.id for m in Metric.objects.filter(code__in=[dbv[0] for dbv in db_data]).distinct('code')}
        data = {}
        for dbv in db_data:
            if dbv[0] in metrics:
                data[metrics[dbv[0]]] = dbv[2][:self.interval]
            else:
                data[Metric.objects.get_or_create(code=dbv[0])[0].id] = dbv[2][:self.interval]
        data['xAxis'] = db_data[0][1] if db_data and db_data[0] else []
        return data

    def graphs_settings(self):
        settings = [{'values': metric,
                     'title': '_'.join(metric.split('_')[1:]),
                     'color': self.color_mapping(metric)} for metric in self.graphs]
        return settings

    @property
    def yaxis(self):
        yaxis = [
            {'label': self.yaxis_label,
             'overrides': {
                 'min': 0,
             },
             'type': 'line',
             'position': 'left',
             'graphs': self.graphs_settings()
             },
        ]
        #         if not self.job_obj.monitoring_only:
        #             yaxis.insert(0, {'label': self.scheme_type['type'],
        #                  'overrides':{
        #                               'gridLineWidth':0,
        #                               'min': 0,
        #                               'allowDecimals': False
        #                               },
        #                  'position': 'right',
        #                  'type': 'line',
        #                  'graphs': [
        #                             {
        #                              'values': self.scheme_data,
        #                              'zIndex': 2,
        #                              'title': self.scheme_type['type'],
        #                              'color': self.scheme_type['color']
        #                              }
        #                             ]
        #                  })
        return yaxis


class MonitoringNetPlot(MonitoringPlot):
    yaxis_label = ' '

    @property
    def title(self):
        return 'Network @ {}'.format(self.target_name)

    @memoized_property
    def graphs(self):
        """
        returns list of strings (metric codes)
        """
        sorting_map = [
            'Net_recv',
            'Net_send',
            'Net_rx',
            'Net_tx',
            'Net_closewait',
            'Net_timewait',
            'Net_estab',
            'Net_retransmit',
        ]
        sorted_metrics = [metric for metric in sorting_map if metric in list(self.metrics_dict.keys())]
        sorted_metrics += [metric for metric in list(self.metrics_dict.keys()) if
                           metric not in sorting_map and metric.split(':')[0].split('_')[0] == 'Net']
        graphs = sorted_metrics
        return graphs


class MonitoringCPUPlot(MonitoringPlot):
    yaxis_label = '%'

    @property
    def title(self):
        return 'CPU @ {}'.format(self.target_name)

    def series_types_mapping(self, serie_name):
        return 'area'

    @memoized_property
    def graphs(self):
        """
        returns list of strings (metric codes)
        """
        cpu_metrics_order = ('CPU_idle', 'CPU_user', 'CPU_system', 'CPU_iowait', 'CPU_nice', 'CPU_hiq', 'CPU_siq')
        metrics = [metric for metric in list(self.metrics_dict.keys()) if metric.split(':')[0].split('_')[0] == 'CPU']
        graphs = [graph for graph in cpu_metrics_order if graph in metrics]
        graphs += [graph for graph in metrics if graph not in cpu_metrics_order]
        return graphs

    @property
    def yaxis(self):
        yaxis = [
            {'stacking': 'normal',
             'overrides': {
                 'min': 0,
             },
             'label': self.yaxis_label,
             'type': 'area',
             'position': 'left',
             'graphs': self.graphs_settings()
             },
        ]
        #         if not self.job_obj.monitoring_only:
        #             yaxis.insert(0, {'label': self.scheme_type['type'],
        #                  'overrides':{
        #                               'gridLineWidth':0,
        #                               'min': 0,
        #                               'allowDecimals': False
        #                               },
        #                  'position': 'right',
        #                  'type': 'line',
        #                  'graphs': [
        #                             {
        #                              'values': self.scheme_data,
        #                              'zIndex': 2,
        #                              'title': self.scheme_type['type'],
        #                              'color': self.scheme_type['color']
        #                              }
        #                             ]
        #                  })
        return yaxis


class MonitoringMemoryPlot(MonitoringPlot):
    yaxis_label = 'bytes'

    @property
    def title(self):
        return 'Memory @ {}'.format(self.target_name)

    def series_types_mapping(self, serie_name):
        return 'area'

    @memoized_property
    def graphs(self):
        memory_metrics_order = ['Memory_swap', 'Memory_free', 'Memory_buff', 'Memory_cached', 'Memory_used']
        metrics = [metric for metric in list(self.metrics_dict.keys()) if metric.split(':')[0].split('_')[0] == 'Memory']
        graphs = [graph for graph in memory_metrics_order if graph in metrics]
        graphs += [graph for graph in metrics if graph not in memory_metrics_order]
        return graphs

    @property
    def yaxis(self):
        yaxis = [

            {'stacking': 'normal',
             'overrides': {
                 'min': 0,
             },
             'label': self.yaxis_label,
             'type': 'area',
             'position': 'left',
             #                'graphs': self.graphs_settings()
             },
        ]
        #         if not self.job_obj.monitoring_only:
        #             yaxis.insert(0, {'label': self.scheme_type['type'],
        #                              'overrides':{
        #                                           'gridLineWidth':0,
        #                                           'min': 0,
        #                                           'allowDecimals': False
        #                                           },
        #                              'position': 'right',
        #                              'type': 'line',
        #                              'graphs': [
        #                                         {
        #                                          'values': self.scheme_data,
        #                                          'zIndex': 2,
        #                                          'title': self.scheme_type['type'],
        #                                          'color': self.scheme_type['color']
        #                                          }
        #                                         ]
        #                              })
        return yaxis


class MonitoringDiskPlot(MonitoringPlot):
    yaxis_label = 'bytes'

    @property
    def title(self):
        return 'Disk @ {}'.format(self.target_name)

    @memoized_property
    def graphs(self):
        graphs = [metric for metric in list(self.metrics_dict.keys()) if metric.split(':')[0].split('_')[0] == 'Disk']
        return graphs


class MonitoringSystemPlot(MonitoringPlot):
    yaxis_label = ' '

    @property
    def title(self):
        return 'System @ {}'.format(self.target_name)

    @memoized_property
    def graphs(self):
        graphs = [metric for metric in list(self.metrics_dict.keys()) if metric.split(':')[0].split('_')[0] == 'System']
        return graphs


class MonitoringCustomPlot(MonitoringPlot):
    overrides = {'plotOptions': {'series': {'connectNulls': False}}}

    @property
    def title(self):
        return '{metric} @ {target}'.format(metric=self.custom_metric.split(':')[1], target=self.target_name)

    @property
    def yaxis_label(self):
        if self.is_cpu:
            return '%'
        else:
            return ' '

    @property
    def is_cpu(self):
        """
        Костыль для метрик ЦПУ в телеграфе
        чтобы делать stacked area графики
        :return: bool
        """
        # FIXME: костыль
        return bool(len(self.custom_metric.split(':')) > 1 and self.custom_metric.split(':')[1].startswith('cpu-cpu'))

    def sort_cpu_metrics(self, metrics):
        """
        Костыль для метрик ЦПУ в телеграфе
        чтобы на stacked area графике все было чик
        :param metrics: list of strings
        :return: list of strings
        """
        # FIXME: костыль
        try:
            assert self.is_cpu
            cpu_metrics_order = 'idle', 'user', 'system', 'iowait', 'nice', 'hiq', 'siq'
            graphs = []
            for o in cpu_metrics_order:
                for m in metrics:
                    if m.endswith(o):
                        graphs.append(metrics.pop(metrics.index(m)))
            graphs += metrics
            return graphs
        except:
            return metrics

    @property
    def graphs(self):
        if self.custom_metric.startswith('customs'):
            graphs = ['custom:' + self.custom_metric.replace('customg:', 'customg__').split('__', 1)[1]]
        elif self.custom_metric.startswith('customg'):
            graphs = sorted(
                metric for metric in list(self.metrics_dict.keys())
                if metric.startswith('custom:')
                and metric.split(':', 1)[1].split('_')[0] ==
                self.custom_metric.replace('customg:', 'customg__').split('__', 1)[1]
            )
        return self.sort_cpu_metrics(graphs)

    @property
    def negative_values(self):
        try:
            metric_ids = ','.join("'{}'".format(escape_string(g)) for g in self.graphs)
            assert metric_ids
        except AssertionError:
            return False
        sql = '''
            select any(value) 
            from loaddb.monitoring_verbose_data_buffer
            where job_id={job}
            and job_date = toDate({job_date})
            and target_host='{target}'
            and time > toDateTime({latest_second}) 
            and metric_name in ({metric_ids})
            and value < 0
            '''
        query_params = self.job_obj.basic_query_params.copy()
        query_params.update({
            'target': Server.objects.get(n=int(self.machine)).host,
            'latest_second': self.latest_second,
            'metric_ids': metric_ids
        })
        return bool(self.ch_client.select(sql, query_params=query_params))

    @property
    def yaxis(self):
        yaxis = [
            {'stacking': 'normal',
             'label': self.yaxis_label,
             'overrides': {
                 'min': None,
             },
             'type': 'area' if self.is_cpu else 'line',
             'position': 'left',
             #                   'graphs': self.graphs_settings()
             },
        ]
        #         if not self.job_obj.monitoring_only:
        #             yaxis.insert(0, {'label': self.scheme_type['type'],
        #                              'overrides':{
        #                                           'gridLineWidth':0,
        #                                           'min': 0,
        #                                           'allowDecimals': False
        #                                           },
        #                              'position': 'right',
        #                              'type': 'line',
        #                              'graphs': [
        #                                         {
        #                                          'values': self.scheme_data,
        #                                          'zIndex': 2,
        #                                          'title': self.scheme_type['type'],
        #                                          'color': self.scheme_type['color']
        #                                          }
        #                                         ]
        #                              })
        return yaxis

    def color_mapping(self, metric):
        """
        returns html color for specified metric
        returns random color for new, unknown or custom metrics_dict
        :param metric: metric code string
        """
        blue = '#335bad'
        yellow = 'Gold'
        red = '#ff3333'
        color_map = {
            'iowait': blue,
            'user': yellow,
            'system': red,
            'idle': 'rgb(160,160,160)',
        }
        try:
            assert self.is_cpu
            color = color_map[metric.split('_')[-1]]
            return color
        except:
            return None


class TelegrafCPUPlot(MonitoringCustomPlot):
    yaxis_label = '%'

    def sort_cpu_metrics(self, metrics):
        """
        :param metrics: list of strings
        :return: list of strings
        """
        try:
            cpu_metrics_order = 'idle', 'user', 'system', 'iowait', 'nice', 'hiq', 'siq'
            graphs = []
            for o in cpu_metrics_order:
                for m in metrics:
                    if m.endswith(o):
                        graphs.append(metrics.pop(metrics.index(m)))
            graphs += metrics
            return graphs
        except:
            return metrics

    @property
    def graphs(self):
        graphs = []
        if self.custom_metric.split(':')[0] == 'customg':
            graphs = [metric for metric in list(self.metrics_dict.keys())
                      if metric.split(':')[0] == 'custom'
                      and metric.split(':')[1].split('_')[0] == self.custom_metric.split(':')[1]]
        elif self.custom_metric.split(':')[0] == 'customs':
            graphs = ['custom:' + self.custom_metric.split(':')[1]]
        return self.sort_cpu_metrics(graphs)

    @property
    def yaxis(self):
        yaxis = [
            {'stacking': 'normal',
             'label': self.yaxis_label,
             'overrides': {
                 'min': 0,
             },
             'type': 'area',
             'position': 'left',
             # 'graphs': self.graphs_settings()
             },
        ]
        # if not self.job_obj.monitoring_only:
        #     yaxis.insert(0, {
        #         'label': self.scheme_type['type'],
        #         'overrides': {
        #             'gridLineWidth': 0,
        #             'min': 0,
        #             'allowDecimals': False
        #         },
        #         'position': 'right',
        #         'type': 'line',
        #         'graphs': [
        #             {
        #                 'values': self.monitoring_scheme_data,
        #                 'zIndex': 2,
        #                 'disabled': self.job_obj.uses_jmeter and self.scheme_type['type'] == 'instances',
        #                 'title': self.scheme_type['type'],
        #                 'color': self.scheme_type['color']
        #             }
        #         ]
        #     })
        return yaxis

    @staticmethod
    def color_mapping(metric):
        """
        returns html color for specified metric
        returns random color for new, unknown or custom metrics_dict
        :param metric: metric code string
        """
        blue = '#335bad'
        yellow = 'Gold'
        red = '#ff3333'
        color_map = {
            'iowait': blue,
            'user': yellow,
            'system': red,
            'idle': 'rgb(160,160,160)',
        }
        color = color_map.get(metric.split('_')[-1])
        return color


class TelegrafDiskPlot(MonitoringCustomPlot):
    pass


class TelegrafNetPlot(MonitoringCustomPlot):
    pass