# -*- 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 collections import OrderedDict, namedtuple
from common.models import Server
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_host(self):
        return self.target_obj.host

    @property
    def target_obj(self):
        try:
            return Server.objects.get(n=self.target_n)
        except ObjectDoesNotExist:
            logging.warning('No such target {}'.format(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_host
        query_params['compress_ratio'] = self.compress_ratio
        return query_params

    @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.target_n != '-1':
            sql += " and target_host = '{target}'"
        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
    def metrics_dict(self):
        return {m.code: str(m.id) for m in self.metrics}

    @staticmethod
    def color_mapping(metric, *args):
        """
        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,
        }
        return color_map.get(metric)

    def get_times_and_scheme(self):
        """
        called from self.get_data in TimelinePlot class
        produces 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({start}) 
            and time <= toDateTime({end})
            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({start}) 
            and time <= toDateTime({end})
            group by t
            order by t
            ) 
            using t
        '''
        query_params = self.query_params.copy()
        query_params['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]

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

    @memoized_property
    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})*{compress_ratio} as t
                from loaddb.monitoring_verbose_data_buffer
                where job_id={job}
                and job_date=toDate({job_date})
                and target_host='{target}'
                and metric_name in ({metric_ids})
                and time >= toDateTime({start})
                and time <= toDateTime({end}) 
                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 target_host='{target}'
                and metric_name in ({metric_ids})
                and time >= toDateTime({start})
                and time <= toDateTime({end}) 
                group by m, t 
                order by t ) 
            using m, t group by m
        '''
        query_params = self.query_params.copy()
        query_params['metric_ids'] = ','.join("'{}'".format(escape_string(g)) for g in self.graphs)
        fetched_data = self.ch_client.select(sql, query_params=query_params)
        fetched_data = {v[0]: list(zip((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.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


class MonitoringNetPlot(MonitoringPlot):
    yaxis_label = ' '

    @property
    def title(self):
        return 'Сеть @ ' + self.target_host

    @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 'Процессор @ ' + self.target_host

    @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.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


class MonitoringMemoryPlot(MonitoringPlot):
    yaxis_label = 'bytes'

    @property
    def title(self):
        return 'Память @ ' + self.target_host

    @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.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


class MonitoringDiskPlot(MonitoringPlot):
    yaxis_label = 'bytes'

    @property
    def title(self):
        return 'Диск @ ' + self.target_host

    @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 'Система @ ' + self.target_host

    @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 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 yaxis_label(self):
        if self.is_cpu:
            return '%'
        else:
            return ' '

    @property
    def title(self):
        return self.custom_metric.split(':')[1] + ' @ ' + self.target_host

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

    def graphs_settings(self):
        settings = []
        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]
        return settings

    @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({start})
            and time <= toDateTime({end})
            and metric_name in ({metric_ids})
            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))

    @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.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

    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

    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])
        return color


class TelegrafDiskPlot(MonitoringCustomPlot):
    pass


class TelegrafNetPlot(MonitoringCustomPlot):
    pass