from common.models import Data, Job, DataMeta
from common.util import log_time_decorator, ClickhouseClient, escape_string, log_time_context
from django.db.models import F
from django.http import HttpResponse, HttpResponseBadRequest
from django.core.exceptions import MultipleObjectsReturned
from job_list.views import LAYOUT_TEMPLATE as META_LAYOUT_TEMPLATE
from copy import deepcopy
from collections import defaultdict
import pandas as pd
import numpy as np
from io import BytesIO
import json
import logging
import sys
import warnings

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

CH_CLIENT = ClickhouseClient()

LAYOUT_TEMPLATE = {
    'meta': {},
    'sections': [
        {
            'widgets': []
        }
    ],
    'navigation': {
        'area': {
            'axis': 'left',
            'metrics': [],
        },
        'events': [],
        'line': {
            'left': [],
        },
    }
}

WIDGET_TEMPLATE = {
    'title': '',
    'views': [],
    'settings': {
        'summary': {
            'values': ['max', 'q99', 'q98', 'q95', 'q90', 'q85', 'q80', 'q75', 'q50', 'q25', 'q10', 'min', 'average'],
            'hidden_values': ['q99', 'q98', 'q90', 'q85', 'q80', 'q10']
        }
    },
}

PLOT_VIEW_TEMPLATE = {
    'type': 'plot',
    'area': {
        'axis': 'left',
        'metrics': [],
    },
    'axes': {
        'left': {
            'min': 0,
            'label': ''
        },
        'right': {},
        'x': {}
    },
    'events': [],
    'line': {
        'left': [],
        'right': [],
    },
    'selectors': [],
}

TABLE_VIEW_TEMPLATE = {
    'type': 'table',
    'metrics': [],
    'hidden_values': ['q99', 'q98', 'q90', 'q85', 'q80', 'q10'],
    'selectors': [],
}

QUANTILES_VIEW_TEMPLATE = {
    'type': 'quantiles_plot',
    'palette': 'quantiles',
    'area': {
        'axis': 'left',
        'stacked': False,
        'metrics': [],
    },
    'axes': {
        'left': {
            'min': 0,
            'label': ''
        },
        'right': {},
        'x': {}
    },
    'events': [],
    'line': {
        'left': [],
        'right': []
    },
    'selectors': ['max', 'q99', 'q98', 'q95', 'q90', 'q85', 'q80', 'q75', 'q50', 'q25', 'q10', 'min'],
}


