import re
import xml.etree.ElementTree as ElementTree

from parallels.core import safe_format
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
from parallels.core.utils import windows_utils
from parallels.core.utils.common.xml import elem, xml_to_string_pretty
from parallels.plesk.source.plesk import messages
from parallels.plesk.source.plesk.actions.content.web.utils import \
    list_webuser_security_files, list_subdomain_security_files, get_main_security_file

logger = get_logger(__name__)


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

        :rtype: str | unicode
        """
        return messages.ACTION_TRANSFER_SECURITY_FILES_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 safe_format(messages.ACTION_TRANSFER_SECURITY_FILES_FAILURE, name=subscription.name)

    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
        """
        with logger.debug_level_context(messages.LOG_GET_IIS_DEDICATED_APPLICATION_POOL_USER):
            iis_app_user = global_context.hosting_repository.subscription.get_dedicated_application_pool_user(
                subscription.name
            )
            if iis_app_user is not None:
                logger.fdebug(messages.LOG_IIS_DEDICATED_APPLICATION_POOL_USER_ON_TARGET, iis_app_user)
            else:
                logger.fdebug(messages.LOG_NO_IIS_DEDICATED_APPLICATION_POOL_USER_ON_TARGET)

        with logger.debug_level_context(messages.LOG_TRANSFER_MAIN_SECURITY_FILE):
            self._transfer_main_security_file_and_merge_subdomains(subscription, iis_app_user)
        if len(subscription.raw_dump.get_phosting_webuser_names()) > 0:
            with logger.debug_level_context(messages.LOG_TRANSFER_WEBUSER_SECURITY_FILES):
                self._transfer_webuser_security_files(subscription, iis_app_user)

    def _transfer_main_security_file_and_merge_subdomains(self, subscription, iis_app_user):
        """Transfer main .Security file and merge subdomain .Security files if any.

        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :type iis_app_user: str | unicode | None
        """
        source_server = subscription.web_source_server
        target_server = subscription.web_target_server

        subdomain_security_file_trees = []

        with target_server.runner() as runner_target, source_server.runner() as runner_source:
            assert isinstance(runner_source, BaseRunner)
            assert isinstance(runner_target, BaseRunner)

            for subdomain_security_file in list_subdomain_security_files(subscription):
                subdomain_security_file_on_source = self._get_source_security_file_location(
                    subscription, subdomain_security_file
                )
                if not runner_source.file_exists(subdomain_security_file_on_source):
                    continue

                with logger.debug_level_context(
                    messages.LOG_LOAD_SUBDOMAIN_SECURITY_FILE, path=subdomain_security_file_on_source
                ):
                    try:
                        security_file_contents = runner_source.get_file_contents(subdomain_security_file_on_source)
                        subdomain_security_file_tree = ElementTree.fromstring(security_file_contents)
                    except Exception as e:
                        logger.fwarn(
                            messages.LOG_FAILED_TO_READ_SUBDOMAIN_SECURITY_FILE_ON_SOURCE,
                            path=subdomain_security_file_on_source, reason=e
                        )
                        continue

                subdomain_security_file_trees.append(subdomain_security_file_tree)

            main_security_file = get_main_security_file()
            main_security_file_on_source = self._get_source_security_file_location(
                subscription, main_security_file
            )
            main_security_file_on_target = self._get_target_security_file_location(
                subscription, main_security_file
            )

            main_security_file_tree = None
            if runner_source.file_exists(main_security_file_on_source):
                with logger.debug_level_context(
                    messages.LOG_LOAD_MAIN_SECURITY_FILE, path=main_security_file_on_source
                ):
                    try:
                        main_security_file_contents = runner_source.get_file_contents(main_security_file_on_source)
                        main_security_file_tree = ElementTree.fromstring(main_security_file_contents)
                    except Exception as e:
                        logger.fwarn(
                            messages.LOG_FAILED_TO_READ_MAIN_SECURITY_FILE_ON_SOURCE,
                            path=main_security_file_on_source, reason=e
                        )

            if main_security_file_tree is None and len(subdomain_security_file_trees) == 0:
                return

            if main_security_file_tree is None:
                main_security_file_tree = elem('Entries')

            if len(subdomain_security_file_trees) > 0:
                with logger.debug_level_context(messages.LOG_MERGE_SUBDOMAIN_SECURITY_FILES):
                    for subdomain_security_file_tree in subdomain_security_file_trees:
                        for entry_node in subdomain_security_file_tree.findall('Entry'):
                            main_security_file_tree.append(entry_node)

            with logger.debug_level_context(messages.LOG_MAIN_SECURITY_FILE_CONVERSIONS):
                self._security_xml_fix_all(main_security_file_tree, subscription, iis_app_user)

            with logger.debug_level_context(messages.LOG_WRITE_MAIN_SECURITY_FILE, path=main_security_file_on_target):
                converted_main_security_file_contents = xml_to_string_pretty(main_security_file_tree)
                logger.fdebug(messages.LOG_SECURITY_FILE_CONTENTS, contents=converted_main_security_file_contents)
                runner_target.upload_file_content(
                    main_security_file_on_target, converted_main_security_file_contents
                )

    def _transfer_webuser_security_files(self, subscription, iis_app_user):
        """
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :type iis_app_user: str | unicode | None
        """
        source_server = subscription.web_source_server
        target_server = subscription.web_target_server

        with target_server.runner() as runner_target, source_server.runner() as runner_source:
            assert isinstance(runner_source, BaseRunner)
            assert isinstance(runner_target, BaseRunner)

            for web_user_security_file in list_webuser_security_files(subscription):
                security_file_on_source = self._get_source_security_file_location(subscription, web_user_security_file)
                security_file_on_target = self._get_target_security_file_location(subscription, web_user_security_file)

                if not runner_source.file_exists(security_file_on_source):
                    continue

                with logger.debug_level_context(
                    messages.LOG_LOAD_WEBUSER_SECURITY_FILE, path=security_file_on_source
                ):
                    try:
                        security_file_contents = runner_source.get_file_contents(security_file_on_source)
                        security_file_tree = ElementTree.fromstring(security_file_contents)
                    except Exception as e:
                        logger.fwarn(
                            messages.LOG_FAILED_TO_READ_WEBUSER_SECURITY_FILE_ON_SOURCE,
                            path=security_file_on_source, reason=e
                        )
                        continue

                with logger.debug_level_context(messages.LOG_WEBUSER_SECURITY_FILE_CONVERSIONS):
                    self._security_xml_fix_all(security_file_tree, subscription, iis_app_user)

                with logger.debug_level_context(messages.LOG_WRITE_WEBUSER_SECURITY_FILE, path=security_file_on_target):
                    converted_security_file_contents = xml_to_string_pretty(security_file_tree)
                    logger.fdebug(messages.LOG_SECURITY_FILE_CONTENTS, contents=converted_security_file_contents)
                    runner_target.upload_file_content(
                        security_file_on_target, converted_security_file_contents
                    )

    @staticmethod
    def _get_source_security_file_location(subscription, security_file):
        """
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :type security_file: str | unicode
        :rtype: str | unicode
        """
        source_server = subscription.web_source_server
        source_plesk_version = source_server.plesk_version

        if source_plesk_version >= (11, 5):
            return windows_utils.path_join(
                source_server.vhosts_dir, subscription.name_idn, '.plesk', security_file
            )
        else:
            return windows_utils.path_join(
                source_server.vhosts_dir, subscription.name_idn, security_file
            )

    @staticmethod
    def _get_target_security_file_location(subscription, security_file):
        """
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :type security_file: str | unicode
        :rtype: str | unicode
        """
        target_server = subscription.web_target_server
        return windows_utils.path_join(
            target_server.vhosts_dir, subscription.name_idn, '.plesk', security_file
        )

    def _security_xml_fix_all(self, tree, subscription, iis_app_user):
        """
        :type tree: xml.etree.ElementTree.Element
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :type iis_app_user: str | unicode | None
        :return:
        """
        self._security_xml_fix_account_name(tree, subscription)
        self._security_xml_fix_win2003_without_asp_net(tree, subscription)
        self._security_xml_fix_iis_app_user(tree, iis_app_user)

    @staticmethod
    def _security_xml_fix_account_name(tree, subscription):
        """
        :type tree: xml.etree.ElementTree.Element
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: None
        """
        source_user_name = subscription.raw_dump.get_phosting_sysuser_name()
        target_user_name = subscription.target_sysuser_name

        if source_user_name == target_user_name or target_user_name is None:
            return

        with logger.debug_level_context(
            messages.LOG_SECURITY_FILE_FIX_SYSUSER_NAME,
            source_user_name=source_user_name, target_user_name=target_user_name
        ):
            for entry in tree.findall('Entry'):
                if entry.attrib['Account'] == source_user_name:
                    entry.attrib['Account'] = target_user_name

    @staticmethod
    def _security_xml_fix_win2003_without_asp_net(tree, subscription):
        """Fix issue when migrating from Windows 2003 with IIS 6 for domains with ASP.NET disabled on Plesk <= 9.5.3.
        For such domains if you won't remove NoAccess permission from .Security file, sites will response with 500
        error because of insufficient permissions to access httpdocs directory.

        :type tree: xml.etree.ElementTree.Element
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        :rtype: None
        """
        if subscription.web_source_server.plesk_version > (9, 5, 2):
            return

        to_remove = []

        with logger.debug_level_context(messages.LOG_SECURITY_FILE_FIX_NO_ASP_NET):
            for entry in tree.findall('Entry'):
                if (
                    # - IWPC_* for domains that have dedicated IIS pool
                    # enabled at client or reseller level
                    # - IWPD_* for domains that have dedicated IIS pool
                    # enabled at domain level
                    re.match('^(IWAM|IWPC|IWPD)_', entry.attrib['Account']) and
                    entry.attrib['AccessMask'] == 'NoAccess'
                ):
                    to_remove.append(entry)

            for entry in to_remove:
                tree.remove(entry)

    @staticmethod
    def _security_xml_fix_iis_app_user(tree, iis_app_user):
        """
        :type tree: xml.etree.ElementTree.Element
        :type iis_app_user: str | unicode | None
        :rtype: None
        """
        if iis_app_user is None:
            return

        with logger.debug_level_context(
            messages.LOG_SECURITY_FILE_FIX_APPLICATION_POOL_USER, iis_app_user=iis_app_user
        ):
            for entry in tree.findall('Entry'):
                if re.match('^IWPD_\d+\(.*\)$', entry.attrib['Account']):
                    entry.attrib['Account'] = iis_app_user
