from load.projects.tasklets.shooting.proto import shooting_tasklet
from ci.tasklet.common.proto import service_pb2 as ci
from tasklet.services.yav.proto import yav_pb2
from sandbox.common.rest import Client
from retry import retry
from . import tools
from .tools import ShootingError

import re
import time
import yaml
import logging
import sandbox.common.types.task as ctt
import sandbox.common.patterns as patterns


TASK_TYPE = 'FIRESTARTER'
GET_STATUS_TIMEOUT = 20
NO_STATUS_MAX = 10

BREAK_STATUSES = (
    ctt.Status.EXCEPTION,
    ctt.Status.NO_RES,
    ctt.Status.TIMEOUT,
    ctt.Status.STOPPED,
    ctt.Status.EXPIRED,
    ctt.Status.NOT_RELEASED,
    ctt.Status.FAILURE
)
SUCCESS_STATUSES = (
    ctt.Status.SUCCESS,
    ctt.Status.RELEASED
)


class ShootingImpl(shooting_tasklet.ShootingBase):

    def run(self):

        try:
            if re.match(r'^http', self.input.load.config):
                config_content = tools.read_urlfile(self.input.load.config)
            elif tools.check_for_arc_path(self.input.load.config):
                config_content = tools.read_arcfile(self.input.load.config)
            else:
                config_content = str(self.input.load.config)
        except AttributeError as error:
            raise ShootingError('Wrong load config.') from error

        logging.info(f'Config content:\n{config_content}')

        if self.input.load.options:
            from sandbox.projects.tank.Firestarter.tasks import compile_config
            config_content = compile_config(config_content)
            for option in self.input.load.options:
                config_content = tools.apply_option(config_content, option)
            config_content = yaml.safe_dump(config_content)

        if '!inject :' in str(config_content):
            params = {'tank_config': config_content.decode('utf-8')}
        else:
            params = {'tank_config': yaml.safe_dump(tools.prepare_config(yaml.safe_load(config_content)))}

        try:
            task_id = self.start_task(
                TASK_TYPE,
                tools.get_group(self.input.sandbox.owner),
                params,
                self.input.sandbox.description
            )

            progress_msg = ci.TaskletProgress()
            progress_msg.job_instance_id.CopyFrom(self.input.context.job_instance_id)
            progress_msg.id = 'YandexTankShooting'
            progress_msg.module = 'SANDBOX'
            progress_msg.url = f'{tools.SANDBOX}/task/{task_id}/view'
            progress_msg.status = ci.TaskletProgress.Status.RUNNING

            self.ctx.ci.UpdateProgress(progress_msg)

        except Exception:
            logging.error('ERROR! Failed tasklet running or execution', exc_info=True)
            raise ShootingError('Error during tasklet execution')

        self.output.result.shooting_id = self.get_shooting_id(task_id)
        if not self.output.result.shooting_id:
            self.output.result.shooting_id = 0
            raise ShootingError(f'The shooting did not take place. {tools.SANDBOX}/task/{task_id}/view')

        progress_msg.progress = 1
        progress_msg.status = ci.TaskletProgress.Status.SUCCESSFUL
        progress_msg.module = 'LUNAPARK'
        progress_msg.url = f'{tools.LUNAPARK}/{self.output.result.shooting_id}'

        self.ctx.ci.UpdateProgress(progress_msg)

        if not tools.check_shooting_quit_status(self.output.result.shooting_id):
            raise ShootingError('Non-zero shooting completion status')

    @patterns.singleton_property
    def sandbox_client(self):
        spec = yav_pb2.YavSecretSpec(uuid=self.input.context.secret_uid, key=self.input.sandbox.token)
        return Client(auth=self.ctx.yav.get_secret(spec).secret)

    @retry(tries=3)
    def start_task(self, task_type, owner, params, description='Shooting from Tasklet'):
        task = self.sandbox_client.task.create(
            {
                'type': task_type,
                'description': description or self.default_description,
                'owner': owner,
                'custom_fields': [{'name': k, 'value': v} for k, v in params.items()]
            }
        )
        self.sandbox_client.batch.tasks.start.update([task['id']])
        return task['id']

    def get_shooting_id(self, task_id, timeout=1 * 60 * 60 + 600):
        start = time.time()
        nostatus = 0
        attempt_after_success = 0
        shooting_id = None
        while time.time() - start <= timeout:

            if shooting_id:
                if tools.check_shooting_td(shooting_id):
                    break
                else:
                    logging.info(f'[GET_SHOOTING_ID] Shooting {shooting_id} doesn\'t stop yet')
                    time.sleep(GET_STATUS_TIMEOUT)
            else:
                time.sleep(GET_STATUS_TIMEOUT)
                status = self.check_task(task_id)

                if status in SUCCESS_STATUSES:
                    shooting_id = self.get_output_shooting_id(task_id)
                    logging.info(f'[GET_SHOOTING_ID] Task {task_id} run a shooting {shooting_id}')
                    attempt_after_success += 1
                    if attempt_after_success > NO_STATUS_MAX:
                        raise ShootingError(f'Sandbox task {tools.SANDBOX}/task/{task_id}/view was completed successfully, but does not have shooting_id.')

                if status == ctt.Status.DRAFT and (time.time() - start) > GET_STATUS_TIMEOUT * 4:
                    raise ShootingError(f'Sandbox task {tools.SANDBOX}/task/{task_id}/view is stuck in the {status} status')

                if status in BREAK_STATUSES:
                    raise ShootingError(f'Sandbox task {tools.SANDBOX}/task/{task_id}/view was finished with the {status} status.')

                if not status:
                    nostatus += 1
                    if nostatus > NO_STATUS_MAX:
                        raise ShootingError(f'Sandbox task {tools.SANDBOX}/task/{task_id}/view did not respond {nostatus} times.')
        return shooting_id

    def check_task(self, task_id):
        try:
            status = self.sandbox_client.task[task_id].read()['status']
            logging.info(f'[CHECK_STATUS] Task {task_id} has status {status}')
            return status
        except Exception:
            logging.error('[CHECK_STATUS] Failed to get status of the shooting task', exc_info=True)

    def get_output(self, task_id):
        try:
            return {output['name']: output.get('value', None) for output in self.sandbox_client.task[task_id].output.read()}
        except Exception:
            logging.error('[GET_OUTPUT] Wrong output from sandbox client')

    def get_output_shooting_id(self, task_id):
        output = self.get_output(task_id)
        if isinstance(output, dict) and str(output.get('lunapark_job_id', '')).isdigit():
            return int(output['lunapark_job_id'])
        elif isinstance(output, dict) and str(output.get('lunapark_id', '')).isdigit():
            return int(output['lunapark_id'])
        else:
            logging.error(f'[GET_OUTPUT_SHOOTING_ID] Can\'t find the shooting_id for task {tools.SANDBOX}/task/{task_id}/view\nSee the logs for more information')
