# -*- coding: utf-8 -*-
"""
Created on Jun 5, 2014

@author: noob
"""
import io
import json
import logging
import re
import time
import uuid
from configparser import ParsingError as IniParsingError
from copy import deepcopy
from datetime import datetime

import requests
import yaml
from django.core.exceptions import ObjectDoesNotExist
from django.db import connections
from django.http import HttpResponseNotAllowed, HttpResponse, HttpResponseBadRequest
from django.shortcuts import render_to_response
from django.template import RequestContext
from django.views.decorators.csrf import csrf_exempt
from django_yauth.decorators import yalogin_required
from sandbox.common import rest
from sandbox.common.types.misc import BatchResultStatus

from common.models import Job, Ammo, JobMonitoringConfig, PandoraBinary
from common.util.clients import MDSClient
from common.util.meta import Json2Ini
from common.views import get_common
from settings import VALIDATOR_URL, SANDBOX_OAUTH_TOKEN
from .config_validator import ConfigValidator
from .default_config import section_defaults, rudimentary_params, yaml_defaults, yaml_rudimentary_params, \
    yaml_uploader_meta_rudimentary_params
from .tank_finder import TankFinder
from .vault_client import get_token
from ..forms import UploadFileForm


@yalogin_required
def render_firestarter(request):
    error = request.POST.get('error', '')
    error_link = request.POST.get('error_link', '')

    if request.POST.get('api_handle'):
        return [{'success': not bool(error), 'error': error}]

    user = request.yauser

    if request.method == 'POST':
        conf = request.POST.get('conf')
        try:
            assert conf
        except AssertionError:
            return HttpResponseBadRequest()

        conf = _choose_uris_or_ammo(json.loads(conf))
        monitoring_conf = request.POST.get('monitoring_conf', '')
    elif request.method == 'GET':
        conf = {
            'phantom': {
                'address': request.GET.get('address', ''),
                'load_profile': {'load_type': 'rps', 'schedule': ''},
                'ammofile': '',
                'uris': []
            },
            'uploader': {
                'enabled': True,
                'operator': str(user.login),
                'job_dsc': '',
                'job_name': '',
                'task': request.GET.get('task', ''),
                'package': 'yandextank.plugins.DataUploader',
                'ver': '',
            },
            'metaconf': {
                'package': 'yandextank.plugins.MetaConf',
            }
        }
        monitoring_conf = ''
        if request.GET.get('use_tank', ''):
            conf['uploader']['meta'] = {'use_tank': request.GET.get('use_tank', '')}
    else:
        return HttpResponseNotAllowed(['POST', 'GET'])

    return render_to_response(
        'firestarter.html',
        {
            'common': get_common(request),
            'conf': conf,
            'conf_yaml': yaml.safe_dump(conf, encoding='utf-8', allow_unicode=True, default_flow_style=False),
            'monitoring_conf': monitoring_conf.replace('\t', '').strip(),
            'error': error,
            'error_link': error_link,
            'recent_user_tasks': get_user_recent_tasks(user.login),
            'recent_user_targets': get_user_recent_targets(user.login),
            'recent_user_tanks': get_user_recent_tanks(user.login),
            'ammofiles': Ammo.objects.filter(author=user.login).exclude(hidden=1).order_by('-flag', 'dsc'),
            'ammo_upload_form': UploadFileForm(),
            'pandora_binaries': PandoraBinary.objects.filter(author=user.login).exclude(hidden=1).order_by('-flag', 'dsc'),
            'pandora_binary_upload_form': UploadFileForm(),
            'pagename': 'firestarter',
            'help_message': 'При повторе стрельбы выбирать танк необязательно',
        },
        RequestContext(request)
    )


def waiter_refresh(request):
    """
    called by status refreshing script from frontend
    :param request: HTTP request
    """
    location = request.GET.get('location', 'not_set')
    link = request.GET.get('link', '')
    if location == 'local':
        job_id = request.GET.get('id', '')
        resp = requests.get('/api/v2/jobs/{}/?'.format(job_id))
        status = resp.json()['status']
        tank_fqdn = request.GET.get('tank', '')
        tank_port = request.GET.get('port', '')
        link = '/{}'.format(job_id)
        tank_msg = ''
    else:
        sandbox_id = link.split('/')[-1]
        if sandbox_id:
            status, tank_msg, link = get_sandbox_status(sandbox_id)
            result = {'status': status,'tank_msg': tank_msg, 'link': link, 'tank': link}
        else:
            status, tank_msg, link = 'IN_PROGRESS', '', link
            result = {'status': status, 'tank_msg': tank_msg, 'link': link}
        resp = json.dumps(result)

    return HttpResponse(resp, content_type='text/json')


