# -*- coding: utf-8 -*-
"""
Created on Dec 2, 2013

@author: noob
"""

from common.models import Component, KPI, Job, Server
from common.util.clients import ClickhouseClient
from common.util.meta import format_job_dates
from common.views import get_common
from collections import OrderedDict
from django.core.exceptions import ObjectDoesNotExist
from django.http import HttpResponseRedirect, HttpResponse, HttpResponseBadRequest
from django.shortcuts import render_to_response, render
from django.template import RequestContext
from django_yauth.decorators import yalogin_required
from monitoring.models import Metric
from regression.views.overview import get_common_for_regression
from json import dumps
import itertools as itt
import logging
import re
import json


@yalogin_required
def component_edit(request, project, component):
    """

    :param request: HTTP Request
    :param project: project value
    :param component: component NUMBER
    """
    project = project.upper()
    user = request.yauser
    common_data = get_common(request)

    # component
    try:
        component_obj = Component.objects.get(n=component)
    except ObjectDoesNotExist:
        logging.error('no such component_obj: "{}"'.format(component))
        page_error = 'no_component'
        return render_to_response('error.html', {'common': common_data, 'error': page_error},
                                  RequestContext(request))

    active_service = request.GET.get('active_service', '')
    if not active_service and component_obj.services:
        active_service = component_obj.services[0]

    common_for_regression = get_common_for_regression(project, service=active_service)

    # kpis
    kpis = KPI.objects.filter(component_id=component_obj.n)
    kpitypes = list(KPI.kpitype_map.items())

    # Не показывать возможность выбрать кастомные метрики, если их нет в компоненте
    component_jobs = Job.objects.filter(component=component_obj.n).order_by("-n")[:100]

    custom_metrics = component_custom_metrics(component_jobs)

    if not custom_metrics:
        try:
            kpitypes.remove(('monitoring__custom', 'Мониторинг: custom'))
        except ValueError:
            pass
        try:
            kpitypes.remove(('monitoring__custom', 25))
        except ValueError:
            pass

    # SLA labels
    sla_labels = {
        'imbalance': '',
        'job_trail__quantiles': 'ms',
        'trail__resps': '',
        'trail__input': '',
        'trail__output': '',
        'trail__expect': 'ms',
        'trail__connect_time': 'ms',
        'trail__send_time': 'ms',
        'trail__latency': 'ms',
        'trail__receive_time': 'ms',
        'trail__threads': '',
        'trail_net__count': '',
        'trail_net__percent': '%',
        'trail_resp__count': '',
        'trail_resp__percent': '%',
        'trail_time__count': '',
        'trail_time__percent': '%',
        'trail_time__quantile': '',
        'monitoring__cpu': '%',
        'monitoring__memory': 'MB',
        'monitoring__disk': 'bytes',
        'monitoring__system': '',
        'monitoring__net': '',
        'monitoring__custom': '',
    }

    #     quit_status_tip = 'dkfjgndfkj'
    #     '\n'.join({'0': 'completed',
    #                    '1': 'interrupted_generic_interrupt',
    #                    '2': 'interrupted',
    #                    '3': 'interrupted_active_task_not_found ',
    #                    '4': 'interrupted_no_ammo_file',
    #                    '5': 'interrupted_address_not_specified',
    #                    '6': 'interrupted_cpu_or_disk_overload',
    #                    '7': 'interrupted_unknown_config_parameter',
    #                    '8': 'interrupted_stop_via_web',
    #                    '9': 'interrupted',
    #                    '11': 'interrupted_job_number_error',
    #                    '12': 'interrupted_phantom_error',
    #                    '13': 'interrupted_job_metainfo_error',
    #                    '14': 'interrupted_target_monitoring_error',
    #                    '15': 'interrupted_target_info_error',
    #                    '21': 'autostop_time',
    #                    '22': 'autostop_http',
    #                    '23': 'autostop_net',
    #                    '24': 'autostop_instances',
    #                    '25': 'autostop_total_time',
    #                    '26': 'autostop_total_http',
    #                    '27': 'autostop_total_net',
    #                    '28': 'autostop_negative_http',
    #                    '29': 'autostop_negative_net',
    #                    '30': 'autostop_http_trend',
    #                    '31': 'autostop_metric_higher',
    #                    '32': 'autostop_metric_lower'}.items())

    if request.method == 'GET':
        return render(request, 'component_edit.html', {
            'common': common_data,
            'user': user,
            'kpis': kpis,
            'kpitypes': kpitypes,
            'sla_labels': sla_labels,
            'component': component_obj,
            'services': component_obj.services,
            'components': common_for_regression['components'],
            'project': common_for_regression['project'],
            'active_service': active_service,
            'include_quit_status': component_obj.include_qs,
            'exclude_quit_status': component_obj.exclude_qs,
        },
                      )
    elif request.method == 'POST':
        try:
            # component
            new_name = request.POST.get('name_new', component_obj.name)
            if not Component.objects.filter(name=new_name, tag=component_obj.tag).exclude(n=component_obj.n).exists():
                component_obj.name = new_name
            else:
                return HttpResponseBadRequest('Component with this name already exists')
            component_obj.dsc = request.POST.get('dsc_new')
            services = list(set(
                [request.POST[s] for s in list(request.POST.keys()) if re.match('^service_.+', s) and request.POST[s]]))

            component_obj.services_json = json.dumps(services)
            component_obj.job_order = request.POST.get('component_xaxis')
            component_obj.include_qs = request.POST.get('include_quit_status', '')
            component_obj.exclude_qs = request.POST.get('exclude_quit_status', '')

            component_obj.save()

            # existing kpis
            for kpi in kpis:
                params = kpi.params
                if request.POST.get('essential__{}'.format(kpi.n, '')):
                    kpi.essential = True
                else:
                    kpi.essential = False
                kpi.data = params
                kpi.save()
        except:
            logging.exception('Could not update current KPIs for component {} for project {} due to:'.format(
                              component_obj.name, project))
        if component_obj.services:
            return HttpResponseRedirect('/regress/{}?service={}'.format(project, component_obj.services[0]))
        else:
            return HttpResponseRedirect('/regress/{}'.format(project))