@log_time_decorator
def compare_layout(request):
    """
    Формирует layout для страницы сравнения на основе метаинформации и параметров взапросе.
    Довольно жутковата, так как layout периодически перепридумывается.
        И нет четкого представления о том, как работать с разными срезами данных, группировкой по хостам,
        группам метрик, или иным признакам

    принимает в запросе:
    - job: номер стрельбы

    :param request: django HTTP request
    :return: django HTTP response
    """
    jobs = request.GET.getlist('job')
    if not all([j.isdigit for j in jobs]):
        return HttpResponseBadRequest('job ids must be digital')
    job_objects = Job.objects.filter(pk__in=jobs)
    LAYOUT = deepcopy(LAYOUT_TEMPLATE)

    for job in job_objects:
        LAYOUT['meta'][job.id] = deepcopy(META_LAYOUT_TEMPLATE)
        for m in LAYOUT['meta'][job.id]:
            m['locations'] = []
            if m.get('label') not in ('Status', 'Name'):
                m['locations'].append('info')
            m['isMutable'] = m.get('dataSource') not in job.immutable_meta

    LAYOUT['immutable_meta'] = {job_obj.id: job_obj.immutable_meta for job_obj in job_objects}

    with log_time_context(tag='COMPAREPAGE FULL META'):  # дебажный логгинг.
        datameta = DataMeta.objects.filter(data__in=Data.objects.filter(job__in=job_objects, type='metrics')) \
            .annotate(_uniq_id=F('data__uniq_id'), _job=F('data__job_id'))
        data = defaultdict(dict)
        for d in datameta:
            data[d.data_id][d.key] = d.value
            data[d.data_id]['_uniq_id'] = d._uniq_id
            data[d.data_id]['_job'] = d._job

    if data:
        data_groups, data_hosts = zip(*[(d.get('group', d.get('name', '')), d.get('host', '')) for d in data.values()])
    else:
        data_groups, data_hosts = [], []
    data_groups = sorted(set(data_groups))
    data_hosts = sorted(set(data_hosts))

    for group in data_groups:
        for host in data_hosts:
            group_metrics = [v for v in data.values() if
                             v.get('group', v.get('name', '')) == group and v.get('host', '') == host]
            if len(group_metrics) == 0: continue
            widget = deepcopy(WIDGET_TEMPLATE)
            widget['title'] = '{} @ {}'.format(group, host) if host else group
            selectors = sorted(set([m['name'] for m in group_metrics]))
            plot_view = deepcopy(PLOT_VIEW_TEMPLATE)
            plot_view['selectors'] = selectors
            table_view = deepcopy(TABLE_VIEW_TEMPLATE)
            table_view['selectors'] = selectors
            metrics = [{
                'job': m['_job'],
                'name': m['name'],
                'selector': m['name'],
                'tag': m['_uniq_id'],
            } for m in group_metrics]
            if len(metrics) > 5:
                plot_view['line']['left'] = metrics
            else:
                plot_view['area']['metrics'] = metrics
            table_view['metrics'] = metrics
            widget['views'].append(plot_view)
            widget['views'].append(table_view)
            if group == 'current':
                quantiles_view = deepcopy(QUANTILES_VIEW_TEMPLATE)
                quantiles_view['axes']['left']['label'] = 'mA'
                metrics = [
                    {
                        'job': m['_job'],
                        'name': 'current',
                        'tag': m['_uniq_id'],
                    } for m in group_metrics if m['name'] == 'current'
                ]
                if len(metrics) > 5:
                    quantiles_view['line']['left'] = metrics
                else:
                    quantiles_view['area']['metrics'] = metrics
                widget['views'].append(quantiles_view)
            LAYOUT['sections'][0]['widgets'].append(widget)

    for job in job_objects:
        try:
            navigation_metric = DataMeta.objects.get(data__job=job, key='name', value='current')
            LAYOUT['navigation']['line']['left'].append(
                {
                    'name': 'current',
                    'job': navigation_metric.data.job.pk,
                    'tag': navigation_metric.data.uniq_id,
                },
            )
        except MultipleObjectsReturned:
            navigation_metric = DataMeta.objects.filter(data__job=job, key='name', value='current')[0]
            LAYOUT['navigation']['line']['left'].append(
                {
                    'name': 'current',
                    'job': navigation_metric.data.job.pk,
                    'tag': navigation_metric.data.uniq_id,
                },
            )
        except DataMeta.DoesNotExist:
            navigation_metric = DataMeta.objects.filter(data__job=job).first()
            if navigation_metric:
                LAYOUT['navigation']['line']['left'].append(
                    {
                        'name': navigation_metric.data.meta.get('name'),
                        'job': navigation_metric.data.job.pk,
                        'tag': navigation_metric.data.uniq_id,
                    },
                )

    return HttpResponse(json.dumps(LAYOUT), content_type='application/json; charset=utf-8')


