from common.models import DataMeta, JobMeta
from common.views import apply_filters
from common.util import ClickhouseClient, escape_string, log_time_decorator, log_time_context
from django.http import HttpResponse, HttpResponseBadRequest
from django.db.models import Case, When
import json
import sys
import warnings

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

LAYOUT_TEMPLATE = [
    {
        'label': 'Test ID',
        'type': 'IDLink',
        'dataSource': '_id',
        'isMutable': False,
    }, {
        'label': 'Name',
        'type': 'String',
        'dataSource': 'name',
        'isMutable': True,
    }, {
        'label': 'Author',
        'type': 'Staff',
        'dataSource': 'person',
        'isMutable': True,
    }, {
        'label': 'Start',
        'type': 'DateTime',
        'dataSource': '_test_start',
        'isMutable': False,
    }, {
        'label': 'Duration',
        'type': 'Duration',
        'dataSource': '_duration',
        'isMutable': False,
    }, {
        'label': 'Status',
        'type': 'Status',
        'dataSource': {
            'status': '_status',
            'progress': 'progress',
        },
        'isMutable': False,
    }, {
        'label': 'Task',
        'type': 'trackerLink',
        'dataSource': 'task',
        'isMutable': True,
    }, {
        'label': 'Lunapark',
        'type': 'Link',
        'dataSource': 'lunapark_link',
        'isMutable': False,
    },
    {
        'label': 'Autostop RPS',
        'type': 'String',
        'dataSource': 'autostop_rps',
        'isMutable': False,
    },
    {
        'label': 'Autostop reason',
        'type': 'String',
        'dataSource': 'autostop_reason',
        'isMutable': False,
    }, {
        'label': 'App',
        'type': 'String',
        'dataSource': 'app',
        'isMutable': True,
    }
]


def job_list_layout(request):
    """
    Пока возвращает просто захардкоженный LAYOUT_TEMPLATE.
    :param request: django HTTP request
    :return:
    """
    return HttpResponse(
        json.dumps(LAYOUT_TEMPLATE), content_type='application/json; charset=utf-8'
    )


@log_time_decorator
def job_list(request):
    """
    параметры в запросе
    - filter - набор фильтров, например filter=person:noob|direvius то есть person = или noob, или direvius
    - fields - набор требуемых полей
    - order - по какому полю или метаинформации сортировать. используем джанговый синтаксис для направления:
        id = asc, -id = desc
    - after - id последней стрельбы, которая уже есть на фронте
    - count - количество запрашиваемых стрельб

    :param request: django HTTP request
    :return: django HTTP response 200 со списком информации по стрельбам согласно запрошенным фильтрам и пр. Или 400.
    """
    filters = [escape_string(f) for f in request.GET.getlist('filter')]
    fields = [escape_string(f) for f in request.GET.getlist('field')]
    count = request.GET.get('count', 20)
    order = escape_string(request.GET.get('order', '-id'))
    after = request.GET.get('after', 0)

    order_param = order.lstrip('-')
    order_desc = order.startswith('-')

    # тут вместо дефолтного лэйаута может быть пользовательский
    if not fields:
        fields = set()
        for field in LAYOUT_TEMPLATE:
            if isinstance(field.get('dataSource'), str):
                fields.add(field.get('dataSource'))
            elif isinstance(field.get('dataSource'), dict):
                fields.update(list(field.get('dataSource').values()))

    if not fields:
        return HttpResponseBadRequest('Nothing to return, no "field" specified.')

    try:
        after = int(after)
        count = int(count)
    except ValueError:
        return HttpResponseBadRequest('"count" and "after" must be digital')

    filters = {f.split(':', 1)[0]: f.split(':', 1)[-1] for f in filters}

    # отделяем фильтры свойств джобы и фильтры по метаинформации
    job_attrs = ('status', 'id', 'test_start', '_status', '_id', '_test_start')
    # значения становятся списками, чтобы поддержать логику "ИЛИ" внутри каждого фильтра
    job_filters = {k.lstrip('_'): v.split('|') for k, v in filters.items() if k in job_attrs}
    meta_filters = {k: v.split('|') for k, v in filters.items() if k not in job_attrs}
    jobs_queryset = apply_filters(job_filters, meta_filters)

    if order_param in job_attrs:
        jobs_queryset = jobs_queryset.order_by('%s%s' % ('-' if order_desc else '', order_param.lstrip('_')))
    else:
        with log_time_context(tag='META INFO SORTING', longer=1):
            # FIXME: медленно
            # сортировка по метаинформации
            sorted_meta = JobMeta.objects.filter(job__in=jobs_queryset, key=order_param)\
                .order_by('-value' if order_desc else 'value', '-job_id')
            job_ids = [jm.job.id for jm in sorted_meta]
            if job_ids:
                # сохраняем порядок айдишников джоб согласно отфильтрованной метаинформации
                jobs_queryset = jobs_queryset.order_by(Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(job_ids)]),
                                                       '-id')
            else:
                jobs_queryset = jobs_queryset.order_by('-id')

    no_more = jobs_queryset.count() <= count

    # нельзя сразу отфильтровать по after, потому что сортировка может быть по любому полю и в любом направлении
    if after:
        ids = [job.id for job in jobs_queryset]
        try:
            after_index = ids.index(after) + 1
        except ValueError:
            # если нет такого id среди отфильтрованных стрельб,
            # например, если стрельбу успели удалить, или с фронта пришел кривой after,
            # то ищем ближайший id.
            # FIXME: если ближайшим окажется следующий, то одна джоба будет пропущена
            after_index = ids.index(min(ids, key=lambda i: abs(after-i))) + 1
        no_more = after_index + count >= len(jobs_queryset)
        jobs_queryset = jobs_queryset[after_index:after_index+count]
    else:
        jobs_queryset = jobs_queryset[:count]

    # TODO: add preview
    # TODO: add common meta keys

    # раскладываем по полям из лэйаута

    jobs = []
    for job in jobs_queryset:
        jm = job.full_meta.copy()
        # заполняем алиасами поля типа author
        # jm['progress'] = int(jm.get('progress') or 100)
        jm['person'] = jm.get('author') or jm.get('person') or jm.get('operator')
        job_dict = {}
        for field in fields:
            job_dict[field] = jm.get(field)
        jobs.append(job_dict)
    return HttpResponse(
        json.dumps({'tests': jobs, 'no_more': no_more}), content_type='application/json; charset=utf-8'
    )


