from parallels.core.logging import get_logger
from parallels.core import messages
from parallels.core.actions.base.subscription_action import SubscriptionAction
from parallels.core.runners.base import BaseRunner
from parallels.core.utils.restore_hosting_utils import restore_hosting_settings_lock
from parallels.core.utils.common.threading_utils import synchronized_by_lock, synchronized

logger = get_logger(__name__)


class TransferErrorDocumentsBase(SubscriptionAction):
    _counter = 0

    def get_description(self):
        return messages.TRANSFER_CUSTOM_ERROR_DOCUMENTS

    def is_critical(self):
        """If action is critical or not

        If action is critical and it failed for a subscription, migration tool
        won't run the next operations for the subscription.

        :rtype: bool
        """
        return False

    def filter_subscription(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        # filter only subscriptions on windows physical servers
        source_info = subscription.get_source_info()
        if not source_info.is_server or not source_info.is_windows:
            return False

        # filter only subscriptions w/ virtual hosting
        if not subscription.converted_dump.is_virtual_hosting:
            logger.debug(messages.SKIP_TRANSFER_CUSTOM_ERROR_DOCUMENTS_TYPES % subscription.name)
            return False

        return True

    def get_failure_message(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        return messages.FAILED_TRANSFER_CUSTOM_ERROR_DOCUMENTS_FOR % subscription.name

    def run(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        for site in subscription.converted_dump.iter_domains():
            if not site.is_virtual_hosting:
                logger.debug(
                    messages.SKIP_TRANSFER_CUSTOM_ERROR_DOCUMENTS_FOR % (
                        site.name, subscription.name
                    )
                )
                continue

            if site.hosting_type == 'sub_hst':
                logger.debug(
                    messages.SKIP_TRANSFER_CUSTOM_ERROR_DOCUMENTS_FOR_SUBDOMAIN_ON_SUBFOLDER.format(
                        site=site.name, subscription=subscription.name
                    )
                )
                continue

            with global_context.safe.try_subscription(
                subscription.name,
                messages.TRANSFER_CUSTOM_ERROR_DOCUMENTS_SITE_S % site.name,
                is_critical=False
            ):
                self._transfer_site_error_documents(global_context, subscription, site)

    def _transfer_site_error_documents(self, global_context, subscription, site):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        vhost_error_documents = self._get_site_error_documents(global_context, subscription, site)
        vdirs_error_documents = self._get_site_vdir_error_documents(global_context, subscription, site)
        self._restore_site_error_documents(site, subscription, vdirs_error_documents, vhost_error_documents)

    # we need to use this lock because calling "websrvmng" when Plesk restore is running could
    # result in "Access is denied. (Error code 5) at create mutex Global" error
    @synchronized_by_lock(lock=restore_hosting_settings_lock)
    def _restore_site_error_documents(self, site, subscription, vdirs_error_documents, vhost_error_documents):
        with subscription.web_target_server.runner() as runner_target:
            assert isinstance(runner_target, BaseRunner)
            if vhost_error_documents is not None:
                logger.debug(messages.RESTORE_ERROR_DOCUMENTS_VIRTUAL_HOSTS_ROOT)
                vhost_filename = self._get_filename(subscription, subscription.web_target_server)
                runner_target.upload_file_content(vhost_filename, vhost_error_documents)
                runner_target.execute_command(
                    '{websrvmng_path} --set-error-docs --vhost-name={vhost_name} '
                    '--error-docs={error_docs} --via-file',
                    dict(
                        websrvmng_path=subscription.web_target_server.websrvmng_bin,
                        vhost_name=site.name.encode('idna'),
                        error_docs=vhost_filename
                    )
                )
                runner_target.remove_file(vhost_filename)

            if vdirs_error_documents is not None:
                for vdir_name, vdir_error_documents in vdirs_error_documents.items():
                    logger.debug(messages.RESTORE_ERROR_DOCUMENTS_VIRTUAL_DIRECTORY_S, vdir_name)
                    vdir_filename = self._get_filename(subscription, subscription.web_target_server)
                    runner_target.upload_file_content(vdir_filename, vdir_error_documents)
                    runner_target.execute_command(
                        '{websrvmng_path} --set-error-docs --vhost-name={vhost_name} '
                        '--vdir-name={vdir_name} --error-docs={error_docs} --via-file',
                        dict(
                            websrvmng_path=subscription.web_target_server.websrvmng_bin,
                            vhost_name=site.name.encode('idna'),
                            vdir_name=vdir_name,
                            error_docs=vdir_filename
                        )
                    )
                    runner_target.remove_file(vdir_filename)

    @synchronized
    def _get_filename(self, subscription, server):
        self._counter += 1
        return server.get_session_file_path(
            'error-docs.{subscription}.{counter}.xml'.format(
                subscription=subscription.name_idn, counter=self._counter
            )
        )

    def _get_site_error_documents(self, global_context, subscription, site):
        """
        Override in child classes

        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        return None

    def _get_site_vdir_error_documents(self, global_context, subscription, site):
        """
        Override in child classes

        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        return None
