from parallels.core import messages, MigrationStopError
from parallels.core.actions.base.common_action import CommonAction
from parallels.core.logging import get_logger
from parallels.core.migrator_config import read_mssql_copy_method, MSSQLCopyMethod
from parallels.core.runners.exceptions.connection_check import ConnectionCheckError
from parallels.core.utils.common import safe_format, is_empty_iterator
from parallels.core.utils.database_server_type import DatabaseServerType
from parallels.core.utils.migrator_utils import is_running_from_cli, get_option_value_safe
from parallels.core.utils.windows_agent_remote_object import RPCAgentConnectionError, RPCAgentBuilder

logger = get_logger(__name__)


class CheckRemoteMSSQLBlocking(CommonAction):
    """Check connections to remote MSSQL servers with RPC agent, which are marked to be migrated with native MSSQL dump.

    This action is applicable only to migration from CLI, when running migration itself ('transfer-accounts'), not
    on any kind of pre-migration checks. It is necessary for those who have not performed pre-migration checks
    by 'check' command.

    For each remote MSSQL server we strongly recommend to have physical access, which is required
    to perform database migration via MSSQL native dump. Otherwise, when there is no physical access,
    migrator will try to copy database with text SMO dump, which is less reliable. To get physical access
    we use RPC agent installed on remote MSSQL server.

    In this action, we try to connect to RPC agent on each of remote MSSQL server
    that we are going to migrate with native MSSQL dump. All servers that were previously reported as failed by
    'check' command will not be checked here.

    If connection has failed - migration is stopped.
    """

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

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

    def get_failure_message(self, global_context):
        """
        :type global_context: parallels.plesk.source.helm3.global_context.Helm3GlobalMigrationContext
        """
        return messages.ACTION_CHECK_REMOTE_MSSQL_BLOCKING_FAILURE

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

        :rtype: bool
        """
        return False

    def filter_action(self, global_context):
        return not is_empty_iterator(self._iter_remote_mssql_servers(global_context))

    def run(self, global_context):
        """
        :type global_context: parallels.plesk.source.helm3.global_context.Helm3GlobalMigrationContext
        """
        if not is_running_from_cli():
            # Action is applicable to CLI only. The difference with GUI that in GUI you always see results of
            # pre-migration reports, while in CLI running pre-migrations checks is optional. So, here we force
            # execution of checks when migrating with CLI.
            return

        if get_option_value_safe(global_context.options, 'skip_remote_mssql_servers_check', False):
            # Check is explicitly disabled by user
            return

        if read_mssql_copy_method(global_context.config) == MSSQLCopyMethod.TEXT:
            return

        failed_servers = []
        for server in self._iter_remote_mssql_servers(global_context):
            try:
                with server.physical_server.runner() as runner:
                    runner.check(server.host())
            except (ConnectionCheckError, RPCAgentConnectionError):
                logger.exception()
                failed_servers.append((server.panel_server.ip(), server.host()))
            except Exception as e:
                logger.exception()
                logger.fwarn(messages.FAILED_TO_CHECK_MSSQL_SERVER, reason=e, host=server.host())

        if len(failed_servers) > 0:
            rpc_agent_path = RPCAgentBuilder().build()
            raise MigrationStopError(safe_format(
                messages.REMOTE_MSSQL_SERVER_BLOCKING_WARNING_CLI,
                servers='\n'.join(
                    "- %s" % safe_format(
                        messages.REMOTE_MSSQL_SERVER_DESCRIPTION,
                        mssql_server=mssql_server,
                        panel_server=panel_server
                    )
                    for panel_server, mssql_server in failed_servers
                ),
                rpc_agent_path=rpc_agent_path,
            ))

    @staticmethod
    def _iter_remote_mssql_servers(global_context):
        database_servers = []
        for database in global_context.migrator._list_databases_to_copy():
            for database_server in (database.source_database_server, database.target_database_server):
                if (
                    database_server in database_servers or
                    database_server.type() != DatabaseServerType.MSSQL or
                    database_server.is_local() or
                    # marked as using text dump when running 'check' command
                    global_context.mssql_servers_without_physical_access.has_server(
                        database_server.panel_server.ip(), database_server.host()
                    )
                ):
                    continue
                database_servers.append(database_server)
                yield database_server