def get_tankapi_job_status(tank_fqdn, tank_port, tankapi_job_id):
    try:
        job_status = requests.get(
            'http://{}:{}/api/v1/tests/{}/status.json'.format(tank_fqdn, tank_port, tankapi_job_id)
        )
        job_status.raise_for_status()
        job_status = job_status.json()
        status = job_status.get('status_code', '')
        tank_msg = job_status.get('tank_msg', '')
        link = job_status.get('lunapark_url', '')
    except (ValueError, requests.exceptions.RequestException) as exc:
        status = 'ERROR'
        tank_msg = exc.args
        link = ''

    return status, tank_msg, link


def get_sandbox_status(sandbox_id):
    status_map = {
        'new': 'IN_PROGRESS',
        'validated': 'IN_PROGRESS',
        'failed to start': 'ERROR',
        'tank found': 'IN_PROGRESS',
        'initiated': 'IN_PROGRESS',
        'preparing': 'IN_PROGRESS',
        'test started': 'IN_PROGRESS',
        'running': 'IN_PROGRESS',
        'post_process': 'IN_PROGRESS',
        'finishing': 'IN_PROGRESS',
        'finished': 'IN_PROGRESS',
        'failed': 'ERROR',
        'success': 'FINISHED',
        None: 'ERROR'
    }
    sandbox_link = 'https://sandbox.yandex-team.ru/task/{}'.format(sandbox_id)

    status = 'ERROR'
    tank_msg = 'Невозможно получить статус задачи в сендбоксе',
    link = sandbox_link

    try:
        result = requests.get('https://sandbox.yandex-team.ru/api/v1.0/task/{}/output'.format(sandbox_id))
        context = requests.get('https://sandbox.yandex-team.ru/api/v1.0/task/{}/context'.format(sandbox_id))
        lunapark_id = context.json().get('lunapark_id', '')

        if isinstance(result.text, str):
            for item in result.json():
                if item.get('name') == 'lunapark_id' and item.get('value'):
                    link = 'https://lunapark.yandex-team.ru/{}'.format(item['value'])
                    status, tank_msg = 'FINISHED', ''
                    logging.info('Lunapark link: %s', link)
                elif lunapark_id:    
                    link = 'https://lunapark.yandex-team.ru/{}'.format(lunapark_id)
                    status, tank_msg = 'FINISHED', ''
                    logging.info('Lunapark link: %s', link)
                elif item.get('name') == 'result':
                    result_output = item.get('value')
                    if not result_output:
                        return 'IN_PROGRESS', '', link
                    result = json.loads(result_output)
                    status = json.dumps(result['status']).rstrip('"').lstrip('"')
                    status = status_map[status]
                    if result.get('errors'):
                        tank_msg = json.dumps(result.get('error_message'))
                        status = 'ERROR'
                        break
            return status, tank_msg, link
    except (ValueError, TypeError, requests.exceptions.RequestException):
        status = 'ERROR'
    return status, tank_msg, link


def get_project_ammofiles(request):
    task = request.GET.get('task', '')
    try:
        task = task.upper()
        if re.match(r'^[a-zA-Z]+-[0-9]+$', task.strip()):
            jobs_sql = '''
                select j.configinfo from job j
                where task like %(project)s order by j.n desc limit 1000;
            '''
            configammo = []
            cursor_j = connections['default'].cursor()
            try:
                cursor_j.execute(jobs_sql, {'project': '{}-%'.format(task.split('-')[0])})
                configinfos = cursor_j.fetchall()
                configammo = [re.findall('(\nammofile = .+\n?|\n *\t*ammofile: .+\n?)', configinfo[0]) for configinfo in configinfos if
                              configinfo[0]]
                configammo = sorted(set(ammo[0].replace(':', '=').split('=', 1)[1].strip() for ammo in configammo if ammo))
            except:
                logging.exception('')
            finally:
                cursor_j.close()
            dbammo = [a.path for a in Ammo.objects.exclude(private=1)]
            ammofiles = [(ammo, Ammo.objects.filter(path=ammo)[0].dsc) if ammo in dbammo else (ammo, ammo) for ammo
                         in configammo]
            return HttpResponse(json.dumps(ammofiles), content_type='application/json')
        else:
            return HttpResponse(json.dumps(None), content_type='application/json')
    except:
        logging.exception('Could not get configammo for task {} due to:'.format(task))
        return HttpResponse(json.dumps(None), content_type='application/json')


