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

@author: noob
"""

import logging
import time
from hashlib import md5


from common.models import Job
from common.util.meta import xss_unescape, escape_string
from common.util.clients import ClickhouseClient
from common.util.decorators import memoized_property, cached


class Plot(object):
    """
    Plots and Tables Superclass
    """

    def __init__(self, job, latest_second, interval, compress_ratio, case, custom_metric=None):
        """
        :param job: Job NUMBER
        :param compress_ratio: int
        :param case: string
        :param custom_metric: if not None looks like this "customg:group_poop" or "customs:nogroup_soup"
        """

        # meta attributes
        self.ch_client = ClickhouseClient()

        # input params
        self.job = job

        self.interval = int(interval)
        self.compress_ratio = int(compress_ratio)

        self.latest_second = latest_second or self.last_second - self.interval
        self.latest_second = int(self.latest_second)

        self.raw_case = case
        self.custom_metric = custom_metric

        self.subtitle = ''  # можно использовать в качестве сообщения для пользователя.

        self.series_limit = 10

    @memoized_property
    def query_params(self):
        query_params = self.job_obj.basic_query_params.copy()
        query_params['interval'] = self.interval
        query_params['latest_second'] = self.latest_second
        query_params['compress_ratio'] = self.compress_ratio
        query_params['cases_with_tag'] = ','.join(
            ["'" + escape_string(case) + "'"
             for case in self.job_obj.cases if self.tag in case.split('|')]
        )
        query_params['tag'] = self.tag
        return query_params

    @memoized_property
    def md5_cases(self):
        return dict(zip((md5(c.encode('utf-8')).hexdigest() for c in self.job_obj.tags if c),
                         (c for c in self.job_obj.tags if c)))

    @memoized_property
    def case(self):
        try:
            case = self.md5_cases[self.raw_case]
        except KeyError:
            case = self.raw_case if self.raw_case in self.job_obj.tags else ''
        return xss_unescape(case)

    @memoized_property
    def first_second(self):
        if self.job_obj.monitoring_only:
            min_time = self.ch_client.select('''
                select toUInt32(min(time))
                from loaddb.monitoring_verbose_data_buffer
                where job_id={job}
                ''',
                                             query_params=self.job_obj.basic_query_params
                                             )
        else:
            min_time = self.ch_client.select('''
                select toUInt32(min(time))
                from loaddb.rt_microsecond_details_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):
        if self.job_obj.monitoring_only:
            max_time = self.ch_client.select('''
                select toUInt32(max(time))
                from loaddb.monitoring_verbose_data_buffer
                where job_id={job}
                ''',
                                             query_params=self.job_obj.basic_query_params
                                             )
        else:
            max_time = self.ch_client.select('''
                select toUInt32(max(time))
                from loaddb.rt_microsecond_details_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())

    @property
    def tag(self):
        """
        alias
        """
        return self.case

    @memoized_property
    def job_obj(self):
        try:
            job_obj = Job.objects.get(n=self.job)
            return job_obj
        except:
            logging.exception("Could not get job OBJECT for plot due to:")
            return None

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

    @property
    def xaxis_type(self):
        """
        returns string
        None by default
        can be "datetime", "category"
        """
        return None


class TimelinePlot(Plot):
    """
    Upper class for all timeline plots
    """

    def __init__(self, *args, **kwargs):
        super(TimelinePlot, self).__init__(*args, **kwargs)
        self.times = []
        self.scheme_data = []

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

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

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

    @property
    def table(self):
        return False

    @property
    def xaxis_type(self):
        return 'datetime'

    @property
    def overrides(self):
        """
        returns dict of data to override default plot parameters
        if necessary, elements are added separately for each Plot subclass
        """
        overrides = super(TimelinePlot, self).overrides
        if not 'xAxis' in list(overrides.keys()):
            overrides['xAxis'] = {}
        overrides['xAxis']['dateTimeLabelFormats'] = {'millisecond': '%H:%M:%S'}
        return overrides

    @memoized_property
    def scheme_type(self):
        color_map = {
            'rps': '#800000',
            'instances': '#ff00ff',
        }
        return {'type': self.job_obj.scheme_type, 'color': color_map[self.job_obj.scheme_type]}

    def get_data(self):
        """
        Main function
        """
        try:
            self.get_times_and_scheme()
            data = {}
            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 {}

    def get_chunk(self):
        """
        Main function
        """
        try:
            return self.chunk
        except:
            logging.exception("Could not get chunk for tag {} for job {} due to:".format(self.tag, self.job))
            return {}

    def get_times_and_scheme(self):
        """
        gets
        uncompressed times for data
        compressed times for xaxis points
        and scheme data (made in one method not to rape db)
        calls "compress_data", "compress_threads" and "compress_times" methods
        """
        sql = 'select intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t, '
        if self.scheme_type['type'] == 'rps':
            sql += 'avg(reqps) '
        else:
            sql += 'avg(threads) '
        sql += '''from loaddb.rt_microsecond_details_buffer
                where job_id={job}
                and job_date=toDate({job_date})
                and tag=''
                and time > toDateTime({latest_second})
                group by t
                order by t
                limit {interval}
                '''
        query_params = self.job_obj.basic_query_params.copy()
        query_params.update({
            'latest_second': self.latest_second,
            'interval': self.interval,
            'compress_ratio': self.compress_ratio,
        })
        fetched_data = self.ch_client.select(sql, query_params=query_params)
        # processing raw data
        self.times = [int(value[0]) for value in fetched_data]
        if self.scheme_type['type'] == 'instances':
            self.scheme_data = [value[1] if value[1] else None for value in fetched_data]
        else:
            self.scheme_data = [value[1] if value[1] else 0 for value in fetched_data]


class DistPlot(Plot):
    """
    Upper class for all vs plots
    """

    @property
    def overrides(self):
        """
        returns dict of data to override default plot parameters
        if necessary, elements are added separately for each Plot subclass
        """
        overrides = super(DistPlot, self).overrides
        if not 'xAxis' in list(overrides.keys()):
            overrides['xAxis'] = {}
        overrides['xAxis']['type'] = 'linear'
        overrides['xAxis']['categories'] = sorted(set(value[0] for value in self.data_values))
        overrides['xAxis']['gridLineWidth'] = 1
        if self.ticks > 15:
            overrides['xAxis']['tickInterval'] = self.ticks // 15
        return overrides

    @property
    def scheme_type(self):
        return None

    @property
    def table(self):
        return False

    @property
    def data_values(self):
        return []

    @memoized_property
    def ticks(self):
        """
        quantity of points on xAxis
        """
        return len([value[0] for value in self.data_values])

    @property
    def xaxis_type(self):
        return 'linear'


class Table(Plot):
    """
    Upper class for all tables
    """

    @property
    def overrides(self):
        return {'chart': {'borderWidth': 0}}

    @property
    def rows(self):
        return None

    @property
    def table(self):
        return True

    def get_chunk(self):
        columns = self.get_columns()
        chunk = {'header': [c['title'] for c in columns],
                 'rows': self.rows,
                 'columns': columns}
        return chunk

    @property
    def yaxis(self):
        return None

    @staticmethod
    def count_percentage(count_data):
        """
        returns list
        :param count_data: list of count
        """
        summa = float(sum(count_data))
        percent_values = [str(round(float(value) * 100 / summa, 3)) + '%'
                          for value in count_data]
        return percent_values
