import os

from parallels.core import get_logger
from parallels.core.actions.base.common_action import CommonAction
from parallels.core.actions.utils.logging_properties import LoggingProperties
from parallels.core.task_manager.manager import TaskManager, TaskOperations
from parallels.core.task_manager.task import Task
from parallels.core.utils.common import default, exists, open_no_inherit
from parallels.core.utils.locks.file_lock import FileLock
from parallels.plesk.source.web import messages
from parallels.plesk.source.web.site_migration_list import SiteMigrationListReader
from parallels.plesk.source.web.tasks import Tasks
from parallels.plesk.source.web.utils.status_storage import StatusStorage
from parallels.plesk.source.web.utils.status_utils import set_status_for_all_migration_list_objects


logger = get_logger(__name__)


class CancelTasksAction(CommonAction):
    def get_description(self):
        """Get short description of action as string

        :rtype: str
        """
        return messages.ACTION_CANCEL_TASKS_DESCRIPTION

    def get_failure_message(self, global_context):
        """Get message for situation when action failed

        :type global_context: parallels.core.global_context.GlobalMigrationContext
        """
        return messages.ACTION_CANCEL_TASKS_FAILURE

    def get_logging_properties(self):
        """Get how action should be logged to migration tools end-user

        :rtype: parallels.core.actions.utils.logging_properties.LoggingProperties
        """
        return LoggingProperties(info_log=False)

    def run(self, global_context):
        """Run action

        :type global_context: parallels.plesk.source.web.global_context.WebGlobalMigrationContext
        """
        site_migration_list_filename = global_context.options.site_migration_list
        migration_list = SiteMigrationListReader().read(site_migration_list_filename)

        source = global_context.conn.get_source()
        status_storage = StatusStorage(
            global_context.session_files.get_status_file(), source.config.host
        )

        task_manager = TaskManager(global_context.session_files.get_file_path('tasks'))
        with task_manager.task_operations() as task_operations:
            assert isinstance(task_operations, TaskOperations)
            for task in task_operations.get_all_tasks():
                task_status = task_operations.get_task_status(task)
                if task_status in [Task.STATUS_PREPARE, Task.STATUS_READY, Task.STATUS_RUNNING]:
                    task_migration_list = SiteMigrationListReader().read(
                        os.path.join(task.task_dir, 'migration-list.json')
                    )
                    if self._migration_list_includes(migration_list, task_migration_list):
                        if task_status == Task.STATUS_RUNNING:
                            # The task is running right now - stop it.
                            logger.finfo(messages.STOP_RUNNING_TASK, task_id=task.task_id)
                            # 1. Create a stop file.
                            # Task process (site migration) checks periodically if stop file exists,
                            # and if so, shuts down as soon as possible.
                            with open_no_inherit(os.path.join(task.task_dir, 'stop'), 'w'):
                                pass
                            # 2. Wait once the task process finishes.
                            # Task process holds the migration lock when it is running.
                            # Once it exists - the migration lock is released, and we can acquire the lock.
                            # Here we block till we acquire the lock.
                            lock = FileLock(task.task_dir, 'migration.lock')
                            lock.acquire_block()
                            lock.release()
                            task_operations.set_task_status(task, Task.STATUS_CANCELLED)
                            logger.finfo(messages.TASK_STOPPED, task_id=task.task_id)
                        else:
                            task_operations.set_task_status(task, Task.STATUS_CANCELLED)
                            logger.finfo(messages.TASK_CANCELLED, task_id=task.task_id)

                        set_status_for_all_migration_list_objects(
                            task_migration_list, status_storage, StatusStorage.STATUS_CANCELLED,
                            Tasks.get_storage_status_type(task_migration_list.task)
                        )

    @staticmethod
    def _migration_list_includes(outer_migration_list, inner_migration_list):
        """
        :type outer_migration_list: parallels.plesk.source.web.site_migration_list.SiteMigrationList  
        :type inner_migration_list: parallels.plesk.source.web.site_migration_list.SiteMigrationList 
        :rtype: bool 
        """
        outer_apps = default(outer_migration_list.filter_applications, [])
        inner_apps = default(inner_migration_list.filter_applications, [])
        for inner_app in inner_apps:
            if not exists(outer_apps, lambda outer_app: inner_app == outer_app):
                return False

        outer_files = default(outer_migration_list.include_files, [])
        inner_files = default(inner_migration_list.include_files, [])
        for inner_file in inner_files:
            if not exists(outer_files, lambda outer_file: inner_file == outer_file):
                return False

        outer_dbs = default(outer_migration_list.include_databases, [])
        inner_dbs = default(inner_migration_list.include_databases, [])
        for inner_db in inner_dbs:
            if not exists(outer_dbs, lambda outer_db: inner_db == outer_db):
                return False

        return True
