import os
import re
import sys
from urllib.parse import quote

import requests
import yaml

from lp_hosts import hosts
from lp_metrics import metrics
from lp_components import components_ids
from lp_prefetch_data import LP_DB_Client

lp_kpi_types = {
    'imbalance': ('interval_real __overall__', 'get_autostop_rps'),
    'job_trail__quantiles': ('interval_real __overall__', 'q_cum'),
    'trail__resps': ('proto_code __overall__', None),
    'trail__expect': ('interval_real __overall__', 'avg'),
    'trail_net__count': ('net_code __overall__', 'total'),
    'trail_net__percent': ('net_code __overall__', 'percent'),
    'trail_resp__count': ('proto_code __overall__', 'total'),
    'trail_resp__percent': ('proto_code __overall__', 'percent'),
    'trail_time__quantile': ('interval_real __overall__', 'percent_rank'),
    'monitoring__cpu': (None, None),
    'monitoring__custom': '',
 }


def map_quantile_arg(arg):
    return re.match(re.compile(r'^q(\d{2,3})$'), arg).group(1)


class SLA(object):
    def __init__(self, key, value):
        self.key = key
        self.operator = value['operator']
        self.threshold = value['threshold']


class KPI(object):
    def __init__(self, kpi):
        self.id = kpi['n']
        self.kpi_type = kpi['kpitype']
        self.graphs = kpi.get('params', {}).get('graphs')
        self.case = kpi.get('params', {}).get('case') or '__overall__'
        self.slas = {k: SLA(k, v) for k, v in kpi.get('params', {}).get('sla', {}).items()}
        self.metric_id = kpi.get('params', {}).get('metric')
        self.target_id = kpi.get('params', {}).get('target')
        self.description = kpi['dsc']

    def _name_with_case_getter(self, _name):
        return lambda: {'name': _name,
                        'case': self.case}

    def get_filter(self, **kw):
        return dict({
            'imbalance': self._name_with_case_getter('interval_real'),
            'job_trail__quantiles': self._name_with_case_getter('interval_real'),
            'trail__resps': self._name_with_case_getter('proto_code'),
            'trail__expect': self._name_with_case_getter('interval_real'),
            'trail_net__count': self._name_with_case_getter('net_code'),
            'trail_net__percent': self._name_with_case_getter('net_code'),
            'trail_resp__count': self._name_with_case_getter('proto_code'),
            'trail_resp__percent': self._name_with_case_getter('proto_code'),
            'trail_time__quantile': self._name_with_case_getter('interval_real'),
            'monitoring__cpu': self._get_filter_for_monitoring,
            'monitoring__custom': self._get_filter_for_monitoring,
        }.get(self.kpi_type)(), **kw)

    def get_slas(self):
        return {
            'imbalance':
                self._get_default_slas,
            'job_trail__quantiles':
                lambda: self._get_slas_for_function_with_arg('q_cum', map_quantile_arg),  # ('interval_real __overall__', 'q_cum'),
            'trail__resps':
                self._get_default_slas, # ('proto_code __overall__', None),
            'trail__expect':
                self._get_default_slas,  # ('interval_real __overall__', 'avg'),
            'trail_net__count':
                lambda: self._get_slas_for_function_with_arg('total', lambda x: x),  # ('net_code __overall__', 'total'),
            'trail_net__percent':
                lambda: self._get_slas_for_function_with_arg('percent',
                                                             lambda x: x),  # ('net_code __overall__', 'percent'),
            'trail_resp__count':
                lambda: self._get_slas_for_function_with_arg('total',
                                                             lambda s: str(s).lower().replace('x', '\d')),  # ('proto_code __overall__', 'total'),
            'trail_resp__percent':
                lambda: self._get_slas_for_function_with_arg('total',
                                                             lambda s: str(s).lower().replace('x', '\d')),  # ('proto_code __overall__', 'percent'),
            'trail_time__quantile':
                lambda: self._get_slas_for_function_with_arg('percent_rank',
                                                             lambda x: x),  # ('interval_real __overall__', 'percent_rank'),
            'monitoring__cpu':
                self._get_default_slas,
            'monitoring__custom':
                self._get_default_slas,
        }.get(self.kpi_type)()

    def _get_slas_for_function_with_arg(self, func, arg_mapper):
        return [LunaSLA('{}({})'.format(func, arg_mapper(graph)), name=self.description)
                for graph in self.graphs if graph not in self.slas] +\
               [LunaSLA('{}({})'.format(func, arg_mapper(sla.key)), lp_sla=sla, name=self.description)
                for sla in self.slas.values()]

    def _get_default_slas(self):
        func_map = {"maximum": "max",
                    "average": "avg",
                    "minimum": "min",
                    "median": "median",
                    'stddev': "stddev",
                    'imbalance': 'get_autostop_rps'
                    }
        return [LunaSLA(func_map[graph], name=self.description)
                for graph in self.graphs if graph not in self.slas] + \
               [LunaSLA(func_map[sla.key], sla, self.description)
                for sla in self.slas.values()]

    def _get_filter_for_monitoring(self):
        try:
            return {'name': metrics[self.metric_id].split('_')[1],
                    'group': metrics[self.metric_id].split('_')[0],
                    'host': hosts[self.target_id]}
        except IndexError:
            print('Could not parse group of metric "{}" in kpi {}'.format(metrics[self.metric_id],
                                                                          self.id))
            return {'name': metrics[self.metric_id],
                    'host': hosts[self.target_id]}


