# -*- coding: utf-8 -*-
import json
import logging
import time
import html
from base64 import b64decode
from datetime import datetime
from pprint import pformat

from django.http import HttpResponse, HttpResponseBadRequest
from django.http.response import HttpResponseGone, HttpResponseNotFound
from django.views.generic.base import View

from common.models import Server, Job, DeletedJob
from common.util.meta import escape_string
from common.util.clients import ClickhouseClient
from common.util.decorators import cached
from .models import JobMetricTargetManager


class JobException(Exception):
    pass


class CustomMetricException(Exception):
    pass


class FormatException(Exception):
    pass


class PutData(View):
    """
    Store monitoring data into DB

    JSON data format:
    [
    {
    'timestamp': 1293819283,
    'data': {'localhost', {'metrics': {<metric_name>: <value>}}}
    }
    ]
    CSV data format:
    https://wiki.yandex-team.ru/nagruzochnoetestirovanie/lunapark/dev/monitoring/target/pipe-format#metriki
    """
    is_debug = False  # write log
    _uncached_hosts = {}
    _hosts = None

    jmt = JobMetricTargetManager()

    def post(self, request, **kwargs):
        if request.GET.get('debug', False):
            self.is_debug = True
        self._log('started')
        job_n = request.GET.get('job_id')
        try:
            assert job_n.isdigit(), "Wrong job: %s" % job_n
            job_obj = self._job(request)
        except AssertionError as e:
            return HttpResponseBadRequest(e)
        except JobException as e:
            deleted = False
            logging.error('Problems with Put data', exc_info=True)
            if DeletedJob.objects.filter(n=job_n):
                deleted = True
                msg = 'Job had been deleted'
            else:
                msg = 'Job not found'
            if deleted:
                return HttpResponseGone(
                    json.dumps([{
                        'success': False,
                        'error': msg,
                        'msg': msg,  # backwards compatibility
                    }]),
                    content_type='application/json')
            else:
                return HttpResponseNotFound(
                    json.dumps([{
                        'success': False,
                        'error': msg,
                        'msg': msg,  # backwards compatibility
                    }]),
                    content_type='application/json')

        # @cached('job_%s_fd' % job_n)
        def get_fd():
            return int(time.mktime(job_obj.fd.timetuple()))

        values = []

        try:
            try:
                dataset = json.loads(request.body.decode('utf-8'))
                assert isinstance(dataset, list)
            except (ValueError, AssertionError):
                raise FormatException()
            for item in dataset:
                timestamp = item['timestamp']
                servers = item['data']
                for server in servers.items():
                    server_obj = self._host(server[0], server[1].get('comment'))
                    metrics = server[1]['metrics']
                    # validate metrics
                    for metric in list(metrics.keys()):
                        if metric.lower().startswith('custom:') and not metric.lower().split(':')[1]:
                            self._log('detected сustom metric with empty name')
                            return HttpResponseBadRequest('Empty name for custom metric')
                    try:
                        values.extend([','.join(map(str, [
                            "toDate(%s)" % get_fd(),
                            int(job_n),
                            "'%s'" % escape_string(server_obj['host']),
                            "'%s'" % escape_string(metric),
                            "toDateTime(%s)" % int(timestamp),
                            float(value),
                        ]))
                                       for metric, value in metrics.items() if
                                       value != ''])  # skip empty value, LUNAPARK-1143
                    except TypeError:
                        return HttpResponseBadRequest('Invalid monitoring data')

        except FormatException:
            self._hosts_from_cache(job_n)
            try:
                dataset = json.loads(request.body.decode('utf-8')).strip().split('\n')
            except ValueError:
                dataset = request.body.strip().split('\n')

            for data in dataset:
                self._log('parsing data string: ' + data)
                t = data.split(';')
                t_len = len(t)

                # detect host and metrics
                if t_len > 2 and t[0] == 'start':
                    try:
                        self._log('detected string with metrics')
                        self._parse_host_metrics(t[1].strip(), t[3:])
                        self._hosts_to_cache(job_n)
                    except CustomMetricException:
                        self._log('detected сustom metric with empty name')
                        self._hosts_cache_clear(job_n)
                        return HttpResponseBadRequest('Empty name for custom metric')
                    continue

                host_name = t[0].strip()
                if host_name not in self._hosts:
                    self._log('detected host without metrics: %s' % host_name)
                    self._hosts_cache_clear(job_n)
                    return HttpResponseBadRequest('Unknown target: ' + html.escape(host_name))

                # prepare values to store monitoring data
                metrics = self._hosts[host_name]['metrics']
                if len(metrics) != t_len - 2:
                    self._log('detected wrong number of metrics data')
                    self._hosts_cache_clear(job_n)
                    return HttpResponseBadRequest('Wrong number of metrics data: ' + html.escape(data))

                values.extend([','.join(map(str, [
                    "toDate(%s)" % get_fd(),
                    int(job_n),
                    "'%s'" % escape_string(host_name),
                    "'%s'" % escape_string(metrics[i - 2]),
                    "toDateTime(%s)" % int(t[1]),
                    float(t[i]),
                ]))
                               for i in range(2, t_len) if t[i] != ''])  # skip empty value, LUNAPARK-1143
        if values:
            ch_client = ClickhouseClient()
            sql = 'INSERT INTO loaddb.monitoring_verbose_data_buffer VALUES ({mon_data})'.format(
                mon_data='),('.join(values)
            )
            ch_client.insert(sql)
            ch_client.session.close()

        self._log('finished')
        return HttpResponse('ok')

    def _parse_host_metrics(self, host_name, metrics):
        #        if host_name in self._hosts:
        #            return

        host = self._host(host_name)
        self._hosts[host['host']] = {'metrics': [], 'host': host['n']}

        for metric_code in metrics:
            code = metric_code.strip()
            if code.lower().find('custom:') == 0:
                # custom metric format:
                # "Custom:base64.encode(metric_name):base64.encode(metric_cmd)"
                t = code.split(':')
                if not t[1]:
                    # empty custom metric name: "Custom:"
                    raise CustomMetricException()
                try:
                    custom_code = b64decode(t[1])
                except TypeError:
                    custom_code = t[1]
                metric_name = 'custom:%s' % custom_code
            else:
                metric_name = code
            self._hosts[host['host']]['metrics'].append(metric_name)
        self._log('parsed metrics: %s for host %s' % (
            pformat(self._hosts[host['host']]['metrics']),
            host_name))

    def _job(self, request):
        job_id = request.GET.get('job_id')

        logging.debug("JOBID: %s", job_id)
        if not job_id:
            self._log('empty GET-param job_id')
            raise JobException('Empty job_id')
        try:
            job = Job.objects.get(n=job_id)
            self._log('got job #%s' % job.n)
            return job
        except Job.DoesNotExist:
            self._log('non existent job #%s' % job_id)
            raise JobException('Invalid job_id %s' % job_id)

    def _host(self, host_name, comment=None):
        @cached('server_object_%s' % host_name)
        def get_hosts():
            # check default db
            try:
                server = Server.objects.get(host=host_name)
                if comment is not None and comment != server.dsc and server.host not in ('localhost', '127.0.0.1'):
                    server.dsc = comment
                    server.save()
                self._log('got host: %d, %s' % (server.n, server.host))
            except Server.DoesNotExist:
                now = datetime.now()
                server = Server(host=host_name, fd=now, td=now, dsc=comment)
                server.save()
            self._log('created a new host: %d, %s' % (server.n, server.host))
            self._uncached_hosts[host_name] = True
            return {'host': server.host, 'n': server.n}

        return get_hosts()

    def _hosts_from_cache(self, job_id):
        logging.debug('Getting hosts from cache')
        self._uncached_hosts = {}
        self._hosts = self.jmt.get_cache(job_id)
        if self._hosts:
            self._log('got metrics data from cache: ' + pformat(self._hosts))
        else:
            logging.debug('No metrics from cache')

    def _hosts_to_cache(self, job_id):
        if self._uncached_hosts:
            self.jmt.set_cache(job_id, self._hosts)
            self._log('stored metrics to cache: %s' % pformat(self._hosts))

    def _hosts_cache_clear(self, job_id):
        try:
            self.jmt.drop_cache(job_id)
            self._log('dropped metrics from cache for job %s' % job_id)
        except:
            logging.exception('Could not drop metrics cache for job %s', job_id)

    @staticmethod
    def _log(msg):
        logging.debug('Put monitoring data: ' + msg)