@log_time_decorator
def job_list_groups(request):
    """
    Возвращает список групп, на основании значений ключа, переданного в параметре groupby,
    среди стрельб, отфильтрованных переданными фильтрами
    :param request: django HTTP request
    - filter - набор фильтров, например filter=person:noob|direvius то есть person = или noob, или direvius
    - groupby - параметр - ключ метаинформации, значения которого и будут являться группами
    :return: django HTTP request со списком групп в формате json
    """
    filters = [escape_string(f) for f in request.GET.getlist('filter')]
    groupby = escape_string(request.GET.get('groupby'))
    if not groupby:
        return HttpResponseBadRequest('"groupby" is mandatory')

    filters = {f.split(':', 1)[0]: f.split(':', 1)[-1] for f in filters}

    # отделяем фильтры свойств джобы и фильтры по метаинформации
    jobAttrs = ('status', 'id', 'test_start')
    # значения становятся списками, чтобы поддержать логику "ИЛИ" внутри каждого фильтра
    job_filters = {k: v.split('|') for k, v in filters.items() if k in jobAttrs}
    meta_filters = {k: v.split('|') for k, v in filters.items() if k not in jobAttrs}
    jobs = apply_filters(job_filters, meta_filters)

    if groupby in jobAttrs:
        groups = [j.__dict__[groupby] for j in jobs.distinct(groupby)]
    else:
        groups = [jm.value for jm in JobMeta.objects.filter(job__in=jobs, key=groupby).distinct('value')]
    groups.sort()
    return HttpResponse(
        json.dumps(groups), content_type='application/json; charset=utf-8'
    )


def _preview(job):
    """

    :param job: job OBJECT
    :return:
    """
    current_data_obj = DataMeta.objects.filter(data__job__id=job.pk, key='name', value='current')[0].data
    # в кликхаусных массивах индексы начинаются с 1
    query = '''
        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 
            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}'
        group by intDiv(toUInt64(ts)+{test_start}+{offset}, 1000000)*1000000
    '''
    query_params = {
        'tag': current_data_obj.uniq_id,
        'offset': current_data_obj.offset,
        'test_start': job.test_start
    }
    ch_client = ClickhouseClient()
    data = ch_client.select_csv(query, query_params=query_params)
    return data


def job_meta_keys(request):
    filters = [escape_string(f) for f in request.GET.getlist('filter')]
    keys = [jm.key for jm in JobMeta.objects.distinct('key')]
    return HttpResponse(
        json.dumps(keys), content_type='application/json; charset=utf-8'
    )
