import os
import posixpath
import ntpath

from parallels.core.logging import get_logger
from parallels.core.runners.base import BaseRunner
from parallels.core.runners.entities import ExecutionOptions
from parallels.core.utils.common import format_multiline_list
from parallels.core.utils.restore_hosting_utils import restore_hosting_settings_lock
from parallels.plesk.source.plesk import messages
from parallels.core.actions.base.subscription_action import SubscriptionAction
from parallels.core.utils.windows_utils import path_join as windows_path_join

logger = get_logger(__name__)


class Transfer(SubscriptionAction):
    def get_description(self):
        return messages.ACTION_TRANSFER_WPB_DESCRIPTION

    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.ACTION_TRANSFER_WPB_FAILURE % subscription.name

    def filter_subscription(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        return len(self._list_wpb_sites(global_context, subscription)) > 0

    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 run(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        with restore_hosting_settings_lock:
            self._run_plain(global_context, subscription)

    def _run_plain(self, global_context, subscription):
        """
        :type global_context: parallels.core.global_context.GlobalMigrationContext
        :type subscription: parallels.core.migrated_subscription.MigratedSubscription
        """
        logger.debug(messages.DEBUG_COLLECTING_INFO_ABOUT_WPB_SITES)
        wpb_sites = self._list_wpb_sites(global_context, subscription)

        with subscription.panel_target_server.runner() as runner_target:
            assert isinstance(runner_target, BaseRunner)
            source_server = subscription.web_source_server
            is_windows = source_server.is_windows()
            target_plesk_dir = subscription.panel_target_server.plesk_dir

            if is_windows:
                bru_php = u"%s\\sb\\utils\\bru.php" % (target_plesk_dir,)
            else:
                bru_php = u'/usr/local/sb/utils/bru.php'

            with source_server.runner() as runner_source:
                assert isinstance(runner_source, BaseRunner)
                if len(wpb_sites) > 0 and not runner_target.file_exists(bru_php):
                    error_message = messages.WPB_NOT_INSTALLED_ERROR.format(
                        sites=format_multiline_list([
                            messages.WPB_SITE_DESCRIPTION.format(domain_name=domain_name, site_id=wpb_site_id)
                            for domain_name, wpb_site_id in wpb_sites
                        ])
                    )

                    global_context.safe.fail_subscription(
                        subscription.name, error_message=error_message,
                        solution=messages.WPB_NOT_INSTALLED_SOLUTION, is_critical=False
                    )
                    return

                for domain_name, sitebuilder_site_id in wpb_sites:
                    base_filename = u'wpb_%s.zip' % domain_name
                    source_filename = source_server.get_session_file_path(base_filename)
                    local_filename = global_context.migrator_server.get_session_file_path(base_filename)
                    target_filename = subscription.panel_target_server.get_session_file_path(base_filename)

                    logger.debug(messages.LOG_BACKUP_WPB_SITE_ON_SOURCE)
                    if is_windows:
                        runner_source.execute_command(
                            '{php} -dauto_prepend_file="" {bru} backup '
                            '--target=site --uuid={site_id} --file={filename}',
                            dict(
                                php=ntpath.join(source_server.plesk_dir, u'admin\\bin\\php'),
                                bru=ntpath.join(source_server.plesk_dir, u'sb\\utils\\bru.php'),
                                site_id=sitebuilder_site_id, filename=source_filename
                            )
                        )
                    else:
                        runner_source.execute_command(
                            u'{sw_engine_pleskrun} /usr/local/sb/utils/bru.php backup '
                            u'--target=site --uuid={site_id} --file={filename}',
                            dict(
                                sw_engine_pleskrun=posixpath.join(source_server.plesk_dir, 'bin/sw-engine-pleskrun'),
                                site_id=sitebuilder_site_id,
                                filename=source_filename
                            )
                        )

                    logger.debug(messages.LOG_DOWNLOAD_BACKUP_FILE_TO_LOCAL_SERVER)
                    runner_source.get_file(source_filename, local_filename)
                    runner_source.remove_file(source_filename)  # remove immediately as all WPB sites could take a lot of disk space
                    logger.debug(messages.LOG_UPLOAD_BACKUP_FILE_TO_TARGET)
                    runner_target.upload_file(local_filename, target_filename)
                    os.remove(local_filename)

                    logger.debug(messages.LOG_RESTORE_WPB_SITE_ON_TARGET)
                    if subscription.panel_target_server.is_windows():
                        runner_target.execute_command(
                            '{php} -dauto_prepend_file="" {bru} restore '
                            '--target=site --uuid={site_id} --file={filename} --use_defaults',
                            dict(
                                php=ntpath.join(target_plesk_dir, u'admin\\bin\\php'),
                                bru=bru_php,
                                site_id=sitebuilder_site_id, filename=target_filename
                            )
                        )
                        runner_target.remove_file(target_filename)
                        plesk_bin_domain = windows_path_join(target_plesk_dir, "bin/domain")
                    else:
                        runner_target.execute_command(
                            u'{sw_engine_pleskrun} /usr/local/sb/utils/bru.php restore '
                            u'--target=site --uuid={site_id} --file={filename} --use_defaults',
                            dict(
                                sw_engine_pleskrun=posixpath.join(target_plesk_dir, u'bin/sw-engine-pleskrun'),
                                site_id=sitebuilder_site_id,
                                filename=target_filename
                            )
                        )
                        runner_target.remove_file(target_filename)
                        plesk_bin_domain = "%s/bin/domain" % target_plesk_dir

                    logger.debug(messages.LOG_RESTORE_RELATION_PLESK_DOMAIN_TO_WPB_SITE)
                    runner_target.execute_command(
                        u'{plesk_bin_domain} --update {domain_name} -publish-sb-site true -sb-site-id {site_id}',
                        dict(plesk_bin_domain=plesk_bin_domain, domain_name=domain_name, site_id=sitebuilder_site_id),
                        ExecutionOptions(
                            environment_variables=dict(
                                # Necessary for Plesk Multi Server, which disallows execution of "domain" utility on
                                # service nodes when that environment variable is not set.
                                PLESK_MIGRATION_MODE='1',
                            )
                        )
                    )

    @staticmethod
    def _list_wpb_sites(global_context, subscription):
        """ Returns the following structure:
        wpb_sites = [
            (subscription_name, site_id),
            (subdomain1_name, site_id), (subdomain2_name, site_id), ...,
            (addondomain1_name, site_id), (addondomain2_name, site_id), ...
        ]
        where site_id is an id of WPB site for subscription/addon domain/subdomain
        the function guarantees that site_id is not None
        """

        safe = global_context.safe
        wpb_sites = []

        with safe.try_subscription(
            subscription.name,
            messages.FAILED_TO_READ_INFORMATION_ABOUT_WPB_SITES, is_critical=False
        ):
            for site in subscription.converted_dump.iter_domains():
                if site.sitebuilder_site_id is not None:
                    wpb_sites.append((site.name, site.sitebuilder_site_id))

        return wpb_sites