def get_metrics(request):
    """
    Ручка нового образца.
    Отдает сэмплированные данные для графиков метрик

    принимает в запросе:
    - tag: набор тэгов метрик
    - interval_offset: набор офсетов нужного интервала для каждого тэга
    - interval: длительность интервала одна на всех.

    соответствие тэга и interval_offset обеспечивается упорядоченностью списка
    например ?tag=t1&tag=t2&interval_offset=1&interval_offset=2
    при первой загрузке графиков офсеты не передаются и считаются равными нулю

    normalize: нужно ли нормализовать значения в рамках 0..1 (используется фронтом в навигационном виджете - зумилке)
    dots: сколько примерно точек хочет получить фронт

    :param request: django HTTP request
    :return: django HTTP response
    """
    global CH_CLIENT

    tags = [escape_string(tag) for tag in request.GET.getlist('tag')]  # SECURITAY!
    interval_offsets = request.GET.getlist('interval_offset')
    interval = request.GET.get('interval')
    normalize = bool(int(request.GET.get('normalize', 0)))
    dots = request.GET.get('dots', 1000)

    try:
        if interval:
            assert interval.isdigit(), 'interval must be digital'
        interval_offsets = list(map(int, interval_offsets))
        if not interval_offsets:
            interval_offsets = [0] * len(tags)
        interval_offsets = dict(zip(tags, interval_offsets))
        assert len(interval_offsets) == len(tags), 'every "tag" must have a corresponding "interval_offset"'
        assert str(dots).isdigit(), '"dots" must be digital'
        dots = int(dots)
        assert int(dots) > 0, '"dots" must be > 0'
    except AssertionError as aexc:
        return HttpResponseBadRequest(repr(aexc))
    except ValueError:
        return HttpResponseBadRequest('"interval_offset" must be digital')

    data_objects = {}
    # Лучше сразу проверить все переданные в запросе теги, чтобы не молотить данные в базе и сфэйлиться на последнем
    for tag in tags:
        try:
            data_obj = Data.objects.get(uniq_id=tag)
            if not data_obj.type == 'metrics':
                return HttpResponseBadRequest('This handler is for metrics only. For events use /get_events/?tag=...')
            data_objects[tag] = data_obj
        except Data.DoesNotExist:
            return HttpResponseBadRequest('metric for tag {} does not exist'.format(tag))

    frames = []
    for tag in tags:
        data_obj = data_objects[tag]
        data_offset = data_obj.offset
        interval_offset = interval_offsets[tag]
        # test_start = data_obj.job.test_start

        # отметка, что метрика должна обработаться как diff (например тики процессора в мобильных)
        data_is_diff = data_obj.meta.get('_apply', '') == 'diff'

        start = data_offset + interval_offset
        if interval is not None:
            interval = int(interval)
            end = start + interval
        else:
            end = None

        dots_available, compress_ratio = _dots_available_and_compress_ratio(tags[0], start, end, data_offset, dots)
        if not dots_available:
            return HttpResponse('timestamp', content_type='text/csv')

        sql = '''
            select any(tag), floor((toInt64(ts) - toInt64({offset})) / {compress_ratio})*{compress_ratio} as t, avg(value)
            from metrics 
            where tag = '{tag}' 
            and ts >= toInt64({start})
        '''
        if end is not None:
            sql += ' and ts <= toInt64({end})'
        sql += '''
            group by t, tag
            order by t, tag
        '''
        query_params = {
            'tag': tag,
            'start': start,
            'end': end,
            'offset': interval_offset,
            'compress_ratio': compress_ratio,
        }

        df = pd.read_csv(
            BytesIO(CH_CLIENT.select_csv(sql, query_params=query_params)),
            names=['tag', 'timestamp', 'value'])
        df = df.set_index(['timestamp', 'tag'])
        if df.empty:
            continue
        else:
            df = df.unstack().get('value')
        if normalize:
            df = df.apply(lambda x: x / x.max(), axis=0)
        # применяем diff к метрикам, у которых есть соответствующая отметка
        if data_is_diff:
            df = df.diff()
        frames.append(df)

    if frames:
        resulting_df = pd.concat(frames, axis=1)
    else:
        resulting_df = pd.DataFrame()

    # Добавляем тэг к названию колонки
    resulting_df.columns = ['{}_'.format(c) for c in resulting_df.columns]

    # Заполняем дыры в данных усредненными значениями
    resulting_df = resulting_df.applymap(lambda x: pd.to_numeric(x, errors='coerce'))
    resulting_df.interpolate(inplace=True, limit=2, limit_direction='both', limit_area='inside')

    # Заполняем пропуски на таймлайне пустыми значениями
    if resulting_df.index.size > 1:
        index_interval = int(pd.Series(resulting_df.index).diff().mode()[0])
        new_index = range(resulting_df.index[0], resulting_df.index[-1], index_interval)
        resulting_df = resulting_df.reindex(new_index, fill_value='', method='nearest', tolerance=index_interval)

    return HttpResponse(resulting_df.to_csv(), content_type='text/csv')