def get_project_pandora_binaries(request):
    task = request.GET.get('task', '')
    try:
        task = task.upper()
        if re.match(r'^[a-zA-Z]+-[0-9]+$', task.strip()):
            jobs_sql = '''
                    select j.configinfo from job j
                    where task like %(project)s order by j.n desc limit 1000;
                '''
            config_pandora_binaries = []
            cursor_j = connections['default'].cursor()
            try:
                cursor_j.execute(jobs_sql, {'project': '{}-%'.format(task.split('-')[0])})
                configinfos = cursor_j.fetchall()
                config_pandora_binaries = [re.findall('(\npandora_cmd = .+\n?|\n *\t*pandora_cmd: .+\n?)', configinfo[0]) for configinfo in
                              configinfos if
                              configinfo[0]]
                config_pandora_binaries = sorted(
                    set(pandora_binary[0].replace(':', '=').split('=', 1)[1].strip() for pandora_binary in config_pandora_binaries if pandora_binary))
            except:
                logging.exception('')
            finally:
                cursor_j.close()
            db_pandora_binaries = [pb.path for pb in PandoraBinary.objects.exclude(private=1)]
            pandora_binaries = [(pandora_binary, PandoraBinary.objects.filter(path=pandora_binary)[0].dsc) if pandora_binary in db_pandora_binaries else (pandora_binary, pandora_binary) for pandora_binary
                         in config_pandora_binaries]
            return HttpResponse(json.dumps(pandora_binaries), content_type='application/json')
        else:
            return HttpResponse(json.dumps(None), content_type='application/json')
    except:
        logging.exception('Could not get config_pandora_binaries for task {} due to:'.format(task))
        return HttpResponse(json.dumps(None), content_type='application/json')


def get_user_recent_tasks(login, limit=3):
    """
    :param login: user login
    :param limit: how many tasks to return
    """
    try:
        tasks = [j.task for j in Job.objects.filter(person=login).order_by('-n')[:50]]  # can't use set to keep order
        distinct_tasks = []
        for task in tasks:
            if task and task not in distinct_tasks:
                distinct_tasks.append(task)
    except:
        logging.exception('Could not get tasks for user {} due to:'.format(login))
        distinct_tasks = []
    return distinct_tasks[:limit]


def get_user_recent_targets(login, limit=2):
    """
    :param login: user login
    :param limit: how many targets to return
    """
    try:
        jobs = Job.objects.filter(person=login).order_by('-n')[:50]
        distinct_targets = []
        for j in jobs:
            if len(distinct_targets) >= limit:
                break
            target = j.config.get('phantom', {}).get('address', '')
            if target and target not in distinct_targets:
                distinct_targets.append(target)
    except:
        logging.exception('Could not get targets for user {} due to:'.format(login))
        distinct_targets = []
    return distinct_targets[:limit]


def get_user_recent_tanks(login, limit=2):
    """
    :param login: user login
    :param limit: how many tanks to return
    """
    try:
        jobs = Job.objects.filter(person=login).order_by('-n')[:50]
        distinct_tanks = []
        for j in jobs:
            if len(distinct_tanks) >= limit:
                break
            parsed_config = j.config
            tank = parsed_config.get('uploader', {}).get('meta', {}).get('use_tank', '') \
                or parsed_config.get('meta', {}).get('use_tank', '')
            if tank and tank not in distinct_tanks:
                distinct_tanks.append(tank)
    except Exception:
        logging.exception('Could not get tasks for user {} due to:'.format(login))
        distinct_tanks = []
    return distinct_tanks[:limit]


def get_start_sandbox_params(conf, user):
    auth_token = SANDBOX_OAUTH_TOKEN
    owner = 'LOAD-ROBOT'
    try:
        config = yaml.safe_load(conf)
        if config.get('metaconf', {}).get('firestarter', {}).get('tank', '') == 'sandbox':
            owner = config.get('metaconf').get('firestarter').get('owner', '')
            secret = config.get('metaconf').get('firestarter').get('secret', '')
            key = config.get('metaconf').get('firestarter').get('key', '') or 'sandbox_oauth_token'
            auth_token = get_token(secret=secret, key=key, user=user)
    except Exception:
        logging.error('Failed to get auth parameters for sandbox.', exc_info=True)
    return owner, auth_token


