#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Created on Apr 22, 2015

@author: noob
"""

import datetime
import json
import logging
import re
import time

from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.db import connections
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect, HttpRequest
from django.shortcuts import render_to_response
from django.template import RequestContext
from django_yauth.decorators import yalogin_required

from api.views.jobclose import JobClose
from common.models import Job, Component, LoadScheme, DeletedJob, JobImbalance, Task
from common.util.clients import ClickhouseClient, StartrekClient, st_statuses_img_path, st_statuses, \
    link_and_create_task, TaskError
from common.util.decorators import cached
from common.views import get_common
from mobilecompare.models import CompareMobileJobsManager
from comparepage.models import CompareJobsManager
from mobilepage.models import MobileJob



@yalogin_required
def task_page(request, task):

    user = request.yauser
    common_data = get_common(request)
    if not re.match(r'[a-zA-Z]+-\d+', task):
        return render_to_response('error.html', {'common': common_data, 'error': 'no_task'},
                                  RequestContext(request))  # user
    task = task.upper()
    jobs = Job.objects.filter(task=task).filter(td__isnull=True).order_by('-n')
    fire_a = get_job_array(jobs, user.login)
    jobs2 = Job.objects.filter(task=task, td__isnull=False).order_by('-n')
    fire_a.extend(get_job_array(jobs2, user.login, limit=30))
    comp_a = []
    comps = Component.objects.filter(tag=task.split('-')[0])
    for c in comps:
        comp_h = {'id': c.n, 'name': c.name}
        comp_a.append(comp_h)
    try:
        from mobilepage.models import MobileJob
        mobile_is_present = bool(MobileJob.objects.filter(task=task))
    except:
        logging.exception('')
        mobile_is_present = False
    return render_to_response(
        'taskpage.html', {
            'task': task,
            'fire': fire_a,
            'common': common_data,
            'component': comp_a,
            'nav': [],
            'mobile_is_present': mobile_is_present,
            'pagename': 'taskpage',
        },
        RequestContext(request)
    )  # user


@yalogin_required
def render_task_info(request, task_id):
    st_client = StartrekClient()
    st_task = st_client.get_task(task_id)
    info = {
        'key': st_task.key.upper(),
        'name': st_task.summary,
        'jobs_count': Job.objects.filter(task=st_task.key.upper()).count(),
        'assignee': st_task.assignee.login if st_task.assignee else '',
        'createdAt': datetime.datetime(*(time.strptime(st_task.createdAt.split('.')[0],
                                                       '%Y-%m-%dT%H:%M:%S')[0:6])),
        'createdBy': st_task.createdBy.login,
        'status_img': st_statuses_img_path + st_statuses.get(st_task.status.key, st_statuses['']),
        'queue': st_task.queue.key,
        'queue_dsc': st_task.queue.description,
    }
    return render_to_response('info_block.html', {'task': info}, RequestContext(request))


@yalogin_required
def more_jobs(request, task):
    user = request.yauser
    limit = request.GET.get('limit', '30')
    before = request.GET.get('before')
    if limit.isdigit():
        limit = int(limit)
    else:
        limit = 30
    jobs = Job.objects.filter(task=task, td__isnull=False)
    if before:
        jobs = jobs.filter(n__lt=before)
    jobs = jobs.order_by('-n')[:limit]
    fire_a = get_job_array(jobs, user.login)
    return HttpResponse(json.dumps(fire_a), content_type='application/json')


def get_job_array(jobs, person, limit=None):
    # TODO: USE DIFFERENT ARRAYS FOR ONLINE AND OFFLINE NAVIGATION
    fire_a = []
    if limit:
        jobs = jobs[:int(limit)]
    for job in jobs:
        # print job.n
        fire_h = {
            'id': job.n,
            'author': job.person,
            'name': job.name.replace('<', '&lt;').replace('>', '&gt;'),
            'dsc': job.dsc.replace('<', '&lt;').replace('>', '&gt;') if job.dsc else '',
            'star': job.flag,
            'task': job.task,
        }
        dur = 0
        if job.td:  # offline
            job_trail = job.jobtrail_set.all()
            if job_trail:
                job_trail = job_trail[0]
        else:
            job_trail = 0

        if job.td:  # offline
            fire_h['onair'] = 0
            if job.fd:
                dur = job_trail.trail_stop - job_trail.trail_start \
                    if job_trail and job_trail.trail_start and job_trail.trail_stop else job.td - job.fd
        else:  # online
            fire_h['onair'] = 1
            if job.fd:
                dur = datetime.datetime.now() - job.fd
            now = datetime.datetime.now()
            already = now - job.fd
            loadscheme = job.loadscheme_set.all().order_by('-sec_to')
            if loadscheme.count():
                tmd = datetime.timedelta(seconds=loadscheme[0].sec_to)
                remain = tmd - already
                hours, remainder = divmod(remain.seconds + remain.days * 24 * 3600, 3600)
                minutes, seconds = divmod(remainder, 60)
                fire_h['onair_remain'] = str(remain.days) + ' дней ' if dur.days else ''
                fire_h['onair_remain'] += '%02.0f:%02.0f:%02.0f' % (hours, minutes, seconds)
            else:
                fire_h['onair_remain'] = 'неизвестно'
        if dur:
            # dur = re.split('\.\d+$', str(dur))
            hours, remainder = divmod(dur.seconds + dur.days * 24 * 3600, 3600)
            minutes, seconds = divmod(remainder, 60)
            fire_h['duration'] = str(dur.days) + ' дней ' if dur.days else ''
            fire_h['duration'] += '%02.0f:%02.0f:%02.0f' % (hours, minutes, seconds)
            # fire_h['duration'] = dur[0]
        else:
            fire_h['duration'] = 0
        if job.ammo_path:
            patrons = re.search('/[^/]+$', job.ammo_path)
            if patrons:
                patrons = patrons.group()
                fire_h['patrons'] = patrons[1:]
            else:
                fire_h['patrons'] = job.ammo_path
        fire_h['ver'] = job.ver
        if not fire_h['ver']:
            fire_h['ver'] = ''
        fire_h['date_start'] = job_trail.trail_start if job_trail and job_trail.trail_start else job.fd
        fire_h['date_start'] = fire_h['date_start'].strftime("%d/%m/%y %H:%M")
        tank = job.tank  # Server.objects.filter(n=job.tank)
        if tank:
            host = re.split('\.tanks\.yandex\.net', tank.host)  # tank[0].host
            fire_h['tank'] = host[0]
        target = job.srv  # Server.objects.filter(n=job.srv)
        if target:
            fire_h['target'] = target.host  # target[0].host
        fire_h['port'] = job.srv_port
        try:
            job_imb = JobImbalance.objects.filter(up=job.n)
            if job_imb:
                job_imb = job_imb[0]
                fire_h['imbalance_type'] = job_imb.hum_processed
                if job_imb.hum_processed:
                    if job_imb.hum_isimbalance:
                        fire_h['imbalance_rps'] = job_imb.hum_imbalance or job_imb.rob_imbalance
                    else:
                        fire_h['imbalance_rps'] = 0
                else:
                    fire_h['imbalance_rps'] = job_imb.rob_imbalance
        except:
            fire_h['imbalance_rps'] = 0
            fire_h['imbalance_type'] = 0
        fire_h['quit_status'] = job.quit_status
        if not fire_h['quit_status']:
            fire_h['quit_status'] = 0
        http = []
        if job_trail:
            fire_h['rps_from'] = job_trail.min_rps
            fire_h['rps_to'] = job_trail.max_rps
            http = re.split(',', job_trail.http) if job_trail.http else []
            fire_h['net'] = job_trail.net
        for h in http:
            fire_h['http_{}'.format(h)] = 1
        fire_h['component_id'] = job.component
        comp = Component.objects.filter(n=job.component)
        if comp:
            fire_h['component_name'] = comp[0].name
        manager = CompareJobsManager(person)
        fire_h['compare'] = int(bool(job.n in manager.get_jobs()))
        fire_a.append(fire_h)
    return fire_a


@yalogin_required
def mass_compare(request):
    user = request.yauser
    action = request.POST.get('action', '')
    ctype = request.POST.get('type', '')
    error = ''
    if action not in ('add', 'remove'):
        return HttpResponse(json.dumps({'status': 0, 'error': 'Inrecognized action {}'.format(action)}),
                            content_type='application/json')
    fire_ids = request.POST.getlist('fire_ids[]')
    if ctype == 'mobile':  # mobile
        jobs = MobileJob.objects.filter(n__in=fire_ids)
        if not jobs.count():
            return HttpResponse(json.dumps({'status': 0, 'error': 'There are no such jobs!'}),
                                content_type='application/json')
        manager = CompareMobileJobsManager(user.login)
        if action == 'remove':
            manager.remove_jobs([job.n for job in jobs])
        else:
            manager.add_jobs([job.n for job in jobs])
        to_dump = _return_to_dump(error)
        to_dump['count'] = manager.count
    else:
        jobs = Job.objects.filter(n__in=fire_ids)
        if not jobs.count():
            return HttpResponse(json.dumps({'status': 0, 'error': 'There are no such jobs!'}),
                                content_type='application/json')
        manager = CompareJobsManager(user.login)
        if action == 'remove':
            manager.remove_jobs([job.n for job in jobs])
        else:
            manager.add_jobs([job.n for job in jobs])
        to_dump = _return_to_dump(error)
        to_dump['count'] = manager.count
    return HttpResponse(json.dumps(to_dump), content_type='application/json')


@yalogin_required
def addtofav(request, task_jiratask):
    action = request.POST.get('action', '')
    job_type = request.POST.get('type', '')
    if task_jiratask.isdigit():  # from the online page! not a jiratask, but a job number!
        job_n = task_jiratask
    else:
        job_n = request.POST.get('job_id', 0)
    error = ''
    if action != 'add' and action != 'remove':
        return HttpResponse(json.dumps({'status': 0, 'error': 'Inrecognized action {}'.format(action)}),
                            content_type='application/json')
    elif action == 'add':
        action = True
    else:
        action = False
    if job_type == 'mobile':
        if not MobileJob.objects.filter(n=job_n).update(flag=action):
            return HttpResponse(json.dumps({'status': 0, 'error': 'There is no such job!'}),
                                content_type='application/json')
    else:
        if not Job.objects.filter(n=job_n).update(flag=action):
            return HttpResponse(json.dumps({'status': 0, 'error': 'There is no such job!'}),
                                content_type='application/json')
    to_dump = _return_to_dump(error)
    return HttpResponse(json.dumps(to_dump), content_type='application/json')


@yalogin_required
def mass_fav(request):
    error = ''
    fire_ids = request.POST.getlist('fire_ids[]')
    action = request.POST.get('action')
    if action != 'add' and action != 'remove':
        return HttpResponse(json.dumps({'status': 0, 'error': 'Inrecognized action {}'.format(action)}),
                            content_type='application/json')
    elif action == 'add':
        action = True
    else:
        action = False
    if not Job.objects.filter(n__in=fire_ids).update(flag=action):
        return HttpResponse(json.dumps({'status': 0, 'error': 'There are no such jobs!'}),
                            content_type='application/json')
    to_dump = _return_to_dump(error)
    return HttpResponse(json.dumps(to_dump), content_type='application/json')


def delete_job(job, user='lunapark', job_type=''):
    """

    :param job_type: can be mobile
    :param user: string
    :param job: Job OBJECT
    """
    try:
        if job_type == 'mobile':
            job.delete()
        else:
            if not job.td:  # close online job before deleting;
                request = HttpRequest()
                request.method = 'get'
                user_lunapark = User.objects.get(username=user)
                request.yauser = user_lunapark
                riper = JobClose()
                riper.get(request, job)

            d = DeletedJob(n=job.n,
                           task=job.task,
                           fd=job.fd,
                           td=job.td,
                           person=job.person,
                           name=job.name,
                           dsc=job.dsc,
                           tank=job.tank,
                           command_line=job.command_line,
                           ammo_path=job.ammo_path,
                           loop_cnt=job.loop_cnt,
                           quit_status=job.quit_status,
                           srv=job.srv,
                           srv_port=job.srv_port,
                           instances=job.instances,
                           flag=job.flag,
                           component=job.component,
                           ver=job.ver,
                           configinfo=job.configinfo,
                           finalized=job.finalized,
                           status=job.status)
            d.save()

            cursor = connections['default'].cursor()
            sql = '''
                    delete from job_monitoring_config where job_id=%(job)s;
                    delete from job_event where job_id=%(job)s;
                    delete from common_tankuseragent where job_id=%(job)s;
                    delete from regression_comment where job_id=%(job)s;
                    delete from mobile_data_key where job_id=%(job)s;
                    '''
            cursor.execute(sql, {'job': job.n})
            cursor.close()

            LoadScheme.objects.filter(up=job).delete()

            job.delete()

        return 1
    except:
        logging.exception('')
        return 0


@yalogin_required
def mass_delete(request):
    error = ''
    fire_ids = request.POST.getlist('fire_ids[]')
    job_type = request.POST.get('type', '')

    if job_type == 'mobile':
        jobs = MobileJob.objects.filter(n__in=fire_ids)
    else:
        jobs = Job.objects.filter(n__in=fire_ids)
    if not jobs.count():
        return HttpResponse(json.dumps({'status': 0, 'error': 'There are no such jobs!'}), content_type='application/json')
    for job in jobs:
        delete_job(job, user=request.yauser.login, job_type=job_type)
    to_dump = _return_to_dump(error)
    return HttpResponse(json.dumps(to_dump), content_type='application/json')


@yalogin_required
def deletefavfires(request):
    error = ''
    user = request.yauser
    manager = CompareJobsManager(user.login)
    manager.clear_jobs()
    to_dump = _return_to_dump(error)
    return HttpResponse(json.dumps(to_dump), content_type='application/json')


@yalogin_required
def mass_move(request):
    error = ''
    fire_ids = request.POST.getlist('fire_ids[]')
    jobs = Job.objects.filter(n__in=fire_ids)
    if not jobs.count():
        return HttpResponse(json.dumps({'status': 0, 'error': 'There are no such jobs! {}'.format(fire_ids)}),
                            content_type='application/json')
    task = request.POST.get('task')

    if not task:
        return HttpResponse(json.dumps({'status': -1, 'error': 'Task name not set!'}), content_type='application/json')
    try:
        new_task = link_and_create_task(request, task)
    except TaskError:
        return HttpResponse(json.dumps({'status': -1, 'error': 'There is no such task!'}),
                            content_type='application/json')
    jobs.update(task=new_task.key)
    to_dump = _return_to_dump(error)
    return HttpResponse(json.dumps(to_dump), content_type='application/json')


def _return_to_dump(error=''):
    to_dump = {}
    if error:
        to_dump['status'] = 0
    else:
        to_dump['status'] = 1
    to_dump['error'] = error
    return to_dump


@yalogin_required
def generate_preview(request):
    """

    :param request: HTTP Request
    """

    try:
        job_n = request.GET.get('job')
        assert job_n
        job_obj = Job.objects.get(n=job_n)
    except (AssertionError, ObjectDoesNotExist):
        return HttpResponseBadRequest('no such job {}'.format(job_n))

    cache_key = 'job_{}_quantiles_preview_dec'.format(job_obj.n)

    @cached(cache_key, bypass=bool(not job_obj.td))
    def preview():
        # Тип схемы нагрузки
        sql = '''select any(job_id)
                from loaddb.rt_microsecond_details_buffer
                where job_id={job}
                and job_date=toDate({job_date})
                and reqps!=0'''
        ch_client = ClickhouseClient()
        nonzero_reqps = ch_client.select(sql, query_params=job_obj.basic_query_params)
        if nonzero_reqps:
            scheme_type = 'reqps'
            scheme_color = '#800000'  # BF0129
        else:
            scheme_type = 'threads'
            scheme_color = '#ff00ff'
        try:
            trail_count = ch_client.select('''
                select toUInt32(count())
                from loaddb.rt_microsecond_details_buffer
                where job_id={job}
                and job_date=toDate({job_date})
                and tag = ''
                ''', query_params=job_obj.basic_query_params)[0][0]
        except IndexError:
            logging.warning('No data for job {} preview'.format(job_obj.n))
            return []
        # чем больше знаменатель, тем больше точек на графике (100 соответствует 0-199 точек)
        compress_ratio = trail_count // 50 or 1
        # Довольно грубая выборка.
        # Без усреднения и взвешивания.
        # Даже не гарантирует, что будет последний трейл.
        # Но для превью сойдет.

        sql = '''
            select mq50, mq75, mq80, mq85, mq90, mq95, mq98, mq99, mq100, toUInt32(scheme) from
            (
            select any({scheme}) as scheme,
            intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.rt_microsecond_details_buffer
            where job_id={job}
            and job_date=toDate({job_date})
            and tag = ''
            group by t
            order by t
            ) all left join
            (
            select
            round(max(q50), 3) as mq50,
            round(max(q75), 3) as mq75,
            round(max(q80), 3) as mq80,
            round(max(q85), 3) as mq85,
            round(max(q90), 3) as mq90,
            round(max(q95), 3) as mq95,
            round(max(q98), 3) as mq98,
            round(max(q99), 3) as mq99,
            round(max(q100), 3) as mq100,
            intDiv(toUInt32(time), {compress_ratio})*{compress_ratio} as t
            from loaddb.rt_quantiles_buffer
            where job_id={job}
            and job_date=toDate({job_date})
            and tag=''
            group by t
            order by t
            )
            using t'''
        query_params = job_obj.basic_query_params.copy()
        query_params.update({
            'compress_ratio': compress_ratio,
            'scheme': scheme_type,
        })
        trail_data = ch_client.select(sql, query_params=query_params)
        assert trail_data
        return [
               {'data': [v[-1] for v in trail_data] if nonzero_reqps else [v[-1] if v[-1] else None for v in
                                                                           trail_data],
                'color': scheme_color,
                'marker': {'enabled': False},
                'lineWidth': 2,
                'states': {'hover': {'enabled': False}},
                'yAxis': 1,
                },
               {'data': [v[0] for v in trail_data],
                'color': '#f85750',
                'marker': {'enabled': False},
                'lineWidth': 1,
                'type': 'spline',
                'states': {'hover': {'enabled': False}},
                },
               {'data': [v[1] for v in trail_data],
                'color': 'Coral',
                'marker': {'enabled': False},
                'lineWidth': 1,
                'type': 'spline',
                'states': {'hover': {'enabled': False}},
                },
               {'data': [v[2] for v in trail_data],
                'color': 'DarkOrange',
                'marker': {'enabled': False},
                'lineWidth': 1,
                'type': 'spline',
                'states': {'hover': {'enabled': False}},
                },
               {'data': [v[3] for v in trail_data],
                'color': 'Orange',
                'marker': {'enabled': False},
                'lineWidth': 1,
                'type': 'spline',
                'states': {'hover': {'enabled': False}},
                },
               {'data': [v[4] for v in trail_data],
                'color': 'gold',
                'marker': {'enabled': False},
                'lineWidth': 1,
                'type': 'spline',
                'states': {'hover': {'enabled': False}},
                },
               {'data': [v[5] for v in trail_data],
                'color': '#b8e13d',
                'marker': {'enabled': False},
                'lineWidth': 1,
                'type': 'spline',
                'states': {'hover': {'enabled': False}},
                },
               {'data': [v[6] for v in trail_data],
                'color': '#66af5f',
                'marker': {'enabled': False},
                'lineWidth': 1,
                'type': 'spline',
                'states': {'hover': {'enabled': False}},
                },
               {'data': [v[7] for v in trail_data],
                'color': '#7cb3f1',
                'marker': {'enabled': False},
                'lineWidth': 1,
                'type': 'spline',
                'states': {'hover': {'enabled': False}},
                },
               {'data': [v[8] for v in trail_data],
                'color': '#b2b8c8',
                'marker': {'enabled': False},
                'lineWidth': 1,
                'type': 'spline',
                'states': {'hover': {'enabled': False}},
                },
           ][::-1]

    try:
        data = preview()
    except AssertionError:
        logging.info('Failed to get trail data for preview for job %s', job_n)
        data = []
    except Exception:
        logging.error('Failed to get preview for job %s', job_n, exc_info=True)
        data = []

    return HttpResponse(json.dumps(data), content_type='application/json')