def get_aggregates(request):
    """
    Ручка нового образца.
    Отдает агрегированные посекундно данные для графиков (квантильный график)

    принимает в запросе:
    - tag: набор тэгов метрик
    - interval_offset: набор офсетов нужного интервала для каждого тэга
    - interval: длительность интервала одна на всех.

    соответствие тэга и interval_offset обеспечивается упорядоченностью списка
    например ?tag=t1&tag=t2&interval_offset=1&interval_offset=2
    при первой загрузке графиков офсеты не передаются и считаются равными нулю

    - fields: значения, которые следует отфильтровать и вернуть только их
    - cases: набор кейсов через "|", общий для всех тэгов

    :param request: django HTTP request
    :return: django HTTP response
    """
    # TODO: сейчас возвращает отдельные наборы данных для каждого тега.
    # TODO: возможно надо будет уметь суммировать данные по тегам и возвращать один общий набор данных

    tags = [escape_string(tag) for tag in request.GET.getlist('tag')]  # SECURITAY!
    interval_offsets = request.GET.getlist('interval_offset')
    interval = request.GET.get('interval')
    fields = request.GET.getlist('field')
    cases = request.GET.get('cases')

    if not fields:
        fields = ['max', 'q99', 'q98', 'q95', 'q90', 'q85', 'q80', 'q75', 'q50', 'q25', 'q10', 'min']

    try:
        if interval:
            assert interval.isdigit(), 'interval must be digital'
        interval_offsets = list(map(int, interval_offsets))
        if not interval_offsets:
            interval_offsets = [0] * len(tags)
        interval_offsets = dict(zip(tags, interval_offsets))
        assert len(interval_offsets) == len(tags), 'every "tag" must have a corresponding "interval_offset"'
    except AssertionError as aexc:
        return HttpResponseBadRequest(repr(aexc))
    except ValueError:
        return HttpResponseBadRequest('"interval_offset" must be digital')

    data_objects = {}
    # Лучше сразу проверить все переданные в запросе теги, чтобы не молотить данные в базе и сфэйлиться на последнем
    is_aggregated_data = None
    for tag in tags:
        try:
            data_obj = Data.objects.get(uniq_id=tag)
            if not data_obj.type == 'metrics':
                return HttpResponseBadRequest('This handler is for metrics only. For events use /get_events/?tag=...')
            if is_aggregated_data is None:
                is_aggregated_data = data_obj.has_distributions
            if is_aggregated_data != data_obj.has_distributions:
                return HttpResponseBadRequest('All metrics should all be either aggregated or raw')
            data_objects[tag] = data_obj
        except Data.DoesNotExist:
            return HttpResponseBadRequest('metric for tag {} does not exist'.format(tag))

    compress_ratio = 10 ** 6  # агрегировать будем посекундно
    if not is_aggregated_data:
        return _get_raw_aggregates(data_objects, tags, interval_offsets, interval, fields, compress_ratio)
    else:
        return _get_stored_aggregates(data_objects, tags, cases, interval_offsets, interval, fields,  compress_ratio)


def _get_raw_aggregates(data_objects, tags, interval_offsets, interval, fields, compress_ratio):
    global CH_CLIENT

    frames = []
    for tag in tags:
        data_obj = data_objects[tag]
        data_offset = data_obj.offset
        interval_offset = interval_offsets[tag]
        # test_start = data_obj.job.test_start

        # отметка, что метрика должна обработаться как diff (например тики процессора в мобильных)
        data_is_diff = data_obj.meta.get('_apply', '') == 'diff'

        start = data_offset + interval_offset
        if interval is not None:
            interval = int(interval)
            end = start + interval
        else:
            end = None

        sql = '''
                with quantilesExact(0.10, 0.25, 0.50, 0.75, 0.80, 0.85, 0.90, 0.95, 0.98, 0.99)({value}) as qq
                select 
                    intDiv(toInt64(ts) - {offset}, {compress_ratio})*{compress_ratio} as t, 
                    max({value}),
                    qq[10], qq[9], qq[8], qq[7], qq[6], qq[5], qq[4], qq[3], qq[2], qq[1], 
                    min({value})
                from metrics
                where tag = '{tag}'
                and ts >= toInt64({start})
            '''
        if end:
            sql += ' and ts <= toInt64({end})'
        sql += '''
                group by t
                order by t
            '''
        query_params = {
            'tag': tag,
            'start': start,
            'end': end,
            'compress_ratio': compress_ratio,
            'offset': interval_offset,
            'value': 'runningDifference(value)' if data_is_diff else 'value'
        }

        df = pd.read_csv(
            BytesIO(CH_CLIENT.select_csv(sql, query_params=query_params)),
            names=[
                'timestamp', 'max', 'q99', 'q98', 'q95', 'q90', 'q85', 'q80', 'q75', 'q50', 'q25', 'q10', 'min'
            ]
        )
        df = df.set_index(['timestamp'])

        # Отфильтровываем только нужные значения.
        if fields:
            df = df.filter(fields)

        # Добавляем тэг к названию колонки
        df.columns = ['{}_{}'.format(tag, c) for c in df.columns]

        frames.append(df)

    if frames:
        resulting_df = pd.concat(frames, axis=1)
    else:
        resulting_df = pd.DataFrame()

    # Заполняем пропуски на таймлайне пустыми значениями
    if resulting_df.index.size > 1:
        index_interval = int(pd.Series(resulting_df.index).diff().mode()[0])
        new_index = range(resulting_df.index[0], resulting_df.index[-1], index_interval)
        resulting_df = resulting_df.reindex(new_index, fill_value='', method='nearest', tolerance=index_interval)

    return HttpResponse(resulting_df.to_csv(), content_type='text/csv')


