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

@author: noob
"""
import itertools
from collections import OrderedDict, namedtuple
from .plots_base import TimelinePlot
from common.models import Server
from common.util.decorators import Memoize
from django.core.exceptions import ObjectDoesNotExist
from monitoring.models import Metric
import logging


class MonitoringPlot(TimelinePlot):

    @property
    def target_n(self):
        return self.raw_case

    @property
    def target_name(self):
        return self.target_obj.dsc or 'mysterious host %s' % self.target_obj.n

    @property
    def target_obj(self):
        try:
            return Server.objects.get(n=self.target_n)
        except ObjectDoesNotExist:
            logging.info('No such target %s', self.target_n)
            Shmerver = namedtuple('Shmerver', ['n', 'dsc', 'host'])
            return Shmerver(0, 'unknown host', 'unknown host')

    @property
    def query_params(self):
        query_params = self.job_obj.basic_query_params.copy()
        query_params['start'] = self.slider_start
        query_params['end'] = self.slider_end
        query_params['target'] = self.target_obj.host
        query_params['compress_ratio'] = self.compress_ratio
        return query_params

    @property
    @Memoize
    def metrics(self):
        """
        returns Metric queryset
        """
        sql = '''
            select distinct metric_name 
            from loaddb.monitoring_verbose_data_buffer
            where job_id = %(job)s
            '''
        if self.target_n != '-1':
            sql += " and target_host='%(target)s'"
        metric_objects = Metric.objects.filter(
            code__in=[m[0] for m in self.ch_client.select(sql, query_params=self.query_params)])
        return metric_objects

    @property
    @Memoize
    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'
        # black = '#333333'
        # orange = '#fea134'
        color_map = {
            #                      'Net_closewait':'#0000ff',
            #                      'Net_estab':'#ff1100',
            #                      'Net_recv':black,
            #                      'Net_retransmit':'#feef74',
            #                      'Net_rx':None,
            #                      'Net_send':blue,
            #                      'Net_timewait':'#ffcc00',
            #                      'Net_tx':None,
            '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]
            logging.debug('%s metric color is %s', metric, color)
            return color
        except:
            return None

    def get_data(self):
        """
        Main function
        """
        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))
            logging.debug("Got case %s data for job %s", self.tag, self.job)
            return data
        except:
            logging.exception("Could not get data for case %s for job %s due to:", self.tag, self.job)
            return None

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

        logging.debug('Got scheme_data for job %s', self.job)

        if self.scheme_type['type'] == 'instances':
            self.scheme_data = [value if value else None for value in scheme]
            if self.scheme_data and not any(self.scheme_data):
                # У хайчартса сносит крышу когда все значения в одной из серий == None
                # Версия хайчартс 2.3.3 
                self.scheme_data = [0] + self.scheme_data[1:]
        else:
            self.scheme_data = scheme
            if self.job_obj.monitoring_only:
                self.scheme_data = [0] + [None] * (len(self.scheme_data) - 1)

    @property
    @Memoize
    def data(self):
        """
        retrieves monitoring data from database
        """

        sql = '''
            select m, groupArray(t), groupArray(data)  
            from  
            ( select metric_name as m, intDiv(toUInt32(time), %(compress_ratio)s)*%(compress_ratio)s as t
                from loaddb.monitoring_verbose_data_buffer
                where job_id=%(job)s
                and job_date=toDate(%(job_date)s)
                and target_host='%(target)s'
                and metric_name in (%(metric_ids)s)
                and time >= toDateTime(%(start)s)
                and time <= toDateTime(%(end)s) 
                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)s)*%(compress_ratio)s as t
                from loaddb.monitoring_verbose_data_buffer
                where job_id=%(job)s 
                and job_date=toDate(%(job_date)s)
                and target_host='%(target)s'
                and metric_name in (%(metric_ids)s)
                and time >= toDateTime(%(start)s)
                and time <= toDateTime(%(end)s) 
                group by m, t 
                order by t ) 
            using m, t group by m
        '''
        query_params = self.query_params.copy()
        query_params['metric_ids'] = ','.join("'%s'" % g.replace("\\", "\\\\").replace("'", "\\'")
                                              for g in self.graphs)
        fetched_data = self.ch_client.select(sql, query_params=query_params)
        fetched_data = {v[0]: list(itertools.izip([int(t) * 1000 for t in v[1]], v[2])) for v in fetched_data}
        return OrderedDict(((m, fetched_data.get(m, [0] * len(self.times))) for m in self.graphs))

    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 u'Network @ ' + self.target_name

    @property
    @Memoize
    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 self.metrics_dict.keys()]
        sorted_metrics += [metric for metric in self.metrics_dict.keys() if
                           metric not in sorting_map and metric.split(':')[0].split('_')[0] == 'Net']
        graphs = sorted_metrics
        logging.debug('Got graphs for %s for job %s: %s', self.__class__.__name__, self.job, graphs)
        return graphs


class MonitoringCPUPlot(MonitoringPlot):
    yaxis_label = '%'

    @property
    def title(self):
        return u'CPU @ ' + self.target_name

    @property
    @Memoize
    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 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]
        logging.debug('Got graphs for MonitoringCPUPlot for job %s: %s', self.job, graphs)

        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 = 'MB'

    @property
    def title(self):
        return u'Memory @ ' + self.target_name

    @property
    @Memoize
    def graphs(self):
        memory_metrics_order = 'Memory_swap', 'Memory_free', 'Memory_buff', 'Memory_cached', 'Memory_used'
        metrics = [metric for metric in 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]
        logging.debug('Got graphs for MonitoringMemoryPlot for job %s: %s', self.job, graphs)
        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 u'Disk @ ' + self.target_name

    @property
    @Memoize
    def graphs(self):
        graphs = [metric for metric in self.metrics_dict.keys() if metric.split(':')[0].split('_')[0] == 'Disk']
        logging.debug('Got graphs for MonitoringDiskPlot for job %s: %s', self.job, graphs)
        return graphs


class MonitoringSystemPlot(MonitoringPlot):
    yaxis_label = ' '

    @property
    def title(self):
        return u'System @ ' + self.target_name

    @property
    @Memoize
    def graphs(self):
        graphs = [metric for metric in self.metrics_dict.keys() if metric.split(':')[0].split('_')[0] == 'System']
        logging.debug('Got graphs for MonitoringSystemPlot for job %s: %s', self.job, graphs)
        return graphs


class MonitoringCustomPlot(MonitoringPlot):
    yaxis_label = ' '

    @property
    def overrides(self):
        """
        returns dict of data to override default plot parameters
        if necessary, elements are added separately for each Plot subclass
        """
        return {'plotOptions': {'series': {'connectNulls': False}}}

    @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 title(self):
        return self.custom_metric.split(':')[1] + ' @ ' + self.target_name

    @property
    def graphs(self):
        if self.custom_metric.split(':')[0] == 'customg':
            graphs = [metric for metric in 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)

    def graphs_settings(self):
        if self.custom_metric.split(':')[0] == 'customs':
            settings = [{'values': metric,
                         'title': metric.split(':')[1],
                         'color': None} for metric in self.graphs]
        elif self.custom_metric.split(':')[0] == 'customg':
            settings = [{'values': metric,
                         'title': '_'.join(metric.split(':')[1].split('_')[1:]),
                         'color': self.color_mapping(metric)} for metric in self.graphs]
        logging.debug('Got graphs_settings for plot %s machine %s job %s cm=%s %s ', self.__class__.__name__,
                      self.target_n, self.job, self.custom_metric, settings)
        return settings

    @property
    def negative_values(self):
        try:
            metric_ids = ','.join("'%s'" % g.replace("\\", "\\\\").replace("'", "\\'") 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)s
            and job_date=toDate(%(job_date)s)
            and target_host = '%(target)s'
            and time >= toDateTime(%(start)s)
            and time <= toDateTime(%(end)s)
            and metric_name in (%(metric_ids)s)
            and value < 0
            '''
        query_params = self.query_params.copy()
        query_params['metric_ids'] = metric_ids
        return bool(self.ch_client.select(sql, query_params=query_params)[0][0])

    @property
    def yaxis(self):
        yaxis = [
            {'stacking': 'normal',
             'label': self.yaxis_label,
             'overrides': {
                 'min': None if self.negative_values else 0,  # check if there are negative values
             },
             '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]]
            logging.debug('%s metric color is %s', metric, color)
            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 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.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

    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)',
        }
        color = color_map.get(metric.split('_')[-1])
        logging.debug('%s metric color is %s', metric, color)
        return color


class TelegrafDiskPlot(MonitoringCustomPlot):
    pass


class TelegrafNetPlot(MonitoringCustomPlot):
    pass