# -*- coding: utf-8 -*-
"""
Created on Apr 1, 2015

@author: noob
"""

import logging
import re

from common.util.clients import StartrekClient, TaskError


class ConfigError(Exception):
    pass


class ConfigValidator(object):
    def __init__(self, conf_json, dont_check=('_check_use_tank',)):
        """

        :param conf_json:
        :param dont_check:
        """
        self.conf_json = conf_json
        self.dont_check = list(dont_check) if dont_check else []

    @property
    def params_to_check(self):
        return [param for param in vars(self.__class__) if
                param.startswith('_check_') and param not in self.dont_check]

    def validate(self):
        success = False
        error = ''
        error_link = ''

        if 'phantom' not in self.conf_json:
            self.dont_check += ['_check_loadscheme', '_check_multiphantom_sections']

        try:
            for param in self.params_to_check:
                check_param = vars(self.__class__)[param](self)
                error = check_param['error']
                error_link = check_param.get('error_link', '')
                assert check_param['success']
            success = True
        except AssertionError:
            pass
        except Exception as exc:
            logging.exception('')
            error = exc.__class__.__name__
        return {'success': success, 'error': error, 'error_link': error_link}

    def _find_in_default(self, param):
        """
        [DEFAULT] section support
        :param param: config option value
        """
        wild_params = re.findall('\%\([a-zA-Z_]+\)s', param)
        for wp in wild_params:
            wp_value = self.conf_json['DEFAULT'][wp[wp.index('(') + 1:wp.index(')')]]
            param = param.replace(wp, wp_value)
        return param

    def _check_required_sections(self):
        success = True
        error = ''

        sections = list(self.conf_json.keys())
        required_sections = (('phantom', 'bfg', 'pandora'), ('meta',))

        i = 0
        while i < len(required_sections):
            if any(s in sections for s in required_sections[i]):
                i += 1
            else:
                error = 'В конфиге нет секции {}'.format(' или '.join(required_sections[i]))
                success = False
                break

        return {'success': success, 'error': error}

    def _check_task(self):
        """
        native lunapark method. should be excluded from common validator;
        """
        success = False
        error = ''
        error_link = ''
        task_id_ini = self.conf_json.get('meta', {}).get('task', '')
        task_id_yaml = self.conf_json.get('uploader', {}).get('task', '')
        task_id, is_yaml = task_id_yaml or task_id_ini, bool(task_id_yaml)

        # task_id = self._find_in_default(task_id)
        if not task_id:
            error = 'Не указан таск'
        else:
            if re.match(r'https?://st.yandex-team.ru/[a-zA-Z]+-[0-9]+', task_id):
                task_id = task_id.split('/')[-1]
                if is_yaml:
                    self.conf_json['uploader']['task'] = task_id  # костыль :( валидатор не должен менять конфиг.
                else:
                    self.conf_json['meta']['task'] = task_id  # костыль :( валидатор не должен менять конфиг.
            try:
                success = StartrekClient().check_task_exists(task_id)
            except TaskError as e:
                error = e.__str__()
        return {'success': success, 'error': error, 'error_link': error_link}

    def _check_multiphantom_sections(self):
        success = True
        error = ''
        phantom_sections = [section for section in list(self.conf_json.keys()) if section.startswith('phantom')]
        if len(phantom_sections) > 1:
            checklist = []
            errors = []
            requiered_params = (
                ('ammofile', 'uris', 'stpd_file'),
                ('address',),
            )
            for phsec in phantom_sections:
                i = 0
                while i < len(requiered_params):
                    if bool([p for p in requiered_params[i] if p in list(self.conf_json[phsec].keys())]):
                        i += 1
                    else:
                        errors.append(
                            'В секции {} не хватает параметра {}'.format(phsec, ' или '.join(requiered_params[i])))
                        checklist.append(False)
                        break
            success = all(checklist)
            if errors:
                error = ' | '.join(errors)

        return {'success': success, 'error': error}

    def _check_loadscheme(self):
        success = False
        error = ''
        try:
            ls_types = ('rps_schedule', 'instances_schedule')
            if all((ls_type in list(self.conf_json['phantom'].keys()) and self.conf_json['phantom'].get(ls_type, '') for
                    ls_type in ls_types)):
                error = 'Указаны схемы нагрузки и для потоков, и для RPS'
            elif any((ls_type in list(self.conf_json['phantom'].keys()) and self.conf_json['phantom'].get(ls_type, '') for
                      ls_type in ls_types)):
                if self.conf_json['phantom'].get('rps_schedule', ''):
                    loadscheme = self.conf_json['phantom']['rps_schedule']
                else:
                    loadscheme = self.conf_json['phantom']['instances_schedule']
                loadscheme = self._find_in_default(loadscheme)
                assert loadscheme
                try:
                    # main part
                    assert loadscheme.count('(') == loadscheme.count(')') > 0
                    ls_parts = loadscheme.replace(' ', '').split(')')[:-1]
                    assert ls_parts and all(
                        [ls_part.split('(')[0] in ('line', 'const', 'step') for ls_part in ls_parts])
                    for ls_part in ls_parts:
                        if ls_part.split('(')[0] == 'line':
                            params = ls_part.split('(')[1].split(',')
                            assert (len(params) == 3 and all(d.isdigit() for d in params[:2]) and params[2][-1] in (
                                's', 'm', 'h')) or (len(params) == 3 and all(d.isdigit() for d in params))
                        elif ls_part.split('(')[0] == 'const':
                            params = ls_part.split('(')[1].split(',')
                            assert (len(params) == 2 and params[0].isdigit() and params[1][:-1].isdigit() and params[1][
                                -1] in ('s', 'm', 'h')) or (len(params) == 2 and all(d.isdigit() for d in params))
                        elif ls_part.split('(')[0] == 'step':
                            params = ls_part.split('(')[1].split(',')
                            assert (len(params) == 4 and all(d.isdigit() for d in params[:3]) and params[3][-1] in (
                                's', 'm', 'h')) or (len(params) == 4 and all(d.isdigit() for d in params))
                    success = True
                except (AssertionError, KeyError, IndexError):
                    error = 'Неверный формат схемы нагрузки'
            else:
                raise AssertionError
        except AssertionError:
            if self.conf_json['phantom'].get('stpd_file', '') \
                    or (self.conf_json['phantom'].get('loop', '').isdigit() and int(
                        self.conf_json['phantom'].get('loop', '0')) > 0):
                success = True
            else:
                error = 'Не указана схема нагрузки'

        return {'success': success, 'error': error}

    def _check_autostop(self):
        autostop = self.conf_json.get('autostop', {}).get('autostop', '')
        autostop = self._find_in_default(autostop)
        if autostop:
            try:
                autostop = autostop.replace('\xd1\x85', 'x')  # cyrillic x
            except UnicodeDecodeError:
                logging.error('UnicodeDecodeError for autostop {}'.format(autostop))
            autostop = autostop.replace(' ', '').replace('\n', '').replace('\t', '')  # remove chaotic delimiters
            autostop = autostop.replace(')', ') ')  # add delimiter
            as_parts = [part for part in autostop.split(' ') if part]

            as_types = (
                'time', 'http', 'net', 'quantile', 'instances', 'metric_lower', 'metric_higher', 'steady_cumulative',
                'total_time', 'total_http', 'total_net', 'negative_http', 'negative_net', 'http_trend', 'limit')
            try:
                assert all((
                    part.endswith(')') and
                    part.count('(') == 1 and
                    part.split('(')[0] in as_types
                    for part in as_parts
                ))
                for part in as_parts:
                    params = [param.strip() for param in part[part.index('(') + 1:part.index(')')].split(',')]
                    assert params[-1][:-1].isdigit() and params[-1][-1] in ('s', 'm', 'h') or params[-1].isdigit()
                    if part.split('(')[0] == 'time':
                        assert len(params) == 2
                        assert re.match('^[0-9,msh]+$', params[0])  # например 1s500ms
                    elif part.split('(')[0] in ('http', 'total_http', 'negative_http'):
                        assert len(params) == 3
                        assert re.match('^[0-9]+x?x?$', params[0])  # например 4xx или 502
                        assert re.match('^[0-9]+%?$', params[1])  # например 47% или просто 47
                    elif part.split('(')[0] in ('net', 'total_net', 'negative_net'):
                        assert len(params) == 3
                        assert re.match('^[0-9]?[0-9]?[0-9]?x?x?$', params[0])  # например xx, 11x, или 110
                        assert re.match('^[0-9]+%?$', params[1])  # например 47% или просто 47
                    elif part.split('(')[0] == 'quantile':
                        assert len(params) == 3
                        assert params[0] in ('25', '50', '75', '80', '85', '90', '95', '98', '99', '100')
                        assert re.match('^[0-9]+m?s?$', params[1])  # например 100ms или 100
                    elif part.split('(')[0] == 'instances':
                        assert len(params) == 2
                        assert re.match('^[0-9]+%?$', params[0])  # например 47% или просто 47
                    elif part.split('(')[0] in ('metric_lower', 'metric_higher'):
                        assert len(params) == 4
                        # TODO: check target (params[0]) for ipv6, ipv4, host
                        # assert params[1] in [m.code for m in Metric.objects.exclude(code__startswith='custom:')]
                        # or params[1].startswith('custom:')
                        assert params[2].isdigit()
                    elif part.split('(')[0] == 'steady_cumulative':
                        assert len(params) == 1
                    elif part.split('(')[0] == 'limit':
                        assert len(params) == 1
                        assert re.match('^[0-9,msh]+$', params[0])  # например 1h50m
                    elif part.split('(')[0] == 'http_trend':
                        assert len(params) == 2
                        assert re.match('^[0-9]+x?x?$', params[0])  # например 4xx или 502
                return {'success': True, 'error': ''}
            except AssertionError:
                logging.exception('')
                return {'success': False, 'error': 'Неверный формат автостопа',
                        'error_link': 'https://yandextank.readthedocs.io/en/latest/core_and_modules.html#auto-stop'}
        else:
            return {'success': True, 'error': ''}

    def _check_use_tank(self):
        try:
            use_tank = self.conf_json.get('meta', {}).get('use_tank', '') or self.conf_json.get('meta', {}).get(
                'use-tank', '') or self.conf_json.get('meta', {}).get('usetank', '')  # aliases
            use_tank = self._find_in_default(use_tank)
            assert use_tank
            return {'success': True, 'error': ''}
        except AssertionError:
            return {'success': False, 'error': 'meta.use_tank parameter is mandatory'}
