import re
import requests
from retry import retry
from requests.exceptions import RequestException, ConnectionError, Timeout


LUNAPARK = "https://lunapark.yandex-team.ru"


@retry(ConnectionError)
def api_job(host, sid, method):
    methods = {
        'summary': "api/job/{}/summary.json",
        'times': "api/job/{}/dist/times.json",
        'percentiles': "api/job/{}/dist/percentiles.json",
        'http': "api/job/{}/dist/http.json",
        'net': "api/job/{}/dist/net.json",
        'cases': "api/job/{}/dist/cases.json",
        'monitoring': "api/job/{}/monitoring.json"
    }
    try:
        return requests.get("{}/{}".format(host, methods[method].format(sid)), timeout=3).json()
    except (RequestException, ValueError) as error:
        raise AssessmentError("Wrong API job request. {}".format(error))
    except Timeout:
        raise AssessmentError("Lunapark is not responding.")


def parse_percentiles(perclist):
    if isinstance(perclist, list) and len(perclist) == 9:
        return {item[u'percentile']: float(item[u'ms']) for item in perclist}
    else:
        raise AssessmentError('Wrong percentiles!')


def get_mediana(array):
    if len(array) > 5:
        return sum(sorted(array)[1:-1])/float(len(array) - 2)
    elif len(array) > 0:
        return sum(sorted(array))/float(len(array))
    else:
        return 0


class AssessmentError(Exception):
    def __init__(self, text):
        self.txt = text


class ShootingAssessment(object):
    def __init__(self, shooting_id, reference_list, assessment_tests=None, lunapark=LUNAPARK):
        self.shooting_id = str(shooting_id)
        self.reference_list = [str(item) for item in reference_list]
        self.lunapark = lunapark

        self.assessment_tests = assessment_tests
        try:
            self.assessment_tests = [test for test in self.assessment_tests if self.assessment_tests]
        except TypeError:
            self.assessment_tests = None
        self.default_tests = ['quantile50', 'quantile75', 'quantile80', 'quantile85', 'quantile90', 'http_codes', 'net_codes']
        self.all_tests = {
            'quantile50': self.test_Q50,
            'quantile75': self.test_Q75,
            'quantile80': self.test_Q80,
            'quantile85': self.test_Q85,
            'quantile90': self.test_Q90,
            'quantile95': self.test_Q95,
            'quantile98': self.test_Q98,
            'quantile99': self.test_Q99,
            'quantile100': self.test_Q100,
            'http_codes': self.test_HTTP,
            'net_codes': self.test_NET
        }

        self.percentiles = parse_percentiles(api_job(self.lunapark, self.shooting_id, 'percentiles'))
        self.ref_percentiles = {shooting: parse_percentiles(api_job(self.lunapark, shooting, 'percentiles')) for shooting in self.reference_list}
        self.codes = {level: self._get_codes(level) for level in ["http", "net"]}
        self.result = self._get_result()

    def _get_codes(self, level):
        codes = api_job(self.lunapark, self.shooting_id, level)
        return {item[level]: float(item[u'percent']) for item in codes if isinstance(codes, list) and isinstance(item, dict)}

    def _get_result(self):
        if self.assessment_tests and isinstance(self.assessment_tests, list) and len(self.assessment_tests) > 0:
            return {test: self.all_tests[test]() for test in [test for test in self.assessment_tests if test in self.all_tests]}
        else:
            return {test: self.all_tests[test]() for test in self.default_tests}

    def get_link(self, num):
        linktemplate = 'https://lunapark.yandex-team.ru/compare/#jobs={}&tab=test_data&mainjob={}&helper=all&plotGroup=additional'
        jobs = sorted(self.reference_list, reverse=True)
        jobs.insert(0, self.shooting_id)
        return linktemplate.format(",".join(jobs[:num]), self.shooting_id)

    def test_HTTP(self):
        if len(self.codes['http']) > 0:
            return (float(100) - sum([value for key, value in self.codes['http'].items() if re.match(r'^2..', str(key))]))/float(100)
        else:
            return None

    def test_NET(self):
        if len(self.codes['net']) > 0:
            return (float(100) - self.codes['net'][0])/float(100)
        else:
            return None

    def _test_quantile(self, quantile):
        try:
            return self.percentiles[quantile] / get_mediana([value[quantile] for value in self.ref_percentiles.values()]) - 1
        except:
            return None

    def test_Q50(self):
        return self._test_quantile(u'50')

    def test_Q75(self):
        return self._test_quantile(u'75')

    def test_Q80(self):
        return self._test_quantile(u'80')

    def test_Q85(self):
        return self._test_quantile(u'85')

    def test_Q90(self):
        return self._test_quantile(u'90')

    def test_Q95(self):
        return self._test_quantile(u'95')

    def test_Q98(self):
        return self._test_quantile(u'98')

    def test_Q99(self):
        return self._test_quantile(u'99')

    def test_Q100(self):
        return self._test_quantile(u'100')
