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

@author: noob
"""

import logging
import time
from hashlib import md5

from datetime import datetime

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


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

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

        # input params
        self.job = job
        self.slider_start = int(slider_start) if slider_start else slider_start
        self.slider_end = int(slider_end) if slider_end else slider_end
        self.compress_ratio = int(compress_ratio) or 1
        self.show_all_events = show_all_events

        self.raw_case = case
        self.custom_metric = custom_metric

        # meta attributes
        self.ch_client = ClickhouseClient()

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

        self.series_limit = 10

    @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['compress_ratio'] = self.compress_ratio
        query_params['cases_with_tag'] = ','.join(
            ["'{}'".format(escape_string(case))
             for case in self.job_obj.cases if self.tag in case.split('|')])
        query_params['tag'] = str(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)

    @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
    """
    table = False
    xaxis_type = 'datetime'

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

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

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

    @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 'xAxis' not 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]}

    @memoized_property
    def user_events_series(self):
        try:
            job_events = JobEvent.objects.filter(job=self.job_obj.id).exclude(author='')
            if not self.show_all_events:
                job_events = job_events.filter(timestamp__range=(
                    datetime.strftime(datetime.fromtimestamp(self.slider_start), '%Y-%m-%d %H:%M:%S'),
                    datetime.strftime(datetime.fromtimestamp(self.slider_end), '%Y-%m-%d %H:%M:%S')))
            job_events = [{'x': time.mktime(e.timestamp.timetuple()) * 1000,
                           'text': e.text,
                           'title': ' ',
                           } for e in job_events]
        except:
            logging.exception('')
            job_events = []
        job_events = {
            'marker': {
                'enabled': False,
            },
            'shape': 'circlepin',
            'lineColor': '#333',
            'name': '_user_events_{}'.format(len(job_events)),
            'type': 'flags',
            'showInLegend': True,
            'data': job_events
        }
        return job_events

    @memoized_property
    def tank_events_series(self):
        try:
            job_events = JobEvent.objects.filter(job=self.job_obj.id, author='')
            if not self.show_all_events:
                job_events = job_events.filter(timestamp__range=(
                    datetime.strftime(datetime.fromtimestamp(self.slider_start), '%Y-%m-%d %H:%M:%S'),
                    datetime.strftime(datetime.fromtimestamp(self.slider_end), '%Y-%m-%d %H:%M:%S')))
            job_events = [{'x': time.mktime(e.timestamp.timetuple()) * 1000,
                           'text': e.text,
                           'title': ' ',
                           } for e in job_events]
        except:
            logging.exception('')
            job_events = []
        job_events = {
            'marker': {
                'enabled': False,
            },
            'shape': 'circlepin',
            'lineColor': '#f00',
            'name': '_tank_events_{}'.format(len(job_events)),
            'type': 'flags',
            'showInLegend': True,
            'visible': False,
            'data': job_events
        }
        return job_events

    def get_data(self):
        """
        Main function
        compresses data and times
        """
        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))
            user_events = self.user_events_series
            if user_events['data']:
                data[user_events['name']] = user_events
            tank_events = self.tank_events_series
            if tank_events['data']:
                data[tank_events['name']] = tank_events
            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_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({start})
                and time <= toDateTime({end})
                group by t
                order by t'''
        fetched_data = self.ch_client.select(sql, query_params=self.query_params)
        self.times = [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
    """
    table = False
    xaxis_type = 'linear'
    scheme_type = None
    data_values = []

    @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 'xAxis' not in list(overrides.keys()):
            overrides['xAxis'] = {}
        overrides['xAxis']['gridLineWidth'] = 1
        if self.ticks > 15:
            overrides['xAxis']['tickInterval'] = self.ticks // 15
        return overrides

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


class Table(Plot):
    """
    Upper class for all tables
    """
    table = True
    yaxis = None
    rows = None
    overrides = {'chart': {'borderWidth': 0}}

    def get_columns(self):
        raise NotImplementedError()

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

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


class MobilePlot(Plot):
    table = False
    times = []

    def get_data(self):
        """
        Main function
        compresses data and times
        """
        try:
            pdata = {}
            for graph in list(self.data.keys()):
                pdata[graph] = self.data.get(graph, [0] * len(self.times))
            pdata['point'] = self.times
            return pdata
        except:
            logging.exception('Could not get data for case {} for job {} due to:'.format(self.tag, self.job))
            return None

    @property
    def mobile_job_obj(self):
        try:
            # чтобы можно было вводить просто номер мобильной джобы, а не айдишник данных в кликхаусе
            if self.raw_case.isdigit():
                mobile_job = MobileJob.objects.get(n=self.raw_case)
            else:
                mobile_job = MobileJob.objects.get(test_id=self.raw_case)
        except:
            mobile_job = None
        return mobile_job

    @property
    def query_params(self):
        return {
            'test_id': self.mobile_job_obj.test_id,
            'start': float(self.slider_start) * 10 if self.slider_start else '',
            'end': float(self.slider_end) * 10 if self.slider_end else '',
            'key_date': self.mobile_job_obj.test_id.split('_')[0],
            'current_table': self.mobile_job_obj.current_table if self.mobile_job_obj else 'currents',
            'test_start': self.mobile_job_obj.test_start if self.mobile_job_obj else 0,
        }