@yalogin_required
@csrf_exempt
def start_sandbox_task(request):

    if request.method != 'POST':
        return HttpResponseNotAllowed(['POST'])

    conf = request.POST['conf']
    monitoring_conf = request.POST.get('monitoring_conf', '')
    owner, auth_token = get_start_sandbox_params(conf, request.yauser.login)

    if owner and auth_token:
        try:
            sandbox_client = rest.Client(auth=auth_token)
            link = ''
            new_task = sandbox_client.task(
                type='FIRESTARTER', owner=owner,
                description='Created from firestarter web-interface',
                custom_fields=[
                    {'name': 'dry_run', 'value': False},
                    {'name': 'tank_config', 'value': conf},
                    {'name': 'monitoring_config', 'value': monitoring_conf}
                ]
            )
            sandbox_id = new_task['id']
            response = sandbox_client.batch.tasks.start.update([sandbox_id])[0]

            if response['status'] != BatchResultStatus.SUCCESS:
                logging.warning('Problem with starting sandbox task: %s', response)
                raise Exception(response)
            sandbox_id = new_task['id']

            if not sandbox_id:
                raise Exception('Sandbox did not return task id, ooops')
            link = 'https://sandbox.yandex-team.ru/task/{}'.format(sandbox_id)
            logging.info('Firestarter task started in sandbox: %s', link)

        except Exception:
            logging.error('Something strange happened with task status in sandbox.', exc_info=True)
            juggler_event('CRIT', 'Failed to start Firestarter task')
            return HttpResponse(
                json.dumps({'success': False,
                            'error': 'Problems with starting task in sandbox.',
                            'error_link': link
                            }),
                content_type='text/json'
            )
    else:
        juggler_event('CRIT', 'Failed to get auth params for sandbox for owner: {owner}'.format(owner=owner))
        logging.error('Failed to start task. Wrong auth attrs: owner, secret or token.')
        return HttpResponse(
                json.dumps({'success': False,
                            'error': 'Failed to run task in the {owner} quota. The owner or secret is not specified, or the {user} or the robot-lunapark does not have permissions to read the secret.'.format(owner=owner, user=request.yauser.login),
                            'error_link': ''
                            }),
                content_type='text/json'
            )

    logging.info('Check sandbox task {} status...'.format(sandbox_id))
    status, tank_msg, link = get_sandbox_status(sandbox_id)
    if status == 'ERROR':
        juggler_event('CRIT', 'Error during firestarter execution')
        result = json.dumps({'success': False, 'error': tank_msg, 'error_link': link})
    else:
        juggler_event('OK', 'Shooting successfully launched')
        result = json.dumps({
            'success': True,
            'status': status,
            'tank_msg': tank_msg,
            'link': link,
        })

    return HttpResponse(result, content_type='text/json')


def juggler_event(status, description):
    try:
        requests.post('http://localhost:31579/events', json={
            'source': 'lunapark',
            'events': [{
                'host': 'lunapark.yandex-team.ru_firestarter',
                'service': 'firestarter_sandbox',
                'instance': '',
                'status': status,
                'description': description,
                'open_time': time.time()
            }]
        })
    except:
        logging.error('Juggler notification error', exc_info=True)