class Component(object):

    def __init__(self, n, name):
        self.name = name
        payload = requests.get('https://lunapark.yandex-team.ru/api/regress/{}/kpilist.json'.format(n)).json()
        self.kpis = [KPI(kpi) for kpi in payload]

        # return {i['n']: {} for i in payload}


class Service(object):

    def __init__(self, name, components):
        self.name = name
        self.components_dict = components
        self._components = None

    @property
    def components(self):
        for _id, attrs in self.components_dict.items():
            yield Component(_id, attrs['name'])


class LPProject(object):
    NO_SERVICE = '__OTHER__'

    def __init__(self, prj):
        self.prj = prj
        self._services = None

    @property
    def services(self):
        if self._services is None:
            payload = requests.get(
                "https://lunapark.yandex-team.ru/api/regress/{}/componentlist.json".format(self.prj)).json()
            _services = {}
            for component in payload:
                if component['n'] in components_ids:
                    for service in component['services']:
                        _services.setdefault(service, {})[component['n']] = {'name': component['name']}
                    if len(component['services']) == 0:
                        _services.setdefault(self.NO_SERVICE, {})[component['n']] = {'name': component['name']}
            self._services = [Service(name, components) for name, components in _services.items()]
        return self._services


def luna_regr_seria():
    return {'filter': {},  # {'name': 'interval_real тэг'}
            'sla': []}  # {'function': 'q_cum(95)'}


class LunaRegrConfig(object):

    def __init__(self, name, service, meta=None):
        """
        :type name: str
        :type service: Service
        """
        self.name = name
        self.meta = meta if meta else {}
        self.service = service

    def as_dict(self):
        return {
            'name': self.name,
            'meta': self.meta,
            'series_list': [s.as_dict() for s in self.get_series()]
        }

    def get_series(self):
        series = []
        for component in self.service.components:
            for kpi in component.kpis:
                series.append(LunaSeria(kpi.get_filter(component=component.name), kpi.get_slas()))
        return series


class LunaSeria(object):

    def __init__(self, filter, slas):
        self.filter = filter
        self.slas = slas

    def as_dict(self):
        return {
            'filter': self.filter,
            'sla': [sla.as_dict() for sla in self.slas]
        }


class LunaSLA(object):
    op_map = {
        '>=': 'ge',
        '>': 'gt',
        '<=': 'le',
        '<': 'lt'
    }

    def __init__(self, func, lp_sla: SLA = None, name=''):
        self.name = name
        self.func = func
        self.operator = self.op_map.get(lp_sla.operator) if lp_sla else None
        self.threshold = lp_sla.threshold if lp_sla else None

    def as_dict(self):
        res = {
            'name': self.func,
            'function': self.func}
        if self.operator:
            res[self.operator] = self.threshold
        return res


if __name__ == '__main__':
    pwd = sys.argv[1]
    lpdb = LP_DB_Client(pwd)
    for prj_name in lpdb.get_projects():
        dir = os.path.join(os.getcwd(), 'lp_regressions', prj_name)
        for service in LPProject(prj_name).services:
            os.makedirs(dir, exist_ok=True)
            name = '{}_{}'.format(prj_name, service.name).replace(' ', '_')
            luna_regr = LunaRegrConfig(name, service,
                                       {'lp_link': 'https://lunapark.yandex-team.ru/regress/{}?service={}'.format(
                                           quote(prj_name), quote(service.name))})
            with open(os.path.join(dir, "{}.yaml".format(name)), 'w') as f:
                yaml.dump(
                    {'person': 'robot_lunapark', 'config': luna_regr.as_dict()},
                    f, allow_unicode=True)
