import pipes
import sys
from contextlib import contextmanager
import os

from parallels.core import ParallelExecutionError
from parallels.core.logging import get_logger
from parallels.core import messages
from parallels.core.migrator_config import MultithreadingParams, MultithreadingStatus
from parallels.core.registry import Registry
from parallels.core.utils.common import is_run_on_windows, is_empty, safe_format
from parallels.core.utils.deamon import demonize
from parallels.core.utils.locks.file_lock_exception import FileLockException
from parallels.core.utils.migrator_utils import get_optional_option_value
from parallels.core.utils.steps_profiler import get_default_steps_profiler
from parallels.core.utils.windows_utils import format_args_list

logger = get_logger(__name__)


class Runner(object):
    def __init__(self, multithreading=None):
        """Class constructor
        :type multithreading: parallels.core.migrator_config.MultithreadingParams | None
        """
        self._context = Registry.get_instance().get_context()
        if multithreading is None:
            self._multithreading = MultithreadingParams(status=MultithreadingStatus.DISABLED, num_workers=None)
        else:
            self._multithreading = multithreading

    def run_entry_point(self, entry_point_name):
        """Run specified entry point and execute shutdown actions
        :type entry_point_name: str
        :rtype: None
        """
        registry = Registry.get_instance()
        entry_point = registry.get_workflow().get_entry_point(entry_point_name)

        if getattr(self._context.options, 'async'):
            demonize()

        if not self._context.options.is_skip_logging:
            registry.get_logging_configuration().configure(self._context.options, use_separate_log_by_default=True)

        with logger.migrator_execution_context(messages.MIGRATOR_EXECUTION, command=entry_point_name):
            logger.debug(messages.MIGRATOR_START % self._format_current_command())

            self._context.progress.start_progress_thread()

            migration_lock = registry.get_migration_lock()
            try:
                if not self._context.options.is_allow_parallel_execution:
                    try:
                        # Note: do not release the lock. It will be released automatically once the process is stopped.
                        # That is necessary for removing of sessions. When "stop-migration" command acquires that lock,
                        # we should be 100% sure that the process is terminated.
                        # Otherwise there could be issues when removing files, which are still open by the process.
                        migration_lock.acquire()
                    except FileLockException:
                        raise ParallelExecutionError()
                task_id = get_optional_option_value(self._context.options, 'progress_task_id', None)
                with self._context.progress.report(
                    entry_point_name, task_id=task_id
                ):
                    try:
                        self.run(entry_point, handle_stop_mark=self._context.options.is_allow_stop)
                    finally:
                        self._shutdown(entry_point)
            finally:
                if not Registry.get_instance().get_context().options.is_skip_progress_reporting:
                    # Stop progress thread and wait till it writes final results for progress file
                    self._context.progress.stop_progress_thread()
                    if self._context.progress.progress_thread is not None:
                        self._context.progress.progress_thread.join()

                logger.debug(messages.MIGRATOR_END % self._format_current_command())

    @staticmethod
    def _format_current_command():
        args = [a for a in sys.argv if a != '--async']
        if not is_run_on_windows():
            return " ".join(pipes.quote(arg) for arg in args)
        else:
            extension_id = os.getenv('PLESK_EXTENSION_ID')
            if is_empty(extension_id):
                extension_id = 'panel-migrator'

            command_str = safe_format(
                r'"%PLESK_DIR%\admin\plib\modules\{extension_id}\backend\plesk-migrator.bat"',
                extension_id=extension_id
            )
            if len(args) > 2:
                command_str += ' ' + format_args_list(args[2:])

            return command_str

    def run(self, action, action_id=None, handle_stop_mark=True):
        raise NotImplementedError()

    def _shutdown(self, entry_point):
        raise NotImplementedError()

    @staticmethod
    @contextmanager
    def _profile_action(action_id, action, profiling_enabled=True):
        """Measure time of specified action

        This function considers that for all parent actions this function was already called, and we're
        already in that context.

        :type action: parallels.core.actions.base.base_action.BaseAction
        :type action_id: str | unicode
        :type profiling_enabled: boolean
        """
        if action.get_description() is not None and profiling_enabled:
            with get_default_steps_profiler().measure_time(action_id, action.get_description()):
                yield
        else:
            # skip profiling in one of following cases:
            # - compound actions that have no descriptions are profiled by all child actions
            # - other actions that have no descriptions are measured as "Other" action
            # - profiling was explicitly disabled
            yield
