from xml.etree import ElementTree
import ntpath

from parallels.core.logging import get_logger
from parallels.core.actions.base.subscription_action import SubscriptionAction
from parallels.core.runners.base import BaseRunner
from parallels.core.utils import subscription_filter, windows_utils
from parallels.plesk.source.plesk import messages
from parallels.plesk.source.plesk.actions.content.web.utils import \
    list_webuser_security_files, get_main_security_file

logger = get_logger(__name__)


class CreateSecuredWebContent(SubscriptionAction):
    """Create files and directories that have customized Windows permissions.

    Operation of permissions update in Windows is too slow if there are a lot
    of folders. For example, update permissions of webspace with ~50000 folders
    takes ~1 hour. So we should create files and directories and apply customized
    permissions before web-content coping.
    """

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

        :rtype: str | unicode
        """
        return messages.ACTION_CREATE_SECURED_WEB_CONTENT_DESCRIPTION

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

        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: str | unicode
        """
        return messages.ACTION_CREATE_SECURED_WEB_CONTENT_FAILURE

    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
        :rtype: bool
        """
        return (
            subscription_filter.windows_with_virtual_hosting(subscription) and
            # Check that ResetPermissions action is already executed. We prevent repeat runs
            # of ResetPermissions action to improve performance and it is not sense to run
            # this action if ResetPermissions will be not executed.
            subscription_filter.reset_permissions(subscription, global_context)
        )

    def run(self, global_context, subscription):
        """
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        """
        source_server = subscription.web_source_server
        target_server = subscription.web_target_server
        processed_entries = set()
        with source_server.runner() as source_runner, target_server.runner() as target_runner:
            assert isinstance(source_runner, BaseRunner)
            assert isinstance(target_runner, BaseRunner)

            for security_file in [get_main_security_file()] + list_webuser_security_files(subscription):
                security_file_path = windows_utils.path_join(
                    target_server.vhosts_dir, subscription.name_idn, '.plesk', security_file
                )

                if not target_runner.file_exists(security_file_path):
                    continue

                logger.fdebug(messages.LOG_CREATE_SECURED_WEB_CONTENT, path=security_file_path)
                try:
                    security_file_contents = target_runner.get_file_contents(security_file_path)
                    security_file_xml_tree = ElementTree.fromstring(security_file_contents)
                except Exception as e:
                    logger.exception()
                    logger.fwarn(messages.LOG_FAILED_TO_READ_SECURITY_FILE, path=security_file_path, reason=e)
                    continue

                self._create_secured_web_content(subscription, security_file_xml_tree, processed_entries)

    @staticmethod
    def _create_secured_web_content(subscription, security_file_xml_tree, processed_entries):
        """Create files/directories, which will be secured according to specified security file XML tree.

        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :type processed_entries: set[string]
        :rtype: None
        """
        source_server = subscription.web_source_server
        target_server = subscription.web_target_server

        with source_server.runner() as source_runner, target_server.runner() as target_runner:
            for entry_node in security_file_xml_tree.findall('Entry'):
                entry_sub_path = entry_node.get('SubPath')

                if not entry_sub_path:  # empty path of entry in .Security XML file - skip
                    continue

                entry_source_path = windows_utils.path_join(source_server.vhosts_dir, entry_sub_path)
                if entry_source_path in processed_entries:  # already processed in previous iterations - skip
                    continue
                processed_entries.add(entry_source_path)

                entry_is_dir = source_runner.is_dir(entry_source_path)
                if entry_is_dir is None:  # entry (file or directory) does not exist on the source server - skip
                    continue

                entry_target_path = windows_utils.path_join(target_server.vhosts_dir, entry_sub_path)

                if target_runner.file_exists(entry_target_path):  # item already exists on the target server - skip
                    continue

                if entry_is_dir:
                    target_runner.mkdir(entry_target_path)
                else:
                    # create base directory of item on target
                    target_runner.mkdir(ntpath.dirname(entry_target_path))
                    # create empty item on target
                    target_runner.upload_file_content(entry_target_path, '')
