# -*- coding: utf-8 -*-
# Description: traefik netdata python.d module
# Author: Alexandre Menezes (@ale_menezes)
# SPDX-License-Identifier: GPL-3.0-or-later

from collections import defaultdict
from json import loads

from bases.FrameworkServices.UrlService import UrlService

ORDER = [
    'response_statuses',
    'response_codes',
    'detailed_response_codes',
    'requests',
    'total_response_time',
    'average_response_time',
    'average_response_time_per_iteration',
    'uptime'
]

CHARTS = {
    'response_statuses': {
        'options': [None, 'Response statuses', 'requests/s', 'responses', 'traefik.response_statuses', 'stacked'],
        'lines': [
            ['successful_requests', 'success', 'incremental'],
            ['server_errors', 'error', 'incremental'],
            ['redirects', 'redirect', 'incremental'],
            ['bad_requests', 'bad', 'incremental'],
            ['other_requests', 'other', 'incremental']
        ]
    },
    'response_codes': {
        'options': [None, 'Responses by codes', 'requests/s', 'responses', 'traefik.response_codes', 'stacked'],
        'lines': [
            ['2xx', None, 'incremental'],
            ['5xx', None, 'incremental'],
            ['3xx', None, 'incremental'],
            ['4xx', None, 'incremental'],
            ['1xx', None, 'incremental'],
            ['other', None, 'incremental']
        ]
    },
    'detailed_response_codes': {
        'options': [None, 'Detailed response codes', 'requests/s', 'responses', 'traefik.detailed_response_codes',
                    'stacked'],
        'lines': []
    },
    'requests': {
        'options': [None, 'Requests', 'requests/s', 'requests', 'traefik.requests', 'line'],
        'lines': [
            ['total_count', 'requests', 'incremental']
        ]
    },
    'total_response_time': {
        'options': [None, 'Total response time', 'seconds', 'timings', 'traefik.total_response_time', 'line'],
        'lines': [
            ['total_response_time_sec', 'response', 'absolute', 1, 10000]
        ]
    },
    'average_response_time': {
        'options': [None, 'Average response time', 'milliseconds', 'timings', 'traefik.average_response_time', 'line'],
        'lines': [
            ['average_response_time_sec', 'response', 'absolute', 1, 1000]
        ]
    },
    'average_response_time_per_iteration': {
        'options': [None, 'Average response time per iteration', 'milliseconds', 'timings',
                    'traefik.average_response_time_per_iteration', 'line'],
        'lines': [
            ['average_response_time_per_iteration_sec', 'response', 'incremental', 1, 10000]
        ]
    },
    'uptime': {
        'options': [None, 'Uptime', 'seconds', 'uptime', 'traefik.uptime', 'line'],
        'lines': [
            ['uptime_sec', 'uptime', 'absolute']
        ]
    }
}

HEALTH_STATS = [
    'uptime_sec',
    'average_response_time_sec',
    'total_response_time_sec',
    'total_count',
    'total_status_code_count'
]


class Service(UrlService):
    def __init__(self, configuration=None, name=None):
        UrlService.__init__(self, configuration=configuration, name=name)
        self.url = self.configuration.get('url', 'http://localhost:8080/health')
        self.order = ORDER
        self.definitions = CHARTS
        self.last_total_response_time = 0
        self.last_total_count = 0
        self.data = {
            'successful_requests': 0,
            'redirects': 0,
            'bad_requests': 0,
            'server_errors': 0,
            'other_requests': 0,
            '1xx': 0,
            '2xx': 0,
            '3xx': 0,
            '4xx': 0,
            '5xx': 0,
            'other': 0,
            'average_response_time_per_iteration_sec': 0,
        }

    def _get_data(self):
        data = self._get_raw_data()

        if not data:
            return None

        data = loads(data)

        self.get_data_per_code_status(raw_data=data)

        self.get_data_per_code_family(raw_data=data)

        self.get_data_per_code(raw_data=data)

        self.data.update(fetch_data_(raw_data=data, metrics=HEALTH_STATS))

        self.data['average_response_time_sec'] *= 1000000
        self.data['total_response_time_sec'] *= 10000
        if data['total_count'] != self.last_total_count:
            self.data['average_response_time_per_iteration_sec'] = \
                (data['total_response_time_sec'] - self.last_total_response_time) * \
                1000000 / (data['total_count'] - self.last_total_count)
        else:
            self.data['average_response_time_per_iteration_sec'] = 0
        self.last_total_response_time = data['total_response_time_sec']
        self.last_total_count = data['total_count']

        return self.data or None

    def get_data_per_code_status(self, raw_data):
        data = defaultdict(int)
        for code, value in raw_data['total_status_code_count'].items():
            code_prefix = code[0]
            if code_prefix == '1' or code_prefix == '2' or code == '304':
                data['successful_requests'] += value
            elif code_prefix == '3':
                data['redirects'] += value
            elif code_prefix == '4':
                data['bad_requests'] += value
            elif code_prefix == '5':
                data['server_errors'] += value
            else:
                data['other_requests'] += value
        self.data.update(data)

    def get_data_per_code_family(self, raw_data):
        data = defaultdict(int)
        for code, value in raw_data['total_status_code_count'].items():
            code_prefix = code[0]
            if code_prefix == '1':
                data['1xx'] += value
            elif code_prefix == '2':
                data['2xx'] += value
            elif code_prefix == '3':
                data['3xx'] += value
            elif code_prefix == '4':
                data['4xx'] += value
            elif code_prefix == '5':
                data['5xx'] += value
            else:
                data['other'] += value
        self.data.update(data)

    def get_data_per_code(self, raw_data):
        for code, value in raw_data['total_status_code_count'].items():
            if self.charts:
                if code not in self.data:
                    self.charts['detailed_response_codes'].add_dimension([code, code, 'incremental'])
                self.data[code] = value


def fetch_data_(raw_data, metrics):
    data = dict()

    for metric in metrics:
        value = raw_data
        metrics_list = metric.split('.')
        try:
            for m in metrics_list:
                value = value[m]
        except KeyError:
            continue
        data['_'.join(metrics_list)] = value

    return data