@yalogin_required
@csrf_exempt
def start(request):
    """
    OMG
    """
    if request.method != 'POST':
        return HttpResponseNotAllowed(['POST'])
    user = request.yauser

    conf = request.POST['conf']
    monitoring_conf = request.POST.get('monitoring_conf', '')
    try:
        conf = yaml.safe_load(conf.replace('!!python/unicode ', ''))
    except yaml.parser.ParserError:
        error = 'Ошибка в формате конфига. Мы перешли на использования yaml:'
        return HttpResponse(
            json.dumps({'success': False,
                        'error': error,
                        'error_link': 'https://clubs.at.yandex-team.ru/tanks/1006'
                        }),
            content_type='text/json'
        )
    conf = _choose_uris_or_ammo(conf)
    # Подставляем текущего юзера как автора стрельбы
    conf['uploader'] = conf.get('uploader', {})
    conf['uploader']['operator'] = conf.get('uploader', {}).get('operator', str(user.login))

    # =======================================================================
    # CONFIG VALIDATION
    # =======================================================================
    task_validation = ConfigValidator(conf)._check_task()
    if not task_validation['success']:
        return HttpResponse(
            json.dumps(task_validation),
            content_type='text/json'
        )
    validation = _validate_config(yaml.safe_dump(conf, encoding='utf-8', allow_unicode=True), 'yaml')

    if not validation['errors']:
        if 'meta' not in conf['uploader']:
            conf['uploader']['meta'] = {}
        conf['uploader']['meta']['launched_from'] = 'firestarter'
    else:
        return HttpResponse(
            json.dumps({
                'success': False,
                'error': 'Ошибка в конфиге: {} '
                         .format(yaml.safe_dump(validation['errors'], encoding='utf-8', allow_unicode=True))
            }),
            content_type='text/json'
        )

    # =======================================================================
    # CHECKING TARGET
    # =======================================================================
    try:
        target = conf.get('phantom', {}).get('address', '') \
                 or conf.get('bfg', {}).get('address', '')
        assert target
    except AssertionError:
        return HttpResponse(json.dumps({'error': 'Не указан адрес мишени в секции phantom или bfg',
                                        'success': False}), content_type='text/json')

    # =======================================================================
    # FINDING TANK
    # =======================================================================
    # Как только не пишут названия опций. добавил алиасов.
    use_tank = conf['uploader']['meta'].get('use_tank') \
        or conf['uploader']['meta'].get('use-tank') \
        or conf['uploader']['meta'].get('usetank')
    use_tank_port = conf['uploader']['meta'].get('use_tank_port') \
        or conf['uploader']['meta'].get('use_port') \
        or conf['uploader']['meta'].get('use-port') \
        or conf['uploader']['meta'].get('useport') \
        or conf['uploader']['meta'].get('use-tank-port') \
        or conf['uploader']['meta'].get('usetankport') \
        or None
    if isinstance(use_tank, bytes):
        use_tank = use_tank.decode('utf-8')
    tank_finder = TankFinder(target, use_tank=use_tank, use_tank_port=use_tank_port)
    tank = tank_finder.tank()

    # ===================================================================
    # TROUBLES FINDING TANK
    # ===================================================================
    if not tank['success']:
        logging.error(tank)
        error = tank.get('error', 'Ошибка')
        return HttpResponse(json.dumps({'success': False,
                                        'error': error,
                                        'error_link': tank.get('error_link', '')
                                        }), content_type='text/json')

    # =======================================================================
    # STARTING
    # =======================================================================
    now = time.time()

    if 'monitoring' in list(conf.keys()) and 'telegraf' not in list(conf.keys()) and isinstance(conf['monitoring'], dict):
        conf['telegraf'] = deepcopy(conf['monitoring'])
        conf.__delitem__('monitoring')

    monitoring_section = conf.get('telegraf', {})

    if monitoring_section.get('config_contents'):
        error = 'Конфиг мониторинга (config_contents) должен быть в отдельном редакторе'
        return HttpResponse(json.dumps({'success': False,
                                        'error': error,
                                        }), content_type='text/json')

    conf_file = io.StringIO()
    mconf_file = io.StringIO()
    files = {}
    try:
        if monitoring_conf.strip():
            mconf_file_path = 'firestarter_mconf_{}_{}.xml'.format(user.login, now)
            monitoring_section['config'] = mconf_file_path
            mconf_file.write(str(monitoring_conf))
            mconf_file.seek(0)
            files[mconf_file_path] = mconf_file
        conf_file.write(yaml.safe_dump(conf, encoding='utf-8', allow_unicode=True).decode('utf-8') + '\n')
        conf_file.seek(0)
        files['load.conf']= conf_file
        resp = requests.post('http://{}:{}/api/v1/tests/start.json'.format(tank['fqdn'], tank['port']), files=files)
    finally:
        conf_file.close()
        mconf_file.close()

    # ===================================================================
    # WORK ON TANKAPI RESPONSE
    # ===================================================================
    if resp.status_code == 200 and resp.json()['success']:
        tankapi_job_id = resp.json().get('id', 0)
        if request.POST.get('api_handle'):
            return HttpResponse(json.dumps({'success': True, 'error': ''}),
                                content_type='text/json')  # можно заекстендить данными из ответа танкапи.
        else:
            status, tank_msg, link = get_tankapi_job_status(tank['fqdn'], tank['port'], tankapi_job_id)
            return HttpResponse(json.dumps({'success': True,
                                            'tank': tank['fqdn'],
                                            'port': tank['port'],
                                            'id': tankapi_job_id,
                                            'status': status,
                                            'tank_msg': tank_msg,
                                            'link': link,
                                            # 'location': 'tank',
                                            }), content_type='text/json')
    elif resp.status_code == 200:
        error = '{} {}'.format(tank['fqdn'], resp.json()['error'])
        return HttpResponse(json.dumps({'success': False,
                                        'error': error,
                                        }), content_type='text/json')
    else:
        error = 'Танкапи {} ответило {}'.format(tank['fqdn'], resp.status_code)
        return HttpResponse(json.dumps({'success': False,
                                        'error': error,
                                        }), content_type='text/json')


