import json
import logging

from common.models import DataTypeChoice, DataMeta


class Widget(object):
    def __init__(self, group_key, data_objs):
        """
        :type data_obj: common.models.Data
        """
        self.group_key = group_key
        self.data_objs = data_objs
        if all([do.type == self.data_objs[0].type for do in self.data_objs]):
            self.type = self.data_objs[0].type
        else:
            raise ValueError('All data in widget should have same type')
        self.types = set(type for data_obj in data_objs for type in data_obj.types_list())
        self.title = ' / '.join(group_key)
        self._cases = None
        self.__required_meta = None
        self._metrics_meta = None
        self._quantiles_meta = None
        self._events_meta = None
        self._distributions_meta = None
        self._histograms_meta = None

    def actual_rps_line(self):
        metrics = DataMeta.objects.filter(data__job__in=[obj.job for obj in self.data_objs],
                                          key='name', value='actual_rps')
        if len(metrics) > 0:
            return [{'name': 'actual_rps', 'tag': metric.data_id, 'label': 'rps'} for metric in metrics]
        else:
            logging.debug('Metric with actual rps is not found for jobs %s', [obj.job.id for obj in self.data_objs])
            return []

    @property
    def cases(self):
        return [case.name for do in self.data_objs for case in do.case_set.all()]

    @property
    def _required_meta(self):
        # tag: {type: }
        if self.__required_meta is None:
            full_meta = {
                data_obj: {
                    'tag': data_obj.id,  # todo: rename tag -> data_id
                    'meta':
                        {data_info.key: data_info.value for data_info in DataMeta.objects.filter(data=data_obj)}
                }
                for data_obj in self.data_objs
            }
            self.__required_meta = {data_obj: {
                'name': info['meta'].get('name', ''),
                'tag': info['tag'],
                'label': info['meta'].get('label', ''),
            } for data_obj, info in full_meta.items()}
        return self.__required_meta

    def _certain_type_meta(self, d_type):
        """
        :type d_type: DataTypeChoice
        """
        return [meta for data_obj, meta in self._required_meta.items()
                if d_type.value in data_obj.types_list()]

    @property
    def metrics_meta(self):
        if self._metrics_meta is None:
            self._metrics_meta = self._certain_type_meta(DataTypeChoice.metrics)
        return self._metrics_meta

    @property
    def quantiles_meta(self):
        if self._quantiles_meta is None:
            self._quantiles_meta = self._certain_type_meta(DataTypeChoice.aggregates)
        return self._quantiles_meta

    @property
    def distr_meta(self):
        if self._distributions_meta is None:
            self._distributions_meta = self._certain_type_meta(DataTypeChoice.distributions)
        return self._distributions_meta

    @property
    def events_meta(self):
        if self._events_meta is None:
            self._events_meta = self._certain_type_meta(DataTypeChoice.events)
        return self._events_meta

    @property
    def hist_meta(self):
        if self._histograms_meta is None:
            self._histograms_meta = self._certain_type_meta(DataTypeChoice.histograms)
        return self._histograms_meta

    def views(self):
        """
        Формирует набор вьюх для виджета
        :return: набор вьюх для виджета
        """
        views = ((
                     [self.plot_view(), self.table_view(self.metrics_meta, 'metrics')] +
                     [self.quantiles_view(self.metrics_meta, 'metrics')] if DataTypeChoice.aggregates.value not in self.types else []
                 ) if DataTypeChoice.metrics.value in self.types else []) + \
                (
                    [self.quantiles_view(self.quantiles_meta, 'aggregates'),
                     self.table_view(self.quantiles_meta, 'aggregates')] if DataTypeChoice.aggregates.value in self.types else []
                ) + \
                (
                    [self.distr_view(self.distr_meta)] if DataTypeChoice.distributions.value in self.types else []
                ) + \
                (
                    [self.event_list_view()] if DataTypeChoice.events.value in self.types else []
                ) + \
                (
                    [self.hist_view(self.hist_meta),
                     self.table_view(self.hist_meta, 'histogram')] if DataTypeChoice.histograms.value in self.types else []
                )
        return views

    def plot_view(self):
        """

        :return: populated PLOT_VIEW_TEMPLATE
        """
        req_meta = self.metrics_meta
        draw_lines = len(req_meta) > 5
        return {
            'type': 'plot',
            'sourceType': 'metrics',
            'area': {
                'axis': 'left',
                'metrics': req_meta if not draw_lines else [],
            },
            'axes': {
                'left': {
                    'min': 0,
                    'label': ''
                },
                'right': {'label': ''},
                'x': {}
            },
            'events': [],
            'line': {
                'left': req_meta if draw_lines else [],
                'right': self.actual_rps_line()
            },
        }

    def quantiles_view(self, metrics, source_type):
        """
        only works for one metric
        :return: populated QUANTILES_VIEW_TEMPLATE
        """
        # metric = self.metrics[0]
        return {
            'type': 'quantiles_plot',
            'sourceType': source_type,
            'palette': 'quantiles',
            'area': {
                'axis': 'left',
                'stacked': False,
                'metrics': [{
                    'name': metric['name'],
                    'tag': metric['tag'],
                    'label': 'mA' if metric['name'] == 'current' else '',  # |====---  <- это костыль
                    'expect_values': [
                        'max', 'q99', 'q98', 'q95', 'q90', 'q85', 'q80', 'q75', 'q50', 'q25', 'q10', 'min',
                        # 'average', 'stddev'
                    ]
                } for metric in metrics],
            },
            'cases': self.cases,
            'axes': {
                'left': {
                    'min': 0,
                    'label': '/'.join({m['label'] for m in metrics})
                },
                'right': {},
                'x': {}
            },
            'events': [],
            'line': {
                'left': [],
                'right': self.actual_rps_line()
            },
        }

    @staticmethod
    def table_view(metrics, source_type):
        """

        :return: populated TABLE_VIEW_TEMPLATE
        """
        return {
            'type': 'table',
            'sourceType': source_type,
            'metrics': metrics,
            'hidden_values': ['q99', 'q98', 'q90', 'q85', 'q80', 'q10'],
        }

    def event_list_view(self):
        """

        :return: populated EVENT_LIST_VIEW_TEMPLATE
        """
        return {
            'type': 'log',
            'events': self.events_meta,
            'cases': self.cases
        }

    def distr_view(self, metrics):
        return {
            "distribution": metrics[0],
            "type": "distribution",
            "sourceType": 'distributions',
            "cases": self.cases,
            "axes": {
                "bottom": {
                    "label": "ms"
                },
                "left": {
                    "label": "1/ms"
                },
                "right": {
                    "label": "%"
                },
            "area": {
                "axis": "left",
                "stacked": True,
                "metrics": metrics
                }
            }
        }

    def hist_view(self, metrics):
        return {
            "type": "categories_plot",
            "sourceType": "http",
            "palette": "http",
            "cases": self.cases,
            "area": {
                "axis": "left",
                "stacked": True,
                "metrics": metrics
            },
            "line": {},
            "axes": {
                "left": {
                    "label": "rps"
                }
            }
        }

    @property
    def settings(self):
        return {
            'summary': {
                'values': [
                    'max', 'q99', 'q98', 'q95', 'q90', 'q85', 'q80', 'q75', 'q50', 'q25', 'q10', 'min', 'average'
                ],
                'hidden_values': [
                    'q99', 'q98', 'q90', 'q85', 'q80', 'q10'
                ]
            }
        }

    def as_dict(self):
        return {
            'type': 'plot',
            'title': self.title,
            'views': self.views(),
            'settings': {
                'summary': {
                    'values': [
                        'max', 'q99', 'q98', 'q95', 'q90', 'q85', 'q80', 'q75', 'q50', 'q25', 'q10', 'min', 'average'
                    ],
                    'hidden_values': [
                        'q99', 'q98', 'q90', 'q85', 'q80', 'q10'
                    ]
                }
            },
        }