def component_custom_metrics(component_jobs):
    """
    Ищет кастомные метрики в переданных джобах
    Отфильтровывает телеграфные
    :param component_jobs:
    :return:
    """
    ch_client = ClickhouseClient()
    sql = '''
        select distinct metric_name from loaddb.monitoring_verbose_data
        where job_id in ({job_ids})
        and job_date in ({job_dates})
        and metric_name like 'custom:%'
    '''
    query_params = {
        'job_ids': ','.join(str(j.id) for j in component_jobs),
        'job_dates': format_job_dates(component_jobs)
    }
    custom_metrics = ch_client.select(sql, query_params=query_params)
    custom_metrics = itt.chain.from_iterable(custom_metrics)
    # gotta remove telegraf metrics for CPU, Net and Diskio
    custom_metrics = [
        m for m in custom_metrics
        if not m.startswith(('custom:cpu-cpu', 'custom:diskio'))
        and not re.match('^custom:net-[a-z0-9]+_(bytes_sent|bytes_recv|packets_sent|packets_recv|err_in|err_out|drop_in|drop_out)$', m)
        ]
    return custom_metrics


@yalogin_required
def render_kpi_editor(request, project, component):
    component_obj = Component.objects.get(n=component)

    if int(request.GET.get('kpi')):
        kpi = KPI.objects.get(n=request.GET.get('kpi'))
        ktype = kpi.ktype
    else:
        kpi = None
        ktype = request.GET.get('kpitype')

    kpitypes = list(KPI.kpitype_map.items())
    kpis = KPI.objects.filter(component_id=component_obj.n)

    component_jobs = Job.objects.filter(component=component_obj.n).order_by("-n")[:100]

    # cases
    available_cases = []
    if ktype.split('__')[0] in ['job_trail', 'trail', 'trail_net', 'trail_resp', 'trail_time']:
        for job in component_jobs:
            available_cases.extend(job.cases)
        available_cases = sorted(set(available_cases))

    # monitoring
    available_targets = []
    metrics_cpu = []
    metrics_net = []
    metrics_memory = []
    metrics_disk = []
    metrics_system = []
    metrics_custom = []
    # metrics_custom_all = []

    if ktype.split('__')[0] == 'monitoring':

        for job in component_jobs:
            available_targets.extend(job.targets)
        available_targets = sorted(set(available_targets), key=lambda server: server.n)
        # REGEXP: for telegraf metrics
        metrics_cpu = Metric.objects.filter(code__regex=r'^(CPU_|custom:cpu-cpu)').order_by('code')
        # REGEXP: for telegraf metrics
        metrics_net = Metric.objects.filter(code__regex=r'^(Net_|custom:net-[a-z0-9]+_(bytes_sent|bytes_recv|packets_sent|packets_recv|err_in|err_out|drop_in|drop_out))').order_by('code')
        metrics_memory = Metric.objects.filter(code__startswith='Memory_').order_by('code')
        # REGEXP: for telegraf metrics
        metrics_disk = Metric.objects.filter(code__regex=r'^(Disk_|custom:diskio)').\
            exclude(code__in=['Disk_Z', 'Disk_Y', 'Disk_X', 'Disk_W', 'Disk_ololo', 'Disk_azaza']).order_by('code')
        metrics_system = Metric.objects.filter(code__startswith='System_').order_by('code')
        metrics_custom = Metric.objects.filter(code__in=component_custom_metrics(component_jobs)).order_by('code')
        # metrics_custom_all = Metric.objects.filter(code__startswith='custom:').order_by('code')

    return render(request, 'kpi_edit.html', {
        'kpi': kpi,
        'kpi_n': int(request.GET.get('kpi')),
        'kpis': kpis,
        'kpitypes': kpitypes,
        'kpitype': [k for k in kpitypes if k[0] == ktype][0],
        'project': project,
        'component': component_obj,
        'available_targets': available_targets,
        'metrics_cpu': metrics_cpu,
        'metrics_net': metrics_net,
        'metrics_memory': metrics_memory,
        'metrics_disk': metrics_disk,
        'metrics_system': metrics_system,
        'metrics_custom': metrics_custom,
        # 'metrics_custom_all': metrics_custom_all,
        'available_cases': available_cases,
    }
                  )


