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.mail import messages
from parallels.plesk.source.mail.mail_migration_list import MailMigrationListReader
from parallels.plesk.source.mail.status_storage import StatusStorage


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.mail.global_context.MailGlobalMigrationContext
        """
        mail_migration_list_filename = global_context.options.mail_migration_list
        migration_list = MailMigrationListReader().read(mail_migration_list_filename)

        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 = MailMigrationListReader().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 all selected objects as cancelled
        status_storage = StatusStorage(
            global_context.session_files.get_global_status_file().path,
            global_context.session_files.get_global_status_file_lock().path
        )
        for mail_address_pair in migration_list.mail_address_pairs:
            status_storage.set_global_status(
                mail_address_pair.source_email, mail_address_pair.target_mailname, StatusStorage.STATUS_CANCELLED
            )

    @staticmethod
    def _migration_list_includes(outer_migration_list, inner_migration_list):
        """
        :type outer_migration_list: parallels.plesk.source.mail.mail_migration_list.MailMigrationList
        :type inner_migration_list: parallels.plesk.source.mail.mail_migration_list.MailMigrationList
        :rtype: bool
        """
        outer_mail_address_pairs = default(outer_migration_list.mail_address_pairs, [])
        inner_mail_address_pairs = default(inner_migration_list.mail_address_pairs, [])
        for inner_app in inner_mail_address_pairs:
            if not exists(
                outer_mail_address_pairs,
                lambda outer_app: (
                    inner_app.source_email == outer_app.source_email and
                    inner_app.target_mailname == outer_app.target_mailname
                )
            ):
                return False

        return True