@yalogin_required
@csrf_exempt
def upload_ammo(request):
    form = UploadFileForm(request.POST, request.FILES)
    path = ''
    dsc = ''
    if form.is_valid():
        try:
            user = request.yauser
            now = datetime.now()
            dsc = request.POST.get('title') or request.FILES['file']._name
            uniq = uuid.uuid4().hex
            size = request.FILES['file']._size
            client = MDSClient()
            resp = client.post(
                uniq,
                request.FILES['file'].file,
                size,
            )
            error = resp['error']
            assert resp['success']
            path = resp['url']
            Ammo.objects.create(
                path=path,
                author=user.login,
                created_at=now,
                last_used=now,
                dsc=dsc,
                size=int(size),
                private=1,
            )
        except AssertionError:
            logging.exception('Could not upload ammo_file due to:')
            error = 'Не удалось залить патроны {}'.format(resp['error'])
        except:
            logging.exception('Could not upload ammo_file due to:')
            error = 'Не удалось залить патроны :('
    else:
        error = 'Файл-то не выбран'

    return HttpResponse(json.dumps({'success': bool(not error),
                                    'error': error,
                                    'dsc': dsc,
                                    'path': path
                                    }), content_type='application/json')


@yalogin_required
@csrf_exempt
def upload_pandora_binary(request):
    form = UploadFileForm(request.POST, request.FILES)
    path = ''
    dsc = ''
    if form.is_valid():
        try:
            user = request.yauser
            now = datetime.now()
            dsc = request.POST.get('title') or request.FILES['file']._name
            uniq = uuid.uuid4().hex
            size = request.FILES['file']._size
            client = MDSClient()
            resp = client.post(
                uniq,
                request.FILES['file'].file,
                size,
            )
            error = resp['error']
            assert resp['success']
            path = resp['url']
            PandoraBinary.objects.create(
                path=path,
                author=user.login,
                created_at=now,
                last_used=now,
                dsc=dsc,
                size=int(size),
                private=1,
            )
        except AssertionError:
            logging.exception('Could not upload pandora_binary_file due to:')
            error = 'Не удалось залить бинарь {}'.format(resp['error'])
        except:
            logging.exception('Could not upload pandora_binary_file due to:')
            error = 'Не удалось залить бинарь :('
    else:
        error = 'Файл-то не выбран'

    return HttpResponse(json.dumps({'success': bool(not error),
                                    'error': error,
                                    'dsc': dsc,
                                    'path': path
                                    }), content_type='application/json')


def _choose_uris_or_ammo(conf):
    """
    Танк ругается если указаны оба параметра - патроны и ури - или не указан ни один.
    Юзеры в firestatrer часто на это натыкались. сделал ежупу, которая выбирает патроны или ури для фантома
    Если указаны патроны, то ури игнорируются, и подставляется uri=/, если ни то ни другое не указано.
    """
    if 'bfg' not in conf and 'phantom' in conf:

        ammofile = conf['phantom'].get('ammofile', '')
        uris = conf['phantom'].get('uris', [])

        if not any([ammofile, uris]):
            conf['phantom']['uris'] = ['/']
        elif ammofile:
            conf['phantom']['uris'] = []
        elif uris:
            conf['phantom']['ammofile'] = ''

    return conf


@yalogin_required
def repeat(request, job):
    """
    severely modifies configinfo
    receives GET request and modifies it into POST for firestarter renderer func
    :param request: HTTP request
    :param job: Job NUMBER
    """
    user = request.yauser
    error = ''
    try:
        job_obj = Job.objects.get(n=job)
    except (ObjectDoesNotExist, ValueError):
        common = get_common(request)
        return render_to_response('error.html', {'common': common, 'error': 'no_job'},
                                  RequestContext(request))
    conf = {}
    monitoring_conf = ''
    try:
        conf, monitoring_conf = _prepare_config(job_obj, user.login)
        validation = _validate_config(conf, job_obj.config_fmt)
        # В старых инишках слишком много шлака, не будем пугать пользователей, будем показывать ошибки только для ямля.
        if validation['errors'] and job_obj.config_fmt == 'yaml':
            error = 'Ошибка в конфиге: {} '\
                    .format(yaml.safe_dump(validation['errors'], encoding='utf-8', allow_unicode=True))
        # Потенциально опасное место, так как стрипаем конфиг после валидации, ибо валидатор засирает конфиг дефолтами.

        # Валидация нужна для правильного конвертирования ini конфига в yaml актуальным конвертером
        # и отображения его в редакторе

        # Но в функции start все равно будет еще одна валидация, которая и будет отправлять ошибки во фронт
        # она будет необходима, так как юзер мог изменить конфиг в редакторе на фронте.
        conf = _strip_yaml_config(validation['config'])

    except (IniParsingError, yaml.parser.ParserError) as pexc:
        logging.exception('')
        error = 'Ошибка в конфиге {}'.format(repr(pexc))

    request.method = 'POST'
    params = request.POST.copy()
    params.__setitem__('conf', json.dumps(conf))
    params.__setitem__('monitoring_conf', monitoring_conf)
    params.__setitem__('repeat', True)
    params.__setitem__('error', error)
    request.POST = params

    return render_firestarter(request)