def _get_stored_aggregates(data_objects, tags, cases, interval_offsets, interval, fields, compress_ratio):
    global CH_CLIENT

    frames = []
    for tag in tags:
        data_obj = data_objects[tag]
        data_offset = data_obj.offset
        interval_offset = interval_offsets[tag]

        start = data_offset + interval_offset
        if interval is not None:
            interval = int(interval)
            end = start + interval
        else:
            end = None

        children_tags = subcase_tags(tag, cases)
        children_tag_set = ','.join(map(lambda child_tag: "'" + child_tag + "'", children_tags))

        sql = '''
            with intDiv(toInt64(ts) - {offset}, {compress_ratio})*{compress_ratio} as t
            select t, l, r, sum(cnt)
            from distributions
            where tag in ({tags})
            and ts >= toInt64({start})
        '''
        if end:
            sql += ' and ts <= toInt64({end})'
        sql += '''
            group by t, l, r 
            order by t, l, r
        '''
        query_params = {
            'tags': children_tag_set,
            'start': start,
            'end': end,
            'compress_ratio': compress_ratio,
            'offset': interval_offset
        }

        df = pd.read_csv(
            BytesIO(CH_CLIENT.select_csv(sql, query_params=query_params)),
            names=[
                'ts', 'l', 'r', 'cnt'
            ]
        )
        df['lr'] = df[['l', 'r']].apply(lambda x: (x[0], x[1]), axis=1)
        df.drop(['l', 'r'], axis=1, inplace=True)
        df = df.groupby(['ts']).agg({'cnt': lambda x: list(x.cumsum()), 'lr': lambda x: list(x)})

        result = pd.DataFrame(index=df.index)

        for field in fields:
            column = '{}_{}'.format(tag, field)
            field = {'max': 'q100', 'min': 'q0'}.get(field, field)
            quantile = float(field[1:]) / 100

            def get_quantile(series, quantile):
                cnt_cumsums = series.cnt
                intervals = series.lr
                if not cnt_cumsums:
                    return 0

                search_value = cnt_cumsums[-1] * quantile
                index = np.searchsorted(cnt_cumsums, search_value)
                if index == len(intervals):
                    index -= 1
                left, right = intervals[index]
                return (left + right) / 2

            result[column] = df.apply(get_quantile, axis=1, args=(quantile,))

        frames.append(result)

    resulting_df = pd.concat(frames, axis=1) if frames else pd.DataFrame()

    if resulting_df.index.size > 1:
        index_interval = int(pd.Series(resulting_df.index).diff().mode()[0])
        new_index = range(resulting_df.index[0], resulting_df.index[-1], index_interval)
        resulting_df = resulting_df.reindex(new_index, fill_value='', method='nearest', tolerance=index_interval)

    return HttpResponse(resulting_df.to_csv(), content_type='text/csv')


