import uuid
from datetime import datetime
from enum import Enum
from io import BytesIO

import logging
import pandas as pd
from django.contrib.postgres.fields import ArrayField, HStoreField
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models.deletion import DO_NOTHING
from collections import defaultdict
from common.util import ClickhouseClient, memoized_property
import sys
import warnings

logger = logging.getLogger(__name__)

if not sys.warnoptions:
    warnings.simplefilter('ignore')


class DeletedJobManager(models.Manager):
    def get_queryset(self):
        """
        Переопределяем queryset, чтобы по умолчанию выбирались только не удаленные джобы
        """
        return super().get_queryset().filter(is_deleted=True)


class JobManager(models.Manager):
    def get_queryset(self):
        """
        Переопределяем queryset, чтобы по умолчанию выбирались только не удаленные джобы
        """
        return super().get_queryset().filter(is_deleted=False)


class JobStatusChoice(Enum):
    finished = 'finished'


class Job(models.Model):
    status = models.CharField(max_length=32, default='')
    host = models.CharField(max_length=64, null=True)
    port = models.IntegerField(null=True)
    local_uuid = models.CharField(max_length=64, null=True)
    test_start = models.BigIntegerField(default=0)
    is_deleted = models.BooleanField(default=False)
    duration = models.BigIntegerField(default=None, null=True)

    objects = JobManager()
    deleted = DeletedJobManager()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._jobmeta_set = None

    def __str__(self):
        return 'Id: {}'.format(self.pk)

    @property
    def attributes(self):
        return {
            'status': self.status,
            'start': self.test_start,
            'duration': self.duration
        }

    @property
    def meta(self):
        if self._jobmeta_set is None:
            self._jobmeta_set = {jm.key: jm.value for jm in self.jobmeta_set.all()}
        return self._jobmeta_set

    @property
    def full_meta(self):
        """
        Добавляем к мете значения атрибутов модели с нижним подчеркиванием
        :return: dict
        """
        # fixme: prefetch
        fm = {jm.key: jm.value for jm in JobMeta.objects.filter(job=self)}
        fm.update({
            '_id': self.pk,
            '_status': self.status,
            '_test_start': self.test_start,
            '_duration': self.duration,
        })
        return fm

    @property
    def immutable_meta(self):
        """
        сделаем это свойством инстанса,
        так как, может быть, захотим сделать неизменяемыми разные наборы меты для стрельб в разных статусах
        :return:
        """
        return ['id', 'job', '_id', '_job', 'duration', '_duration', 'test_start', '_test_start', 'status', '_status']

    @property
    def key_date(self):
        return datetime.fromtimestamp(self.test_start // 10 ** 6).strftime('%Y-%m-%d')

    @memoized_property
    def fragments(self):
        """

        :return: {name: [duration, average, consumption(mAh)]}
        """
        events_tags = [d.tag() for d in Data.objects.filter(job=self, type='events')]
        if not events_tags:
            return {}
        ch_client = ClickhouseClient()
        query = '''
                SELECT e.tag, e.ts, e.value 
                FROM events e 
                WHERE e.key_date = toDate('{key_date}')
                AND e.tag IN ('{tags}')
                AND (e.value LIKE 'fragment%start%' OR e.value LIKE 'fragment%stop%')
            '''
        fragment_events = ch_client.select(query, query_params={
            'tags': "','".join(events_tags),
            'key_date': self.key_date
        })

        fragments = defaultdict(dict)
        for v in fragment_events:
            tag = v[0]
            offset = Data.objects.get(uniq_id=tag).offset
            ts = int(v[1])
            value = v[2].strip().split(' ')
            fragments[value[1]][value[2]] = ts + offset

        data = {}
        data_obj = DataMeta.objects.get(key='name', value='current', data__job_id=self.id).data
        for fragment in fragments.items():
            avg_consumption = self._fragment_avg_consumption(
                data_obj, fragment[1]['start'], fragment[1].get('end', 0) or fragment[1].get('stop', 0)
            )
            data[fragment[0]] = [
                float((fragment[1].get('end', 0)
                       or fragment[1].get('stop', 0)) - fragment[1]['start']) / 1000,  # миллисекунды
                avg_consumption,
                # divide by 3600 to get mAh
                round(float(avg_consumption) / 3600 * (
                    (fragment[1].get('end', 0)
                     or fragment[1].get('stop', 0)) - fragment[1]['start']) / 1000000, 3),  # секунды
            ]
        return data

    @staticmethod
    def _fragment_avg_consumption(data_obj, start, end):
        ch_client = ClickhouseClient(readonly=True)
        query = '''
            SELECT round(avg(value), 3)
            FROM metrics
            WHERE tag='{tag}'
            AND key_date=toDate('{key_date}')
            AND ts+{OFFSET} >= {START}
            AND ts+{OFFSET} <= {END}
        '''
        query_params = {'tag': data_obj.uniq_id,
                        'key_date': data_obj.job.key_date,
                        'start': start,
                        'end': end,
                        'offset': data_obj.offset,
                        }

        avg_consumption = ch_client.select(query, query_params=query_params) or [[0]]
        return avg_consumption[0][0]

    class Meta:
        db_table = 'job'


class JobMetaQuerySet(models.query.QuerySet):

    def get_or_create(self, defaults=None, **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.
        """
        lookup, params = self._extract_model_params(defaults, **kwargs)
        # The get() needs to be targeted at the write database in order
        # to avoid potential transaction consistency problems.
        self._for_write = True
        try:
            return self.get(**lookup), False
        except self.model.MultipleObjectsReturned:
            return self.first(), False
        except self.model.DoesNotExist:
            return self._create_object_from_params(lookup, params)


class JobMetaManager(models.Manager):
    def get_query_set(self):
        return JobMetaQuerySet(self.model)


class Config(models.Model):
    job = models.ForeignKey(Job, on_delete=DO_NOTHING)
    name = models.CharField(max_length=32)
    content = models.TextField()

    class Meta:
        unique_together = (('job', 'name'),)


class JobMeta(models.Model):
    """
    - значением может быть строка или список строк
    - значением ключа _duration может быть только одна строка (? не должно ли _duration быть полем у Джоба?)
    - запрос метаинформации у джобы должен возвращать все ключи с соответствующими значениями / списками значений
    """

    job = models.ForeignKey(Job, on_delete=DO_NOTHING)
    key = models.CharField(max_length=256, null=False)
    value = models.TextField(null=True, default='')

    objects = JobMetaManager()

    def __str__(self):
        return 'Job: {} Key: {} Value:'.format(self.job.pk, self.key, self.value)

    class Meta:
        db_table = 'job_meta'
        unique_together = (('job', 'key'),)


class DataTypeChoice(Enum):
    metrics = 'metrics'
    events = 'events'
    aggregates = 'aggregates'
    distributions = 'distributions'
    histograms = 'histograms'


class DataTypeOrder(Enum):
    metrics = 0
    events = 1
    aggregates = 2
    distributions = 3
    histograms = 4


class Data(models.Model):
    job = models.ForeignKey(Job, on_delete=DO_NOTHING)
    type = models.CharField(max_length=16)  # 'metrics', 'events'
    types = ArrayField(
        models.CharField(max_length=20, choices=[(t.name, t.value) for t in DataTypeChoice]),
        default=list
    )
    offset = models.BigIntegerField(default=0)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._datameta_set = None

    def __str__(self):
        return 'Job: {} Tags: {}'.format(self.job.id, self.case_set.values_list('name','tag'))

    class Meta:
        db_table = 'data'

    def tag(self, case=None):
        """
        :rtype: str
        """
        case = case or Case.OVERALL
        return self.case_set.get(name=case).tag.hex

    @property
    def meta(self):
        if self._datameta_set is None:
            self._datameta_set = {dm.key: dm.value for dm in self.datameta_set.all()}
        return self._datameta_set

    @property
    def full_meta(self):
        """
        Добавляем к мете значения атрибутов модели с нижним подчеркиванием
        :return: dict
        """
        fm = self.meta
        fm.update({
            '_id': self.pk,
            '_type': self.type,
            '_types': self.types_list(),
            '_offset': self.offset,
            '_job': self.job.pk,
        })
        return fm

    def types_list(self):
        result = []
        for enum_name, enum_order in zip(DataTypeChoice, DataTypeOrder):
            name = enum_name.value
            order = enum_order.value
            if self._has_dtype(order):
                result.append(name)
        return result

    @property
    def has_raw(self):
        return self._has_dtype(DataTypeOrder.metrics.value)

    @property
    def has_aggregates(self):
        """
        Ищет агрегаты для этого uniq_id в мете или в соответствующей таблице
        :return: bool
        """
        return self._has_dtype(DataTypeOrder.aggregates.value)

    @property
    def has_distributions(self):
        """
        Ищет distributions для этого uniq_id в мете или в соответствующей таблице
        :return: bool
        """
        return self._has_dtype(DataTypeOrder.distributions.value)

    @property
    def has_histograms(self):
        """
        Ищет histograms для этого uniq_id в мете или в соответствующей таблице
        :return: bool
        """
        return self._has_dtype(DataTypeOrder.histograms.value)

    @property
    def has_events(self):
        return self._has_dtype(DataTypeOrder.events.value)

    def _has_dtype(self, dtype_id):
        if self._incomplete_db_record():
            self.types = cc_get_metrics_types([self.tag()])[self.tag()]
            self.save()
        return int(self.types[dtype_id]) == 1

    def _incomplete_db_record(self):
        return len(self.types) != len(DataTypeOrder) or any(not str(value).isnumeric() for value in self.types)

    def summary(self, start=None, end=None, case=None, fillna='no value'):
        case = case or Case.OVERALL
        names = ['max', 'q99', 'q98', 'q95', 'q90', 'q85', 'q80', 'q75', 'q50', 'q25', 'q10', 'min',
                 'average']
        no_result = pd.DataFrame([[str(self.id)] + [fillna for _ in names]], columns=['tag'] + names)
        if self.has_raw:
            summary = (str(self.id),) + tuple(cc_get_metric_summary(self.tag(case), start, end, self.offset))
            return pd.DataFrame.from_records([summary],
                                             columns=['tag'] + names + ['stddev'])
        elif self.has_aggregates and self.has_distributions:
            data_from_distr = dict(cc_get_aggr_summary(self.tag(case), start, end), tag=str(self.id))
            return pd.DataFrame([data_from_distr], columns=['tag'] + names).fillna(fillna) \
                if fillna is not None \
                else pd.DataFrame([data_from_distr], columns=['tag'] + names)
        elif self.has_histograms:
            summary = cc_get_hist_summary(self.tag(case), start, end)
            # summary['tag'] = str(self.id)
            return pd.DataFrame(dict(summary, tag=str(self.id)),
                                columns=['tag']+list(summary.keys())) if summary else pd.DataFrame()  #, columns=['tag'] + [category for category, cnt in cc_data])\
        else:
            return no_result


class DataMeta(models.Model):
    data = models.ForeignKey(Data, on_delete=DO_NOTHING)
    key = models.CharField(max_length=256, null=False)
    value = models.TextField(null=True, default='')

    def __str__(self):
        return 'Data: {} Key: {} Value: {}'.format(self.data.id, self.key, self.value)

    class Meta:
        db_table = 'data_meta'
        unique_together = (('data', 'key'),)

    def as_tuple(self):
        return (self.key, self.value)


class RegressionManager(models.Manager):
    def get_queryset(self):
        return super(RegressionManager, self).get_queryset().annotate(last_job=models.Max('jobs__id'))


class Regression(models.Model):
    name = models.CharField(max_length=512, unique=True)
    group_by = models.CharField(max_length=256, default='_job')
    person = models.CharField(max_length=50)
    creation = models.BigIntegerField(validators=[MinValueValidator(0)])
    jobs = models.ManyToManyField(Job)

    objects = RegressionManager()

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._regression_meta = None

    @property
    def regression_meta(self):
        if self._regression_meta is None:
            self._regression_meta = {rm.key: rm.value for rm in self.regressionmeta_set.all()}
        return self._regression_meta

    def attributes_dict(self, keys=None):
        attrs = {
            'id': self.id,
            'name': self.name,
            'person': self.person,
            'creation': self.creation,
            'group_by': self.group_by,
            'last_job': self.last_job
        }
        return {k: v for k, v in attrs.items() if not keys or k in keys}

    def get_slas(self):
        return [sla for regr_seria in self.regressionseries_set.all() for sla in regr_seria.sla_set.all()]


class RegressionMeta(models.Model):

    regression = models.ForeignKey(Regression, on_delete=DO_NOTHING)
    key = models.CharField(max_length=256, null=False)
    value = models.TextField(null=True, default='')

    def __str__(self):
        return 'Regression: {} Key: {} Value: {}'.format(self.regression.pk, self.key, self.value)

    class Meta:
        db_table = 'regression_meta'
        unique_together = (('regression', 'key'),)


class RegressionSeries(models.Model):
    regression = models.ForeignKey(Regression)
    filter = HStoreField()


class Function(models.Model):
    name = models.CharField(max_length=512, unique=True)
    description = models.TextField()
    is_parametrized = models.BooleanField(default=False)


class SLA(models.Model):
    regression_series = models.ForeignKey(RegressionSeries)
    name = models.CharField(max_length=512, blank=True, default='')
    function = models.CharField(max_length=512)
    args = ArrayField(base_field=models.CharField(max_length=128), default=list)
    lt = models.FloatField(null=True, blank=True)
    le = models.FloatField(null=True, blank=True)
    ge = models.FloatField(null=True, blank=True)
    gt = models.FloatField(null=True, blank=True)

    @classmethod
    def from_cfg(cls, regr_seria, function, **kw):
        try:
            function_name, rest = function.split('(', 1)
            args = list(rest.strip(')').split(','))
        except ValueError:
            function_name = function
            args = []
        return cls(regression_series=regr_seria,
                   function=function_name,
                   args=args, **kw)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._dict_repr = None

    def widget_name(self):
        # type: () -> str
        """
        SLA name is used in layout as widget name.
        """
        if not self.name:
            filter_name = ', '.join(['{}: {}'.format(k, v) for k, v in self.regression_series.filter.items()])
            return '{} in {}'.format(self.function_with_args(), filter_name)
        else:
            return self.name

    def function_with_args(self):
        parameters = list(self.args.copy())
        if not parameters:
            return self.function
        return '{function}({args})'.format(**{
            'function': self.function,
            'args': ','.join(parameters)
        })

    @property
    def dict_repr(self):
        if self._dict_repr is None:
            self._dict_repr = {
                attr_name: self.__getattribute__(attr_name) for attr_name
                in ['lt', 'le', 'gt', 'ge'] if self.__getattribute__(attr_name) is not None
            }
            if self.name:
                self._dict_repr['name'] = self.name
            self._dict_repr['function'] = self.function_with_args()
        return self._dict_repr

    def constraints_as_dict(self):
        return {k: v for k, v in [("gt", self.gt),
                                  ("lt", self.lt),
                                  ("ge", self.ge),
                                  ("le", self.le)]
                if v is not None}


class StatStorage(models.Model):
    function = models.CharField(max_length=512)
    job = models.ForeignKey(Job)
    filter = HStoreField()
    args = ArrayField(base_field=models.CharField(max_length=128), default=list)
    value = models.FloatField()

    class Meta:
        unique_together = (('function', 'job', 'filter', 'args'),)


def cc_get_metrics_types(tags):
    cols = ', '.join(['%s > 0' % i.name for i in DataTypeOrder])
    SQL = "select _main.tag, [{cols}] from (SELECT arrayJoin([{{tags}}]) tag) as _main\n{joins}".format(
        cols=cols,
        joins='\n'.join(
            ['left join ('
             'select tag, count() as {table} from {table}_buffer where tag in ({{tags}}) group by tag) '
             'as _{table} on _main.tag=_{table}.tag'.format(table=i.name) for i in DataTypeOrder]
        )
    )
    results = ClickhouseClient().select(SQL, {'tags': ', '.join(["'%s'"%tag for tag in tags])})
    return {tag[0]: tag[1] for tag in results}


def cc_get_hist_summary(tag, abs_start_microsec, abs_end_microsec):
    query = '''
    SELECT category, sum(cnt) FROM histograms WHERE tag='{tag}' 
    '''
    if abs_start_microsec is not None:
        query += 'AND ts >= {start} '
    if abs_end_microsec is not None:
        query += 'AND ts <= {end} '
    query += 'GROUP BY category'

    cc_data = ClickhouseClient().select(query, query_params={'tag': tag, 'start': abs_start_microsec, 'end': abs_end_microsec})
    return {category: [cnt] for category, cnt in
               cc_data}
    # df = pd.DataFrame(dict([('tag', tag)] +
    #                        [(category, [cnt]) for category, cnt in cc_data]),
    #                   columns=['tag'] + [category for category, cnt in cc_data]) if cc_data else pd.DataFrame()
    # return df


def cc_get_aggr_summary(tag, abs_start_microsec, abs_end_microsec):
    """

    names = ['max', 'q99', 'q98', 'q95', 'q90', 'q85', 'q80', 'q75', 'q50', 'q25', 'q10', 'min', 'average', 'stddev']
    :return: pd.DataFrame([[self.tag] + ['not implemented' for n in names]], columns=['tag'] + names)
    """
    result = {}
    perc_list = [0, 10, 25, 50, 75, 80, 85, 90, 95, 98, 99, 100]
    start_constraint = 'AND ts >= toInt64({}) '.format(abs_start_microsec) if abs_start_microsec else ''
    end_constraint = 'AND ts <= toInt64({}) '.format(abs_end_microsec) if abs_end_microsec else ''

    query_distr = '''
    SELECT l as left, r as right, sum(cnt) 
    FROM distributions 
    WHERE tag in ('{tag}') {start} {end} 
    GROUP BY l, r
    ORDER BY r
    '''
    df_distr = pd.read_csv(
        BytesIO(ClickhouseClient().select_csv(
            query_distr, query_params={'tag': tag, 'start': start_constraint, 'end': end_constraint})),
            names=['left', 'right', 'sum'],
            index_col='right'
        )
    df_distr = df_distr.sort_index()
    df_distr['sum_cumulative'] = df_distr['sum'].cumsum()
    cum_sum_series = df_distr.loc[:, 'sum_cumulative'.format(tag)]

    query_aggr = '''
    SELECT q0, q100, average
    FROM aggregates
    WHERE tag in ('{tag}') {start} {end}
    '''
    df_aggr = pd.read_csv(
        BytesIO(ClickhouseClient().select_csv(
            query_aggr, query_params={'tag': tag, 'start': start_constraint, 'end': end_constraint})),
        names=['min', 'max', 'average']
    )

    if not df_distr.empty:
        for i in perc_list:
            result['q{}'.format(i)] = (cum_sum_series >= cum_sum_series.quantile(i / 100)).idxmax()

    if not df_aggr.empty:
        result['average'] = df_aggr['average'].sum() / df_aggr.shape[0]
        result['max'] = df_aggr['max'].max()
        result['min'] = df_aggr['min'].min()

    return result


def cc_get_metric_summary(tag, rel_start_sec, rel_end_sec, offset_sec):
    """

    :param tags: список тэгов метрик
    :param rel_start_sec: начало запрашиваемого интервала (микросекунды int)
    :param rel_end_sec: конец запрашиваемого интервала (микросекунды int)
    :param test_start: время начала теста (микросекунды int)
    :param offset_sec: офсет метрики (микросекунды int)
    :return:
    """
    start_constraint = 'and ts >= toUInt64({start}) - {offset} ' if rel_start_sec is not None else ''
    end_constraint = 'and ts <= toUInt64({end}) - {offset} ' if rel_end_sec is not None else ''
    query = '''
            SELECT
            max(value),
            quantilesExact(0.99)(value)[1],
            quantilesExact(0.98)(value)[1],
            quantilesExact(0.95)(value)[1],
            quantilesExact(0.90)(value)[1],
            quantilesExact(0.85)(value)[1],
            quantilesExact(0.80)(value)[1],
            quantilesExact(0.75)(value)[1],
            quantilesExact(0.50)(value)[1],
            quantilesExact(0.25)(value)[1],
            quantilesExact(0.10)(value)[1],
            min(value),
            avg(value),
            stddevPop(value)
            FROM metrics 
            WHERE tag = '{tag}'
            ''' + \
            start_constraint + end_constraint + 'group BY tag'
    db_timestamp_start = rel_start_sec * 1000000 if rel_start_sec is not None else 0
    db_timestamp_end = rel_end_sec * 1000000 if rel_end_sec is not None else 0
    cc_data = ClickhouseClient().select(query, query_params={
        'tag': tag,
        'start': db_timestamp_start,
        'end': db_timestamp_end,
        'offset': offset_sec,
    })[0]
    return cc_data


class Case(models.Model):
    OVERALL = '__overall__'
    data = models.ForeignKey(Data, on_delete=DO_NOTHING, null=False)
    name = models.TextField(null=False, unique=False)
    tag = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)

    class Meta:
        db_table = 'case'

    def summary(self, start=None, end=None, fillna='no value'):
        return self.data.summary(start, end, self.name, fillna=fillna)


CLICKHOUSE_REPLICATES_DB_STRUCT = '''
CREATE DATABASE IF NOT EXISTS luna;

USE luna;
'''
VLA = '''
CREATE TABLE IF NOT EXISTS metrics (key_date Date, tag String, ts Int64, value Float64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/metrics', 'vla', key_date, tag, (tag, ts), 8192);
CREATE TABLE IF NOT EXISTS events (key_date Date, tag String, ts Int64, value String) ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/events', 'vla', key_date, tag, (tag, ts), 8192);

CREATE TABLE IF NOT EXISTS aggregates (key_date Date, tag String, ts Int64, q0 Float64, q10 Float64, q25 Float64, q50 Float64, q75 Float64, q80 Float64, q85 Float64, q90 Float64, q95 Float64, q98 Float64, q99 Float64, q100 Float64, average Float64, stddev Float64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/aggregates', 'vla', key_date, tag, (tag, ts), 8192);
CREATE TABLE IF NOT EXISTS distributions (key_date Date, tag String, ts Int64, l Int64, r Int64, cnt Int64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/distributions', 'vla', key_date, tag, (tag, ts), 8192);
CREATE TABLE IF NOT EXISTS histograms (key_date Date, tag String, ts Int64, category String, cnt Int64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/histograms', 'vla', key_date, tag, (tag, ts), 8192);


CREATE TABLE IF NOT EXISTS metrics_buffer AS metrics ENGINE = Buffer(luna_test, metrics, 16, 10, 100, 10000, 1000000, 10000000, 20000000);
CREATE TABLE IF NOT EXISTS events_buffer AS events ENGINE = Buffer(luna_test, events, 16, 10, 100, 10000, 1000000, 10000000, 20000000);

CREATE TABLE IF NOT EXISTS aggregates_buffer AS aggregates ENGINE = Buffer(luna_test, aggregates, 16, 10, 100, 10000, 1000000, 10000000, 20000000);
CREATE TABLE IF NOT EXISTS distributions_buffer AS distributions ENGINE = Buffer(luna_test, distributions, 16, 10, 100, 10000, 1000000, 10000000, 20000000);
CREATE TABLE IF NOT EXISTS histograms_buffer AS histograms ENGINE = Buffer(luna_test, histograms, 16, 10, 100, 10000, 1000000, 10000000, 20000000);

'''
MAN = '''
CREATE TABLE IF NOT EXISTS metrics (key_date Date, tag String, ts Int64, value Float64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/metrics', 'man', key_date, tag, (tag, ts), 8192);
CREATE TABLE IF NOT EXISTS events (key_date Date, tag String, ts Int64, value String) ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/events', 'man', key_date, tag, (tag, ts), 8192);

CREATE TABLE IF NOT EXISTS aggregates (key_date Date, tag String, ts Int64, q0 Float64, q10 Float64, q25 Float64, q50 Float64, q75 Float64, q80 Float64, q85 Float64, q90 Float64, q95 Float64, q98 Float64, q99 Float64, q100 Float64, average Float64, stddev Float64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/aggregates', 'man', key_date, tag, (tag, ts), 8192);
CREATE TABLE IF NOT EXISTS distributions (key_date Date, tag String, ts Int64, l Int64, r Int64, cnt Int64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/distributions', 'man', key_date, tag, (tag, ts), 8192);
CREATE TABLE IF NOT EXISTS histograms (key_date Date, tag String, ts Int64, category String, cnt Int64) ENGINE = ReplicatedMergeTree('/clickhouse/tables/01/histograms', 'man', key_date, tag, (tag, ts), 8192);


CREATE TABLE IF NOT EXISTS metrics_buffer AS metrics ENGINE = Buffer(luna_test, metrics, 16, 10, 100, 10000, 1000000, 10000000, 20000000);
CREATE TABLE IF NOT EXISTS events_buffer AS events ENGINE = Buffer(luna_test, events, 16, 10, 100, 10000, 1000000, 10000000, 20000000);

CREATE TABLE IF NOT EXISTS aggregates_buffer AS aggregates ENGINE = Buffer(luna_test, aggregates, 16, 10, 100, 10000, 1000000, 10000000, 20000000);
CREATE TABLE IF NOT EXISTS distributions_buffer AS distributions ENGINE = Buffer(luna_test, distributions, 16, 10, 100, 10000, 1000000, 10000000, 20000000);
CREATE TABLE IF NOT EXISTS histograms_buffer AS histograms ENGINE = Buffer(luna_test, histograms, 16, 10, 100, 10000, 1000000, 10000000, 20000000);
'''

CLICKHOUSE_DB_STRUCT = '''
CREATE DATABASE IF NOT EXISTS luna;

USE luna;

CREATE TABLE IF NOT EXISTS metrics (key_date Date, tag String, ts Int64, value Float64) ENGINE = MergeTree(key_date, tag, (tag, ts), 8192);
CREATE TABLE IF NOT EXISTS events (key_date Date, tag String, ts Int64, value String) ENGINE = MergeTree(key_date, tag, (tag, ts), 8192);

CREATE TABLE IF NOT EXISTS aggregates (key_date Date, tag String, ts Int64, q0 Float64, q10 Float64, q25 Float64, q50 Float64, q75 Float64, q80 Float64, q85 Float64, q90 Float64, q95 Float64, q98 Float64, q99 Float64, q100 Float64, average Float64, stddev Float64) ENGINE = MergeTree(key_date, tag, (tag, ts), 8192);
CREATE TABLE IF NOT EXISTS distributions (key_date Date, tag String, ts Int64, l Int64, r Int64, cnt Int64) ENGINE = MergeTree(key_date, tag, (tag, ts), 8192);
CREATE TABLE IF NOT EXISTS histograms (key_date Date, tag String, ts Int64, category String, cnt Int64) ENGINE = MergeTree(key_date, tag, (tag, ts), 8192);


CREATE TABLE IF NOT EXISTS metrics_buffer AS metrics ENGINE = Buffer(luna, metrics, 16, 10, 100, 10000, 1000000, 10000000, 20000000);
CREATE TABLE IF NOT EXISTS events_buffer AS events ENGINE = Buffer(luna, events, 16, 10, 100, 10000, 1000000, 10000000, 20000000);

CREATE TABLE IF NOT EXISTS aggregates_buffer AS aggregates ENGINE = Buffer(luna, aggregates, 16, 10, 100, 10000, 1000000, 10000000, 20000000);
CREATE TABLE IF NOT EXISTS distributions_buffer AS distributions ENGINE = Buffer(luna, distributions, 16, 10, 100, 10000, 1000000, 10000000, 20000000);
CREATE TABLE IF NOT EXISTS histograms_buffer AS histograms ENGINE = Buffer(luna, histograms, 16, 10, 100, 10000, 1000000, 10000000, 20000000);

'''