@yalogin_required
def add_kpi(request, project, component):
    if request.method == 'POST':
        kpi_creator = KPICreator(request.POST, component)
        if kpi_creator.ktype and kpi_creator.kpi_graphs:
            kpi_creator.create()
    return HttpResponseRedirect('/regress/{}/{}/edit'.format(project, component))


@yalogin_required
def edit_kpi(request, project, component):
    if request.method == 'POST':
        kpi = request.GET.get('kpi', '')
        if kpi.isdigit():
            kpi_creator = KPICreator(request.POST, component, int(kpi))
            kpi_creator.create()
    return HttpResponseRedirect('/regress/{}/{}/edit'.format(project, component))


@yalogin_required
def delete_kpi(request, project, component):
    kpi_n = request.GET.get('kpi')
    KPI.objects.get(n=kpi_n).delete()
    return HttpResponse(dumps([{'success': True}]))


class KPICreator(object):
    def __init__(self, data, component, kpi_n=0):
        """

        :param data: dict like object
        :param component: component NUMBER
        :param kpi_n: KPI NUMBER
        """
        self.data = data
        self.component = component
        self.kpi_n = kpi_n

    def create(self):
        if self.kpi_n:
            kpi_obj = self.get_kpi_obj(self.kpi_n)
            kpi_obj.component_id = self.component
            kpi_obj.params_json = json.dumps(self.kpi_params)
            kpi_obj.dsc = self.kpi_dsc
            kpi_obj.essential = self.kpi_essential
            kpi_obj.save()
        else:
            KPI.objects.create(
                ktype=self.ktype,
                component_id=self.component,
                params_json=json.dumps(self.kpi_params),
                dsc=self.kpi_dsc,
                essential=self.kpi_essential,
            )

    @staticmethod
    def get_kpi_obj(kpi_n):
        kpi_obj = KPI.objects.get(n=kpi_n)
        return kpi_obj

    @property
    def ktype(self):
        try:
            ktype = self.data['kpi_type_{}'.format(self.kpi_n)]
        except KeyError:
            if self.kpi_n:
                kpi_obj = self.get_kpi_obj(self.kpi_n)
                ktype = kpi_obj.ktype
            else:
                ktype = None
        return ktype

    @property
    def kpi_essential(self):
        return True  # bool(self.data.get('essential_{}'.format(self.kpi_n, '')))

    @property
    def kpi_params(self):
        kpi_params = {
            'graphs': self.kpi_graphs,
            'sla': self.kpi_sla,
        }
        if self.ktype.split('__')[0] == 'monitoring':
            kpi_params['target'] = int(self.data.get('target_{}_{}'.format(self.ktype, self.kpi_n), 0))
            kpi_params['metric'] = int(self.data.get('metric_{}_{}'.format(self.ktype, self.kpi_n), 0))
        elif self.ktype.split('__')[0] in ['job_trail', 'trail', 'trail_net', 'trail_time', 'trail_resp']:
            case = self.data.get('case_{}_{}'.format(self.ktype, self.kpi_n), '')
            if case == '-1':
                case = ''
            kpi_params['case'] = case

        return kpi_params

    @property
    def kpi_dsc(self):
        kpi_dsc = '{}; '.format(KPI.kpitype_map.get(self.ktype))
        for key, value in self.kpi_params.items():
            if key == 'target' and value:
                try:
                    kpi_dsc += 'Мишень "{}"; '.format(Server.objects.get(n=value).host)
                except Server.DoesNotExist:
                    logging.exception('')
                    pass
            elif key == 'metric' and value:
                try:
                    kpi_dsc += 'Метрика "{}"; '.format(Metric.objects.get(id=value).code)
                except Metric.DoesNotExist:
                    logging.exception('')
                    pass
            elif key == 'case' and value:
                kpi_dsc += 'Гильза "{}"; '.format(str(value))
            elif key == 'graphs' and value:
                kpi_dsc += '{}; '.format(', '.join(str(item) for item in value))
        return kpi_dsc

    @property
    def kpi_graphs(self):
        graphs = []
        if self.ktype == 'imbalance':
            graphs = ['imbalance']
        elif self.ktype == 'job_trail__quantiles':
            graphs = list(itt.compress(['q99', 'q98', 'q95', 'q90', 'q85', 'q80', 'q75', 'q50'],
                                       [
                                           bool(self.data.get('graphs_{}_{}_99'.format(self.ktype, self.kpi_n), '')),
                                           bool(self.data.get('graphs_{}_{}_98'.format(self.ktype, self.kpi_n), '')),
                                           bool(self.data.get('graphs_{}_{}_95'.format(self.ktype, self.kpi_n), '')),
                                           bool(self.data.get('graphs_{}_{}_90'.format(self.ktype, self.kpi_n), '')),
                                           bool(self.data.get('graphs_{}_{}_85'.format(self.ktype, self.kpi_n), '')),
                                           bool(self.data.get('graphs_{}_{}_80'.format(self.ktype, self.kpi_n), '')),
                                           bool(self.data.get('graphs_{}_{}_75'.format(self.ktype, self.kpi_n), '')),
                                           bool(self.data.get('graphs_{}_{}_50'.format(self.ktype, self.kpi_n), '')),
                                       ])
                          )
        elif self.ktype.split('__')[0] in ['trail', 'monitoring']:
            graphs = list(itt.compress(
                ['maximum', 'average', 'median', 'minimum', 'stddev'],
                [
                    bool(self.data.get('graphs_{}_{}_maximum'.format(self.ktype, self.kpi_n), '')),
                    bool(self.data.get('graphs_{}_{}_average'.format(self.ktype, self.kpi_n), '')),
                    bool(self.data.get('graphs_{}_{}_median'.format(self.ktype, self.kpi_n), '')),
                    bool(self.data.get('graphs_{}_{}_minimum'.format(self.ktype, self.kpi_n), '')),
                    bool(self.data.get('graphs_{}_{}_stddev'.format(self.ktype, self.kpi_n), '')),
                ])
  )
        elif self.ktype.split('__')[0] == 'trail_net':  # net_code
            graphs = []
            for net_code in self.data.get('graphs_{}_{}_net_code'.format(self.ktype, self.kpi_n), '').split(','):
                net_code = net_code.replace('х', 'x')  # replace russian x with latin x
                if net_code.strip().isdigit() or re.match(r'^([0-9]|x)+$', net_code.strip()):
                    graphs.append(net_code.strip())
        elif self.ktype.split('__')[0] == 'trail_resp':  # http_code
            graphs = []
            for http_code in self.data.get('graphs_{}_{}_http_code'.format(self.ktype, self.kpi_n), '').split(
                    ','):
                http_code = http_code.replace('х', 'x')  # replace russian x with latin x
                if http_code.isdigit() or http_code.strip()[1:] == 'xx':
                    graphs.append(http_code.strip())
        elif self.ktype.split('__')[0] == 'trail_time':  # time_to
            graphs = list(set(
                time_to.strip()
                for time_to in self.data.get('graphs_{}_{}_time_to'.format(self.ktype, self.kpi_n), '').split(',')
                if time_to.strip().isdigit()
            ))
        return graphs

    @property
    def kpi_sla(self):
        operators_map = {
            '': '',
            '0': '',
            '1': '<',
            '2': '<=',
            '3': '>',
            '4': '>=',
        }
        try:
            sla = OrderedDict()
            if self.ktype.split('__')[0] in ('trail_resp', 'trail_time', 'trail_net'):
                for graph in self.kpi_graphs:
                    operator_value = int(self.data.get('operator_select_{}_{}_{}'.format(
                            self.ktype, self.kpi_n, self.ktype.split('__')[0]), 0))
                    if operator_value \
                    and re.match(
                        r'^-?[0-9]+.?[0-9]*$',
                        self.data.get('threshold_{}_{}'.format(self.ktype, self.kpi_n), '').replace(',', '.')
                    ):
                        sla[graph] = {
                            'operator': operators_map[self.data.get('operator_select_{}_{}_{}'.format(
                                self.ktype, self.kpi_n, self.ktype.split('__')[0]), '')],
                            'threshold': float(
                                self.data.get('threshold_{}_{}'.format(self.ktype, self.kpi_n), '').replace(',', '.')
                            ),
                        }
            else:
                for graph in self.kpi_graphs:
                    operator_value = int(self.data.get('operator_select_{}_{}_{}'.format(
                            self.ktype, self.kpi_n, graph), 0))
                    if operator_value \
                    and re.match(
                        r'^-?[0-9]+.?[0-9]*$',
                        self.data.get('threshold_{}_{}_{}'.format(self.ktype, self.kpi_n, graph), '').replace(',', '.')
                    ):
                        sla[graph] = {
                            'operator': operators_map[
                                self.data.get('operator_select_{}_{}_{}'.format(self.ktype, self.kpi_n, graph), '')],
                            'threshold': float(
                                self.data.get(
                                    'threshold_{}_{}_{}'.format(self.ktype, self.kpi_n, graph), '').replace(',', '.')
                            ),
                        }
            return sla
        except:
            logging.exception('Could not set SLA for component {} due to:'.format(self.component))
            return {}
