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

import os
import argparse
import time
import logging

import yt.yson as yson

import yt_cypress
import duty
import saas.tools.ssm.modules.startrek_api as startrek_api
import saas.tools.ssm.modules.logbroker as logbroker
import saas.library.persqueue.common.python.installations as lb_installations

from saas.library.python import errorbooster
from saas.library.python.saas_ctype import SaasCtype

from saas.tools.ssm.SaaSServiceManager import SaaSServiceManager
from saas.tools.ssm.ssm_ferryman import FerrymanNanny
from saas.tools.ssm.modules.misc import load_and_write_resources
from saas.tools.ssm.modules.sandbox_ferryman import FerrymanConfigRequest
from saas.library.python.awacs import helpers


from daemon import Daemon


class SsmWorker(Daemon):
    """
    Main daemon class with ssm listen logic
    """
    def __init__(self, config='conf/ssm.conf', pid_file='', yt_path='', logfile=None, loglevel=logging.INFO):
        self._logfile = logfile
        self._loglevel = loglevel

        load_and_write_resources('/conf')
        load_and_write_resources('/tmpl')
        self._wd = os.getcwd()

        if yt_path:
            self._yt_jobs = yt_cypress.SaaSYtJobControl(basedir=yt_path)
        else:
            self._yt_jobs = yt_cypress.SaaSYtJobControl()

        self._startrek_client = startrek_api.SaaSStartrekWorkflow()
        super(SsmWorker, self).__init__(pid_file, name='ssm_worker')

    def _approve_checks(self, job, job_attrs):
        """
        Check for approve conditions
        :param job: type str
        :param job_attrs: type dict
        """
        if not job_attrs.get('job_type') or job_attrs['job_type'] == 'service':
            if (job_attrs['service_ctype'].startswith('prestable') or job_attrs['service_ctype'] == 'testing') and int(job_attrs['instances_count']) <= 10:
                logging.info('[%s] Found request for %s with small instances count. Approve is not required', job, job_attrs['service_ctype'])
                self._yt_jobs.set_approve_status(job, True)
                return

        if job_attrs.get('job_type') == 'namespace' and int(job_attrs['namespace_size']) <= 2**30:
            logging.info('[%s] Found request for %s with small namespace size. Approve is not required', job, job_attrs.get('ferryman_ctype'))
            self._yt_jobs.set_approve_status(job, True)
            return

        # Check Startrek ticket for approve
        if not job_attrs['approved']:
            startrek_issue_num = job_attrs['sla_info'].get('ticket')
            if startrek_issue_num and self._startrek_client.check_for_approve(startrek_issue_num, u'подтверждаю',
                                                                              ['saku', 'i024', 'salmin', 'anikella']):
                logging.info('[%s] Found approve from ticket %s, changing approve status', job, startrek_issue_num)
                self._yt_jobs.set_approve_status(job, True)
                return
            logging.info('not approve now')

    def _create_ferryman(self, job_attrs):
        """
        Create Ferryman for service.
        :param job_attrs: type dict
        :return:
        """

        yt_cluster = job_attrs['delivery_info'].get('yt_cluster')
        row_processor = job_attrs['delivery_info'].get('yt_ferryman_format')
        delivery_type = job_attrs['delivery_info'].get('delivery_type')
        if 'ferryman' in delivery_type:
            delivery_type_suffix = delivery_type.split('_')[1]
            ferryman_type = 'ytpull' if 'mapreduce' in delivery_type_suffix else delivery_type_suffix

        for_logbroker_backup = delivery_type == 'logbroker'
        if for_logbroker_backup:
            row_processor = 'logbroker'
            ferryman_type = 'logbroker_backup'
            if yt_cluster is None:
                yt_cluster = 'arnold'

        issue_num = job_attrs['sla_info']['ticket']

        # Basic attrs
        service_name = job_attrs.get('service_name')
        service_ctype = job_attrs.get('service_ctype')

        # Create Ferryman
        ferryman_client = FerrymanNanny(service_name,
                                        yt_cluster=yt_cluster,
                                        row_processor=row_processor,
                                        service_type=job_attrs['service_type'],
                                        service_ctype=service_ctype,
                                        input_size=job_attrs['sla_info'].get('maxdocs', 1000000),
                                        ferryman_type=ferryman_type
                                        )
        ferryman_balancer = ferryman_client.create_service()

        # Startrek report
        comment = (
            'Ferryman заведен. Будет доступен после обновления конфигурации\n'
            'Адрес ручки: {ferryman_url}\n'
            'Документация: {docs_url}'
        ).format(
            ferryman_url=ferryman_balancer,
            docs_url='https://wiki.yandex-team.ru/jandekspoisk/SaaS/Ferryman/'
        )
        if for_logbroker_backup:
            comment = 'Ferryman для формирования бэкапа заведен.'
        self._startrek_client.add_comment(issue_num, comment)
        return ferryman_balancer

    def _post_actions(self, job_attrs):
        """
        Additional Startrek issue modifications
        :param  job_attrs: type dict
        """
        if job_attrs['sla_info'].get('ticket'):
            service_name = job_attrs['service_name']
            service_ctype = job_attrs['service_ctype']
            issue_num = job_attrs['sla_info']['ticket']

            if service_ctype != 'testing':
                current_duty = duty.get_current_duty()
                # Send messagge for duty
                if current_duty:
                    self._startrek_client.add_to_followers(issue_num, current_duty)
                    self._startrek_client.add_comment(issue_num, 'Заведен новый сервис. Требуется деплой проксей.',
                                                      summonees=current_duty)

            # Check for additional works
            if 'ferryman' in job_attrs['delivery_info'].get('delivery_type', ''):
                self._startrek_client.reopen_issue(issue_num)
                self._create_ferryman(job_attrs)

            if 'logbroker' in job_attrs['delivery_info'].get('delivery_type', ''):
                self._startrek_client.reopen_issue(issue_num)
                fm_balancer = self._create_ferryman(job_attrs)
                self._startrek_client.add_to_followers(issue_num, 'i024')
                try:
                    logging.debug('Going to set logbroker stuff up, %s', [service_ctype, service_name, fm_balancer])
                    logbroker.init_logs(self._logfile, self._loglevel)

                    tvm_write = [int(tvm) for tvm in job_attrs['delivery_info']['logbroker_tvm_id'].split(',')]

                    saas_ctype = SaasCtype(service_ctype)
                    tvm_id = saas_ctype.environment.get('TVM_SERVICE_ID', saas_ctype.saas_debug_proxy_tvm_id)
                    tvm_read = [int(tvm_id)]

                    logging.info('Creating lb_resources for %s/%s', service_ctype, service_name)
                    lb_config = logbroker.create_lb_resources(service_name, service_ctype, tvm_write, tvm_read)
                    logging.debug('LB_config : %s', lb_config)

                    logging.info('Creating SB schedueler for %s/%s ferryman %s', service_ctype, service_name, fm_balancer)
                    scheduler_id = logbroker.create_sb_scheduler(service_name, service_ctype, lb_config, fm_balancer)

                except Exception as e:
                    comment = 'Ресурсы для доставки данных не удалось завести автоматически.\n' + \
                        'Полученная ошибка:%%{error}%%\n'.format(error=e) + \
                        '<{Детальная информация:\n%%job_attrs=' + yson.dumps(job_attrs) + '%%}>'
                    logging.exception(e)
                else:
                    comment = 'Шедулер для формирования бэкапов заведен, id={}\n\n'.format(scheduler_id) + \
                        '**Данные для записи в logbroker:**\n' + \
                        'LB endpoint: %%{endpoint}%%\nTopicsDir: %%{topics_dir}%%\n\n'.format(
                            endpoint=lb_installations.get_endpoint(lb_config.Logbroker),
                            topics_dir=lb_config.TopicsPath
                        ) + 'Как писать данные в logbroker: https://wiki.yandex-team.ru/jandekspoisk/saas/DataDelivery/PQ/'
                self._startrek_client.add_comment(issue_num, comment)

            balancer_ref = helpers.namespace_id_from_ctype(service_ctype)
            proxy_macro = SaasCtype(service_ctype).proxy_macro
            puncher_path = "create_destinations={},{}&create_ports=80-82%2C%2084&create_protocol=tcp".format(
                proxy_macro, balancer_ref)
            comment = 'Форма запроса доступов до проксей и балансера: https://puncher.yandex-team.ru/?{})'.format(
                puncher_path)
            self._startrek_client.add_comment(issue_num, comment)

            self._startrek_client.resolve_issue(issue_num)

    def _service_job_workflow(self, job, job_attrs):
        """
        Service creation workflow
        :param job: type str
        :param job_attrs: type dict
        :return:
        """
        if not self._yt_jobs.check_job(job, stage='service'):
            self._yt_jobs.start_service_stage(job)
            manager = SaaSServiceManager(job_attrs.get('service_name'), job_attrs.get('service_ctype'),
                                         job_attrs.get('service_type'),
                                         int(job_attrs.get('instances_count')),
                                         memory_requirements=int(job_attrs.get('req_memory')),
                                         sla_info=job_attrs.get('sla_info'),
                                         delivery_info=job_attrs.get('delivery_info'),
                                         cpu_requirements=int(job_attrs.get('req_cpu')),
                                         replicas_per_dc=int(job_attrs.get('replicas_per_dc')),
                                         startrek_issue=bool(job_attrs.get('startrek_issue', False)),
                                         tvm_id=job_attrs.get('service_tvm'),
                                         saas_tvm=job_attrs.get('service_saas_tvm'),
                                         src_service=job_attrs.get('template_service'),
                                         no_indexing=bool(job_attrs.get('no_indexing', False)),
                                         service_shard_by='url_hash')
            if job_attrs.get('service_ctype') == 'testing':
                manager.create_testing_service()
            else:
                # Prestable services is also can be created here
                manager.create_production_service()
            self._yt_jobs.finish_service_stage(job)
        # Post actions with ferryman, logbroker configure etc.
        if not self._yt_jobs.check_job(job, stage='delivery'):
            self._yt_jobs.start_delivery_stage(job)
            self._post_actions(job_attrs)
            self._yt_jobs.finish_delivery_stage(job)

    def _namespace_job_workflow(self, job, job_attrs):
        """
        Namespace creation workflow
        :param job: type str
        :param job_attrs: type dict
        :return:
        """
        if not self._yt_jobs.check_job(job, stage='service'):
            self._yt_jobs.start_service_stage(job)
            FerrymanConfigRequest.new_namespace(job_attrs.get('namespace_name'), job_attrs.get('ferryman_service'),
                                                job_attrs.get('ferryman_ctype'),
                                                namespace_owners=job_attrs.get('owners_list'),
                                                namespace_size=job_attrs.get('namespace_size'),
                                                namespace_doccount=job_attrs.get('namespace_doccount'),
                                                ticket=job_attrs['sla_info'].get('ticket'))
            self._yt_jobs.finish_service_stage(job)

    def run(self):
        """
        Main loop function
        """
        errorbooster.ErrorBooster('saas-ssm')
        while True:
            job = ""
            try:
                available_jobs = self._yt_jobs.get_avail_jobs()
                for job in available_jobs:
                    logging.info('Found job %s', job)
                    job_attrs = self._yt_jobs.get_job_info(job)
                    logging.info(job_attrs)

                    # Check for approve
                    self._approve_checks(job, job_attrs)

                    if self._yt_jobs.check_job(job):
                        # Create service or namespace
                        os.chdir(self._wd)
                        with self._yt_jobs.run_job(job):
                            logging.info('[%s] Running job', job)
                            # Check job type: service or namespace
                            if job_attrs.get('job_type') and job_attrs['job_type'] == 'namespace':
                                self._namespace_job_workflow(job, job_attrs)
                            else:
                                self._service_job_workflow(job, job_attrs)
                            self._yt_jobs.finish_job(job)
                            logging.info('[%s] Finish job', job)
                    else:
                        logging.info('[%s] Job is not approved or processing by another worker', job)
                time.sleep(60)
            except Exception as exc:
                errorbooster.handle_exception(exc)
                logging.exception('SSM Worker crashes: %s', exc)
                if job:
                    self._yt_jobs._remove_lock(job)
                time.sleep(60)
                continue