class RegressionView():
    def __init__(self, names_ids, gt=None, lt=None, ge=None, le=None):
        self.le = le
        self.ge = ge
        self.lt = lt
        self.gt = gt
        self.names_ids = names_ids

    def dict(self):
        return {
            "type": "regression_plot",
            "palette": "default",
            "area": {},

            "line": {
                "left": [
                    {
                        "name": name,
                        "sla_id": _id,
                        "label": " "
                    } for name, _id in self.names_ids
                ],
                "right": []
            },
            "axes": {
                "left": {
                    "label": " "
                },
                "right": {}
            },
            "sla": self.constraints_as_dict()
        }

    def constraints_as_dict(self):
        return {k: v for k, v in [("gt", self.gt),
                                  ("lt", self.lt),
                                  ("ge", self.ge),
                                  ("le", self.le)]
                if v is not None}


class RegressionWidget():
    def __init__(self, series):
        """

        :type series: common.models.RegressionSeries
        """
        self.series = series
        self.name = json.dumps(series.filter)


    def dict(self):
        return {
            'type': 'plot',
            'title': self.name,
            'views': [RegressionView(names_ids=[(sla.name, sla.id) for sla in self.series.sla_set.all()]).dict()],
            'settings': {},
            'meta': self.series.filter
        }


def main_regression_section(regression):
    """

    :type regression: volta.common.models.Regression
    """
    return {
        'widgets': [RegressionWidget(seria).dict() for seria in regression.regressionseries_set.all()],
        'title': 'main_view',
        'group_keys': list(set([k for seria in regression.regressionseries_set.all() for k in seria.filter]))
    }


def get_regression_sections(regression):
    """

    :type regression: volta.common.models.Regression
    """
    return [main_regression_section(regression)]
