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

@author: noob
"""
import collections
import logging
from collections import OrderedDict
from itertools import chain
from json import dumps

from django.http import HttpResponse
from django_yauth.decorators import yalogin_required

from common.models import Job, CustomUserReport
from common.util.clients import ClickhouseClient
from common.util.decorators import memoized_property
from monitoring.models import Metric
from .plots.dist_plots import TimesDistPlot, QuantilesVsTimes, QuantilesVsRps, QuantilesVsInstanses, \
    AvgTimesVsRps, AvgTimesVsInstanses
from .plots.monitoring_plots import MonitoringCPUPlot, MonitoringCustomPlot, MonitoringDiskPlot, MonitoringMemoryPlot, \
    MonitoringNetPlot, MonitoringSystemPlot
from .plots.tables import CasesDistTable, CasesNetCodesTable, CasesHttpCodesTable, CasesCumulativeQuantilesTable, \
    QuantilesCumulativeTable, TimesDistTable, MonitoringAggregatesTable, HttpDistTable, NetDistTable, \
    AggregatesTable, MultitagCumulativeQuantilesTable, MultitagHttpCodesTable, MultitagNetCodesTable, \
    MultitagDistTable
from .plots.timeline_plots import QuantilesTimelinePlot, HTTPCodesTimelinePlot, NetCodesTimelinePlot, \
    AvgTimesTimelinePlot, InstancesTimelinePlot, \
    SentReceivedPlot, CasesTimelinePlot, CasesAvgTimelinePlot, TimesDistTimelinePlot

PLOT_TYPE_MAPPING = {'quantiles_timeline_plot': QuantilesTimelinePlot,
                     'http_timeline_plot': HTTPCodesTimelinePlot,
                     'net_timeline_plot': NetCodesTimelinePlot,
                     'cases_avg_timeline_plot': CasesAvgTimelinePlot,
                     'timesdist_timeline_plot': TimesDistTimelinePlot,
                     'cases_timeline_plot': CasesTimelinePlot,
                     'avgtimes_timeline_plot': AvgTimesTimelinePlot,
                     'sent_received_plot': SentReceivedPlot,
                     'instanses_timeline_plot': InstancesTimelinePlot,

                     'times_dist_plot': TimesDistPlot,
                     'quantiles_vs_times': QuantilesVsTimes,
                     'quantiles_vs_rps': QuantilesVsRps,
                     'quantiles_vs_instanses': QuantilesVsInstanses,

                     'times_dist_table': TimesDistTable,
                     'cases_dist_table': CasesDistTable,
                     'cases_netcodes_table': CasesNetCodesTable,
                     'cases_httpcodes_table': CasesHttpCodesTable,
                     'cases_cumulative_quantiles_table': CasesCumulativeQuantilesTable,
                     'multitag_dist_table': MultitagDistTable,
                     'multitag_cumulative_quantiles_table': MultitagCumulativeQuantilesTable,
                     'multitag_httpcodes_table': MultitagHttpCodesTable,
                     'multitag_netcodes_table': MultitagNetCodesTable,
                     'http_dist_table': HttpDistTable,
                     'net_dist_table': NetDistTable,
                     'quantiles_cumulative_table': QuantilesCumulativeTable,
                     'aggregates_table': AggregatesTable,
                     'avgtimes_vs_rps': AvgTimesVsRps,
                     'avgtimes_vs_instanses': AvgTimesVsInstanses,

                     'monitoring_net_plot': MonitoringNetPlot,
                     'monitoring_disk_plot': MonitoringDiskPlot,
                     'monitoring_memory_plot': MonitoringMemoryPlot,
                     'monitoring_cpu_plot': MonitoringCPUPlot,
                     'monitoring_system_plot': MonitoringSystemPlot,

                     'monitoring_aggregates_table': MonitoringAggregatesTable,
                     }


@yalogin_required
def get_plots_to_show(request, job):
    """

    :param request: HTTP Request
    :param job: Job NUMBER
    """
    tab = request.GET.get('tab', '')
    tag = request.GET.get('tags', '')
    interval = request.GET.get('interval', '')
    compress_ratio = int(interval) // 1000 or 1 if interval else 1
    if tab == 'monitoring':
        plot_group = request.GET.get('metrics', '')
    elif tab.startswith('custom_'):
        plot_group = 'main'
    else:
        plot_group = request.GET.get('plot_groups', '')
    machine = request.GET.get('machines', '')
    if tag == 'Overall':
        tag = ''
    plot_groups = PlotGroupsMapper(job, tab, tag, machine)
    plot_groups.dispatch()
    plots = OrderedDict()
    for plot_type in plot_groups.mapping[plot_group]:
        if plot_type.startswith('monitoring'):
            if plot_type.split('_')[1].split(':')[0][0:-1].lower() == 'custom':
                custom_metric = '_'.join(plot_type.split('@')[0].split('_')[1:-1])
                plot_obj = MonitoringCustomPlot(job, 0, interval, compress_ratio, machine, custom_metric=custom_metric)
            else:
                plot_obj = PLOT_TYPE_MAPPING[plot_type.split('@')[0]](job, 0, interval, compress_ratio, machine)
        elif plot_type == 'mobile_quantiles_plot':  # might be in some custom reports;
            pass
        else:
            plot_obj = PLOT_TYPE_MAPPING[plot_type.split('@')[0]](job, 0, interval, compress_ratio, tag)

        if plot_obj.table:  # TODO: rename into is_table
            columns = plot_obj.get_columns()
            plots[plot_type.replace(':', '__')] = {
                'title': plot_obj.title,
                'subtitle': plot_obj.subtitle,
                'table': plot_obj.table,
                'overrides': plot_obj.overrides,
                'data': {
                    'header': [c['title'] for c in columns],
                    'rows': plot_obj.rows,
                    'columns': columns,
                },
            }
        else:
            plots[plot_type.replace(':', '__')] = {
                'title': plot_obj.title,
                'subtitle': plot_obj.subtitle,
                'table': plot_obj.table,
                'overrides': plot_obj.overrides,
                'x': {'type': plot_obj.xaxis_type},
                'y': plot_obj.yaxis,
            }

    return HttpResponse(dumps({'compress_ratio': int(compress_ratio),
                               'plots': plots,
                               'tab': tab}), content_type='application/json')


class PlotGroupsMapper(object):
    def __init__(self, job, tab, tag, machine):
        """

        :param job: Job NUMBER
        :param tab: monitoring or test_data
        :param tag: string
        :param machine: string Server.n
        """
        self.job = job
        self.tab = tab
        self.tag = tag
        self.machine = machine

        self.mapping = {}

        self.ch_client = ClickhouseClient()

    @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

    def dispatch(self):
        if self.tab == 'test_data':

            self.mapping.update({'main': (
                'quantiles_timeline_plot',
                'instanses_timeline_plot' if self.job_obj.scheme_type == 'rps' else 'timesdist_timeline_plot',
                'http_timeline_plot',
                'net_timeline_plot',
            ),
                'extended': (
                    'avgtimes_timeline_plot',
                    'timesdist_timeline_plot' if self.job_obj.scheme_type == 'rps' else 'instanses_timeline_plot',
                    'cases_timeline_plot',
                    'cases_avg_timeline_plot',
                    'sent_received_plot',
                ),
                'additional': (
                    'times_dist_plot',
                    'quantiles_vs_times',
                    'avgtimes_vs_rps',
                    'avgtimes_vs_instanses',
                    'quantiles_vs_rps',
                    'quantiles_vs_instanses',
                ),
                'tables': [
                    # FIXME: Удалить, если никто не возбудится подробности в тикете LUNAPARK-2810
                    # 'aggregates_table',
                    'quantiles_cumulative_table',
                    'http_dist_table',
                    'net_dist_table',
                    'times_dist_table',
                ],
            })

            if self.job_obj.multitag:
                if not self.tag and self.job_obj.tags:
                    self.mapping['tables'].insert(0, 'multitag_dist_table')
                    self.mapping['tables'].insert(0, 'multitag_httpcodes_table')
                    self.mapping['tables'].insert(0, 'multitag_netcodes_table')
                    self.mapping['tables'].insert(0, 'multitag_cumulative_quantiles_table')
            else:
                if not self.tag and self.job_obj.tags:
                    self.mapping['tables'].insert(0, 'cases_dist_table')
                    self.mapping['tables'].insert(0, 'cases_httpcodes_table')
                    self.mapping['tables'].insert(0, 'cases_netcodes_table')
                    self.mapping['tables'].insert(0, 'cases_cumulative_quantiles_table')

        elif self.tab == 'monitoring':
            self.mapping.update(self.monitoring_mapping())
        elif self.tab.startswith('custom__'):
            self.mapping.update(self.custom_report_mapping())

    def monitoring_mapping(self):
        """
        plot mapping may vary according to some initial parameters: i.e. case (aka machine)
        """
        all_tab = ['monitoring_{}_plot@{}'.format(str(metric_group).lower(), self.machine) for metric_group in
                   self.metric_groups[self.machine]]
        all_tab += self.custom_metric_groups[self.machine]  # adding custom and telegraf metrics
        all_tab.sort()
        all_tab += ['monitoring_aggregates_table@{}'.format(self.machine)]  # adding aggregates table

        mapping = {
            'Net': ['monitoring_net_plot@{}'.format(self.machine)] if 'Net' in self.metric_groups[
                self.machine] else [],
            'Disk': ['monitoring_disk_plot@{}'.format(self.machine)] if 'Disk' in self.metric_groups[
                self.machine] else [],
            'Memory': ['monitoring_memory_plot@{}'.format(self.machine)] if 'Memory' in self.metric_groups[
                self.machine] else [],
            'System': ['monitoring_system_plot@{}'.format(self.machine)] if 'System' in self.metric_groups[
                self.machine] else [],
            'CPU': ['monitoring_cpu_plot@{}'.format(self.machine)] if 'CPU' in self.metric_groups[
                self.machine] else [],
            'custom': sorted(
                cm for cm in self.custom_metric_groups[self.machine]
                if not cm.split(':')[1].startswith(('cpu-cpu', 'diskio-', 'net-'))
            ),
            'Агрегаты': ['monitoring_aggregates_table@{}'.format(self.machine)],
            '': all_tab,
        }

        for k in sorted(self.telegraf_metric_groups.keys()):
            mapping[k] += self.telegraf_metric_groups[k][int(self.machine)]
        return mapping

    def custom_report_mapping(self):
        """
        Custom user report
        """
        plots = sorted(CustomUserReport.objects.get(n=int(self.tab.split('__')[1])).plots)

        # Monitoring plots
        if self.machine in ['-1', '']:  # all targets
            mlots = []
            for machine in list(self.target_metrics.keys()):
                for mlot in plots:
                    if mlot.startswith('monitoring_') and mlot != 'monitoring_custom_plot':
                        mlots.append('{}@{}'.format(mlot, machine))
            if 'monitoring_custom_plot' in plots:
                mlots += sorted(
                    cm for cm in chain(*list(self.custom_metric_groups.values()))
                    if not any(cm.split(':')[1].startswith(prefix) for prefix in ('cpu-cpu', 'diskio-', 'net-'))
                )
        else:
            mlots = ['{}@{}'.format(mlot, self.machine) for mlot in plots if
                     mlot.startswith('monitoring_') and mlot != 'monitoring_custom_plot']
            if 'monitoring_custom_plot' in plots:
                mlots += [
                    cm for cm in self.custom_metric_groups[self.machine]
                    if not any(cm.split(':')[1].startswith(prefix) for prefix in ('cpu-cpu', 'diskio-', 'net-'))
                ]

        # Tables
        tables = [table for table in plots if table.endswith('_table') and table != 'monitoring_aggregates_table']

        plots = [plot for plot in plots if
                 not plot.startswith('monitoring_')
                 and not plot.startswith('mobile_')
                 and not plot.endswith('_table')] + mlots + tables
        if self.job_obj.multitag:
            replaceable_plots = {
                'cases_dist_table': 'multitag_dist_table',
                'cases_httpcodes_table': 'multitag_httpcodes_table',
                'cases_netcodes_table': 'multitag_netcodes_table',
                'cases_cumulative_quantiles_table': 'multitag_cumulative_quantiles_table',
            }
            for index, plot in enumerate(plots):
                if plot in replaceable_plots:
                    plots[index] = replaceable_plots[plot]

        return {'main': plots}

    @memoized_property
    def target_metrics(self):
        """
        returns dict {target_number:{metric_codes: metric_ids}}
        """
        metrics = {}
        sql = '''
            select distinct metric_name
            from loaddb.monitoring_verbose_data_buffer
            where job_id={job}
            and job_date = toDate({job_date})
            and target_host='{target}'
        '''
        for machine_obj in self.job_obj.targets:
            query_params = self.job_obj.basic_query_params.copy()
            query_params['target'] = machine_obj.host
            metrics[str(machine_obj.n)] = {value[0]: Metric.objects.get_or_create(code=value[0])[0].id
                                      for value in self.ch_client.select(sql, query_params=query_params)}
        return metrics

    @memoized_property
    def metric_groups(self):
        """
        returns dict {machine.n:set([target_metrics])}
        """
        metric_groups = {}
        for machine in list(self.target_metrics.keys()):
            metric_groups[machine] = set(
                [metric.split(':')[0].split('_')[0] for metric in list(self.target_metrics[machine].keys()) if
                 metric.split(':')[0] != 'custom'])
        return metric_groups

    @property
    def custom_metrics(self):
        """
        returns dict {machine.n:[metric.codes]}
        """
        custom_metrics = {}
        for machine in list(self.target_metrics.keys()):
            custom_metrics[machine] = [metric for metric in list(self.target_metrics[machine].keys()) if
                                       metric.split(':')[0] == 'custom']
        return custom_metrics

    @memoized_property
    def telegraf_metric_groups(self):
        """
        returns dict {group {machine.n:: metrics}}
        """
        telegraf_metric_groups = {
            'CPU': collections.defaultdict(list),
            'Disk': collections.defaultdict(list),
            'Net': collections.defaultdict(list),
        }
        for machine in list(self.target_metrics.keys()):
            # CPU
            custom_cpu_metrics = [
                custom_metric.split(':')[1].split('_')[0]
                for custom_metric in self.custom_metrics[machine]
                if custom_metric.split(':')[1].startswith('cpu-cpu')
            ]
            single_custom_cpu_metrics = [
                'telegraf_customs:{metric}_plot@{machine}'.format(metric=metric.split(':')[1], machine=machine)
                for metric in self.custom_metrics[machine]
                if custom_cpu_metrics.count(metric.split(':')[1].split('_')[0]) == 1
            ]
            grouped_custom_cpu_metrics = set([
                'telegraf_customg:{metric_group}_plot@{machine}'
                    .format(metric_group=metric.split(':')[1].split('_')[0], machine=machine)
                for metric in self.custom_metrics[machine]
                if custom_cpu_metrics.count(metric.split(':')[1].split('_')[0]) > 1
            ])
            telegraf_metric_groups['CPU'][machine].extend(
                list(grouped_custom_cpu_metrics) + single_custom_cpu_metrics
            )
            # DISK
            custom_disk_metrics = [
                custom_metric.split(':')[1].split('_')[0]
                for custom_metric in self.custom_metrics[machine]
                if custom_metric.split(':')[1].startswith('diskio-')
            ]
            single_custom_disk_metrics = [
                'telegraf_customs:{metric}_plot@{machine}'.format(metric=metric.split(':')[1], machine=machine)
                for metric in self.custom_metrics[machine]
                if custom_disk_metrics.count(metric.split(':')[1].split('_')[0]) == 1
            ]
            grouped_custom_disk_metrics = set([
                'telegraf_customg:{metric_group}_plot@{machine}'
                    .format(metric_group=metric.split(':')[1].split('_')[0], machine=machine)
                for metric in self.custom_metrics[machine]
                if custom_disk_metrics.count(metric.split(':')[1].split('_')[0]) > 1
            ])
            telegraf_metric_groups['Disk'][machine].extend(
                list(grouped_custom_disk_metrics) + single_custom_disk_metrics
            )
            # NET
            custom_net_metrics = [
                custom_metric.split(':')[1].split('_')[0]
                for custom_metric in self.custom_metrics[machine]
                if custom_metric.split(':')[1].startswith('net-')
            ]
            single_custom_net_metrics = [
                'telegraf_customs:{metric}_plot@{machine}'.format(metric=metric.split(':')[1], machine=machine)
                for metric in self.custom_metrics[machine]
                if custom_net_metrics.count(metric.split(':')[1].split('_')[0]) == 1
            ]
            grouped_custom_net_metrics = set([
                'telegraf_customg:{metric_group}_plot@{machine}'
                    .format(metric_group=metric.split(':')[1].split('_')[0], machine=machine)
                for metric in self.custom_metrics[machine]
                if custom_net_metrics.count(metric.split(':')[1].split('_')[0]) > 1
            ])
            telegraf_metric_groups['Net'][machine].extend(
                list(grouped_custom_net_metrics) + single_custom_net_metrics
            )

        return telegraf_metric_groups

    @memoized_property
    def custom_metric_groups(self):
        """
        returns dict {machine.n:[grouped and single metric codes]}
        """
        custom_metric_groups = {}
        for machine in list(self.target_metrics.keys()):
            custom_metrics = [
                custom_metric.split(':')[1].split('_')[0]
                for custom_metric in self.custom_metrics[machine]
            ]
            # if not any(custom_metric.split(':')[1].startswith(prefix) for prefix in ('cpu-cpu', 'diskio-', 'net-'))
            single_custom_metrics = [
                'monitoring_customs:{metric}_plot@{machine}'.format(metric=metric.split(':')[1], machine=machine)
                for metric in self.custom_metrics[machine]
                if custom_metrics.count(metric.split(':')[1].split('_')[0]) == 1
            ]
            grouped_custom_metrics = set([
                'monitoring_customg:{metric_group}_plot@{machine}'
                .format(metric_group=metric.split(':')[1].split('_')[0], machine=machine)
                for metric in self.custom_metrics[machine]
                if custom_metrics.count(metric.split(':')[1].split('_')[0]) > 1
            ])
            custom_metric_groups[machine] = sorted(list(grouped_custom_metrics) + single_custom_metrics)
        return custom_metric_groups
