# -*- coding: utf-8 -*-

from common.models import Job, Server
from django.core.cache import cache
from django.db import models
from common.util.decorators import cached
from common.util.clients import ClickhouseClient

from django.db import transaction, DatabaseError
from django.db.models.constants import LOOKUP_SEP
from django.utils import six
import sys
from collections import namedtuple


class MetricQuerySet(models.query.QuerySet):
    def get_or_create(self, **kwargs):
        """
        Looks up an object with the given kwargs, creating one if necessary.
        Returns a tuple of (object, created), where created is a boolean
        specifying whether an object was created.
        """
        defaults = kwargs.pop('defaults', {})
        lookup = kwargs.copy()
        for f in self.model._meta.fields:
            if f.attname in lookup:
                lookup[f.name] = lookup.pop(f.attname)
        try:
            self._for_write = True
            return self.get(**lookup), False
        except self.model.MultipleObjectsReturned:
            return self.first(), False
        except self.model.DoesNotExist:
            try:
                params = dict((k, v) for k, v in kwargs.items() if LOOKUP_SEP not in k)
                params.update(defaults)
                obj = self.model(**params)
                with transaction.atomic(using=self.db):
                    obj.save(force_insert=True, using=self.db)
                return obj, True
            except DatabaseError:
                exc_info = sys.exc_info()
                try:
                    return self.get(**lookup), False
                except self.model.DoesNotExist:
                    # Re-raise the DatabaseError with its original traceback.
                    six.reraise(*exc_info)


class MetricManager(models.Manager):
    def get_query_set(self):
        return MetricQuerySet(self.model)


class Metric(models.Model):
    name = models.CharField(max_length=255)
    code = models.TextField(null=1)
    description = models.CharField(max_length=5000)
    created_at = models.DateTimeField(auto_now_add=True)
    modified_at = models.DateTimeField(auto_now=True)

    objects = MetricManager()


class JobMetricTarget(object):
    """
    Кэшированные данные по мишеням и метрикам для стрельбы.
    """

    JMT = namedtuple('JMT', ('job', 'target', 'metric'))

    def __init__(self, job_n, bypass_cache=False):
        self.job_n = job_n
        self.bypass_cache = bypass_cache
        self.cache_key = "jobmetrictarget_%s" % job_n

    @property
    def job_obj(self):
        return Job.objects.get(n=self.job_n)

    @property
    def objects(self):
        @cached(self.cache_key, bypass=self.bypass_cache)
        def get_objects():
            ch_client = ClickhouseClient()
            sql = '''
                select target_host, metric_name
                from loaddb.monitoring_verbose_data_buffer
                where job_id=%(job)s
                and job_date=toDate(%(job_date)s)  
                group by target_host, metric_name
                order by target_host, metric_name
            '''

            jmt = ch_client.select(sql, query_params=self.job_obj.basic_query_params)
            return [(self.job_n, j[0], j[1]) for j in jmt]

        cached_jmt = get_objects()
        return [self.JMT(*j) for j in cached_jmt]


class JobMetricTargetManager(object):
    _cache_prefix = 'MON_JOB_METRCIS_'
    _cache_ttl = 3600 * 24

    def set_cache(self, job_id, hosts):
        """
        Structure of 'hosts':
        {
            'host_name_1': {
                'host': <server_id>,
                'metrics: [<metric_id_1>, ..., <metric_id_N>]
            },
            ...
        }
        """
        cache.set(self._cache_prefix + str(job_id), hosts, self._cache_ttl)
        for host in hosts:
            for metric in hosts[host]['metrics']:
                if not str(metric).isdigit():
                    Metric.objects.get_or_create(code=metric)

    def get_cache(self, job_id):
        hosts = cache.get(self._cache_prefix + str(job_id))
        if not hosts:
            hosts = {}
            jmt = JobMetricTarget(job_id, bypass_cache=True).objects
            for j in jmt:
                if j.target not in hosts:
                    hosts[j.target] = {
                        'host': Server.objects.get_or_create(host=j.target)[0].n,
                        'metrics': []
                    }
                hosts[j.target]['metrics'].append(j.metric)
            # for metric in (self.filter(job_id=job_id)
            #                .values('target', 'target__host', 'metric')
            #                .order_by('job', 'target', 'position')):
            #     if metric['target__host'] not in hosts:
            #         hosts[metric['target__host']] = {
            #             'host': metric['target'],
            #             'metrics': []
            #         }
            #     hosts[metric['target__host']]['metrics'].append(metric['metric'])
        return hosts

    def drop_cache(self, job_id):
        cache.delete(self._cache_prefix + str(job_id))