def get_summary(request):
    """
    Ручка нового образца.
    Отдает агрегированные данные для таблиц

    принимает в запросе:
    - tag: набор тэгов метрик
    - interval_offset: набор офсетов нужного интервала для каждого тэга
    - interval: длительность интервала одна на всех.
    - cases: список кейсов в формате case1|case2|case3...
    - is_raw: следует ли считать значение из таблицы
    соответствие тэга и interval_offset обеспечивается упорядоченностью списка
    например ?tag=t1&tag=t2&interval_offset=1&interval_offset=2
    при первой загрузке графиков офсеты не передаются и считаются равными нулю

    :param request: django HTTP request
    :return: django HTTP response
    """
    # TODO: сейчас возвращает отдельные наборы данных для каждого тега.
    # TODO: возможно надо будет уметь суммировать данные по тегам и возвращать один общий набор данных
    global CH_CLIENT

    tags = [escape_string(tag) for tag in request.GET.getlist('tag')]  # SECURITAY!
    interval_offsets = request.GET.getlist('interval_offset')
    interval = request.GET.get('interval')

    try:
        if interval:
            assert interval.isdigit(), 'interval must be digital'
        interval_offsets = list(map(int, interval_offsets))
        if not interval_offsets:
            interval_offsets = [0] * len(tags)
        interval_offsets = dict(zip(tags, interval_offsets))
        assert len(interval_offsets) == len(tags), 'every "tag" must have a corresponding "interval_offset"'
    except AssertionError as aexc:
        return HttpResponseBadRequest(repr(aexc))
    except ValueError:
        return HttpResponseBadRequest('"interval_offset" must be digital')

    data_objects = {}
    # Лучше сразу проверить все переданные в запросе теги, чтобы не молотить данные в базе и сфэйлиться на последнем
    for tag in tags:
        try:
            data_obj = Data.objects.get(uniq_id=tag)
            if not data_obj.type == 'metrics':
                return HttpResponseBadRequest('This handler is for metrics only. For events use /get_events/?tag=...')
            data_objects[tag] = data_obj
        except Data.DoesNotExist:
            return HttpResponseBadRequest('metric for tag %s does not exist' % tag)

    frames = []
    for tag in tags:
        data_obj = data_objects[tag]
        data_offset = data_obj.offset
        interval_offset = interval_offsets[tag]
        data_is_diff = data_obj.meta.get('_apply', '') == 'diff'
        # test_start = data_obj.job.test_start

        start = data_offset + interval_offset
        if interval is not None:
            interval = int(interval)
            end = start + interval
        else:
            end = None

        sql = '''
            select tag, 
            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}'
            and ts >= toInt64({start})
        '''
        if end:
            sql += ' and ts <= toInt64({end})'
        sql += '''
            group by tag
        '''
        query_params = {
            'tag': tag,
            'start': start,
            'end': end,
            'value': 'runningDifference(value)' if data_is_diff else 'value'
        }

        frames.append(pd.read_csv(
            BytesIO(CH_CLIENT.select_csv(sql, query_params=query_params)),
            names=['tag', 'max', 'q99', 'q98', 'q95', 'q90', 'q85', 'q80', 'q75', 'q50', 'q25', 'q10', 'min',
                   'average', 'stddev'])
        )
    if frames:
        resulting_df = pd.concat(frames, axis=0)
    else:
        resulting_df = pd.DataFrame()
    return HttpResponse(resulting_df.to_csv(index=False), content_type='text/csv')


def _dots_available_and_compress_ratio(tag, start, end, data_offset, dots_required):
    """
    Смотрит сколько значений есть у метрики и вычисляет compress_ratio для семплирования данных
    :param tag:
    :param start:
    :param end:
    :param data_offset:
    :param dots_required:
    :return: dots_available: int, compress_ratio: int
    """
    global CH_CLIENT

    sql = '''select count() 
            from metrics 
            where tag = '{tag}'
            '''
    if start is not None:
        sql += ' and ts >= toInt64({start}) - {offset}'
    if end is not None:
        sql += ' and ts <= toInt64({end}) - {offset}'
    query_params = {
        'tag': tag,
        'start': start,
        'end': end,
        'offset': data_offset,
    }
    dots_available = CH_CLIENT.select(sql, query_params=query_params)
    dots_available = int(dots_available[0][0]) if dots_available else 0

    if dots_available <= dots_required:
        return dots_available, 1
    if end is None:
        end = CH_CLIENT.select("select max(ts)+{} from metrics where tag = '{}'".format(data_offset, tag))
        end = int(end[0][0]) if end else 0
    # rate показывает сколько точек в секунде
    rate = dots_available // ((end - start) // 1000000 or 1) or 1

    return dots_available, dots_available * (10 ** 6 // rate) // dots_required