def _extract_monitoring_config(conf):
    """
    Used for repeating jobs only;
    :param conf: dict
    :return: conf and monitoring_conf
    """
    # Мерджим секции для разных плагинов.
    # секция телеграфа в приоритете, но содержимое конфига выбирается длиннейшее из двух
    # передаем содержимое конфига только если оно есть, и опция config не равна 'none'
    telegraf_section = conf.get('telegraf', {})
    old_monitoring_section = conf.get('monitoring', {})
    monitoring_section = deepcopy(old_monitoring_section)

    telegraf_config_contents = telegraf_section.get('config_contents', '') \
        or telegraf_section.get('meta', {}).get('config_contents', '')
    monitoring_config_contents = old_monitoring_section.get('config_contents', '')

    if len(monitoring_config_contents) > len(telegraf_config_contents):
        longest_monitoring_config_contents = monitoring_config_contents
    else:
        longest_monitoring_config_contents = telegraf_config_contents

    monitoring_section.update(telegraf_section)

    if 'config' in monitoring_section:
        monitoring_section.__delitem__('config')
    if 'config_contents' in monitoring_section:
        monitoring_section.__delitem__('config_contents')

    conf['telegraf'] = monitoring_section
    if 'monitoring' in conf:
        conf.__delitem__('monitoring')

    return conf, longest_monitoring_config_contents


def _prepare_config(job_obj, user_login):
    """
    Prepares job configinfo for repeating
    :param job_obj: Job OBJECT
    :return dumped job config (ini or yaml) and monitoring_config (xml) OMG..
    """
    jmc = JobMonitoringConfig.objects.filter(job=job_obj)
    if jmc.count():
        parsed_config, monitoring_config = deepcopy(job_obj.config), jmc[0].contents
    else:
        parsed_config, monitoring_config = _extract_monitoring_config(deepcopy(job_obj.config))

    if job_obj.config_fmt == 'ini':
        if 'meta' not in parsed_config:
            parsed_config['meta'] = {}
        parsed_config['meta']['task'] = job_obj.task
        parsed_config = _strip_ini_config(parsed_config)
        parsed_config['meta']['operator'] = user_login
        return Json2Ini(parsed_config).convert(), monitoring_config
    elif job_obj.config_fmt == 'yaml':
        # Несмотря на то, что при репите все равно все прогоняется через конвертер,
        # надо привести конфиг в божеский вид перед валидацией, иначе пользователя могу запутать ошибки валидации опций,
        # которые он даже не видит.
        conf_yaml = _strip_yaml_config(parsed_config)
        conf_yaml['uploader']['operator'] = user_login
        return yaml.safe_dump(conf_yaml, encoding='utf-8', allow_unicode=True), monitoring_config


def _strip_yaml_config(parsed_config):
    """
    Для чистоты конфига и уменьшения его длины в эдиторе firestarter
    :param parsed_config:
    :return:
    """
    clean_conf = {}

    # FIXME: иногда валидаторный коневертер из ини в ямль не добавляет enabled для аплоадера. убрать, когда починим
    # проверить можно на такой инишке https://lunapark.yandex-team.ru/api/job/1847657/configinfo.txt
    if 'uploader' in parsed_config:
        parsed_config['uploader']['enabled'] = True

    # Add a metaconf section
    if 'metaconf' in parsed_config:
        parsed_config['metaconf']['enabled'] = True

    # переносим в "чистый" конфиг все плагины которые в конфиге или дефолтах числятся как enabled=true
    # А так же убираем пустые опции
    for key in parsed_config.keys():
        if parsed_config[key].get('enabled') or yaml_defaults.get(key, {}).get('enabled'):
            clean_conf[key] = {k: v for k, v in parsed_config[key].items() if v != ''}

    # Удаление рудиментарных параметров, которые должны быть переопределены для каждой новой стрельбы
    for section in (sect for sect in list(yaml_rudimentary_params.keys()) if not sect.startswith('phantom')):
        for r_param in yaml_rudimentary_params[section]:
            try:
                del clean_conf[section][r_param]
            except KeyError:
                pass
    # проверяем не принадлежит ли значение параметра списку дефолтных значений для этого параметра.
    for section in list(yaml_defaults.keys()):
        if clean_conf.get(section) and all(
                [clean_conf.get(section, {})[k] in yaml_defaults[section].get(k, [])
                 for k in clean_conf.get(section, {})]):
            del clean_conf[section]
        elif clean_conf.get(section):
            for param in list(clean_conf[section].keys()):
                if clean_conf[section][param] in yaml_defaults[section].get(param, []):
                    del clean_conf[section][param]

    # Multiple or single phantom sections
    for k in clean_conf.keys():
        # Удаляем stpd_file всегда
        if k == 'phantom':
            try:
                clean_conf[k].pop('stpd_file')
            except KeyError:
                pass
            for section in clean_conf[k].get('multi', []):
                try:
                    section.pop('stpd_file')
                except KeyError:
                    pass

    # Блин одни костыли.
    # специальное поведение для метаинформации аплоадера
    # удаляем рудименты и удаляем саму мету если она оказалась пустой
    if clean_conf.get('uploader', {}).get('meta', {}):
        for p in yaml_uploader_meta_rudimentary_params:
            try:
                del clean_conf['uploader']['meta'][p]
            except KeyError:
                pass
        if not clean_conf.get('uploader', {}).get('meta', {}):
            try:
                del clean_conf['uploader']['meta']
            except KeyError:
                pass

    # Удаляем мету в телеграфе
    # FIXME: наверное, после того как это исправится в танке, надо будет и тут убрать
    try:
        del clean_conf['telegraf']['meta']
    except KeyError:
        pass

    return clean_conf