def prepare_logging(logfile=None, loglevel=logging.INFO):
    """
    Prepare logging settings
    """
    logger = logging.getLogger()
    handlers = [logging.StreamHandler()]
    logger.setLevel(loglevel)

    if logfile:
        handlers.append(logging.FileHandler(logfile))
    formatter = logging.Formatter('[%(asctime)s SSM [%(process)d] [%(filename)18s#%(lineno)s] [%(levelname)s] %(message)s',
                                  datefmt='%Y-%m-%d %H:%M:%S')
    for handler in handlers:
        handler.setFormatter(formatter)
        logger.addHandler(handler)


def arg_parser(*args):
    parser = argparse.ArgumentParser(
        prog="SSM Worker",
        formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument('action', nargs='?', choices=('start', 'stop', 'restart', 'status', 'run'))
    parser.add_argument('-c,' '--config', dest='config', default='conf/ssm.conf')
    parser.add_argument('-l', '--logfile', dest='logfile', default='ssm_worker.log')
    parser.add_argument('--yt-path', dest='yt_path', default='//home/saas/ssm')
    parser.add_argument('--pid-file', dest='pid', default='/tmp/ssm_worker.pid')
    parser.add_argument('--debug', dest='debug', action='store_true', default=False)
    if args:
        options = parser.parse_args(args)
    else:
        options = parser.parse_args()
    return options


def main(*args):
    # Arguments
    options = arg_parser(*args)

    loglevel = logging.DEBUG if options.debug else logging.INFO
    prepare_logging(logfile=options.logfile, loglevel=loglevel)

    daemon = SsmWorker(
        config=options.config,
        pid_file=options.pid,
        yt_path=options.yt_path,
        logfile=options.logfile,
        loglevel=loglevel
    )
    # Actions
    if options.action == 'start':
        logging.info('Starting SSM Worker')
        daemon.start()
    if options.action == 'restart':
        logging.info('Restarting SSM Worker')
        daemon.restart()
    if options.action == 'stop':
        logging.info('Stopping SSM Worker')
        daemon.stop()
    if options.action == 'status':
        daemon.status()
    if options.action == 'run':
        daemon.run()


if __name__ == '__main__':
    main()