def _strip_ini_config(parsed_config):
    """
    Обратная совместимость для старых стрельб
    Можно, наверное все конфиги перегнать через этот код и валидатор, и перезаписать как ямль.
    :param parsed_config:
    :return:
    """

    # Удаление рудиментарных параметров, которые должны быть переопределены для каждой новой стрельбы
    for section in (sect for sect in list(rudimentary_params.keys()) if not sect.startswith('phantom')):
        for r_param in rudimentary_params[section]:
            try:
                del parsed_config[section][r_param]
            except KeyError:
                pass
    # Для чистоты конфига и уменьшения его длины в эдиторе firestarter
    # проверяем не принадлежит ли значение параметра списку дефолтных значений для этого параметра.
    for section in list(section_defaults.keys()):
        if parsed_config.get(section) and all(parsed_config.get(section, {})[k] in section_defaults[section].get(k, [])
                                              for k in parsed_config.get(section, {})):
            del parsed_config[section]
        elif parsed_config.get(section):
            for param in list(parsed_config[section].keys()):
                if parsed_config[section][param] in section_defaults[section].get(param, []):
                    del parsed_config[section][param]

    # Multiple or single phantom sections
    for p_section in (sect for sect in list(parsed_config.keys()) if sect.startswith('phantom')):
        # Поддержка stpd файла.
        # Его нужно удалять из конфига повторяемой стрельбы, только если указаны либо патроны, либо uris
        # Иначе невозможно будет повторить стрельбу с указанным кастомным stpd
        ammofile = parsed_config[p_section].get('ammofile', '')
        uris = parsed_config[p_section].get('uris', [])
        stpd_file = parsed_config[p_section].get('stpd_file', '')
        if stpd_file and not any((ammofile, uris), ):
            parsed_config[p_section]['ammofile'] = ''
            parsed_config[p_section]['uris'] = []
        else:
            try:
                del parsed_config[p_section]['stpd_file']
            except KeyError:
                pass
        # Удаление рудиментарных параметров, которые должны быть переопределены для каждой новой стрельбы
        for r_param in rudimentary_params['phantom']:
            try:
                del parsed_config[p_section][r_param]
            except KeyError:
                pass
        # Для чистоты конфига и уменьшения его длины в эдиторе firestarter
        # проверяем не принадлежит ли значение параметра списку дефолтных значений для этого параметра.
        for param in section_defaults['phantom']:
            if parsed_config[p_section].get(param) is not None and parsed_config[p_section][param] in \
                    section_defaults['phantom'][param]:
                del parsed_config[p_section][param]
        #
        try:
            if not parsed_config[p_section]['rps_schedule'] and parsed_config[p_section]['instances_schedule']:
                del parsed_config[p_section]['rps_schedule']
            elif parsed_config[p_section]['rps_schedule'] and not parsed_config[p_section]['instances_schedule']:
                del parsed_config[p_section]['instances_schedule']
        except KeyError:
            pass

    return parsed_config


def _validate_config(conf, fmt):
    """

    :param conf: dict
    :return:
    """
    url = VALIDATOR_URL.format(fmt)
    resp = requests.post(url, files={'config': conf}, verify=False)
    return resp.json()
