from parallels.core import messages
from parallels.core.actions.base.common_action import CommonAction
from parallels.core.logging import get_logger
from parallels.core.migrator_config import MSSQLCopyMethod
from parallels.core.migrator_config import read_mssql_copy_method
from parallels.core.reports.model.affected_object import AffectedObject
from parallels.core.reports.model.issue import Issue
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
from parallels.core.utils.windows_agent_remote_object import RPCAgentConnectionError, RPCAgentBuilder

logger = get_logger(__name__)


class CheckRemoteMSSQL(CommonAction):
    """Check connections to remote MSSQL servers with RPC agent

    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 each of remote MSSQL server with RPC agent.
    If connection has failed - we write this server to a special file.
    Then, from GUI we read that file and display a warning to install RPC agent and check firewall rules.
    """

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

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

    def get_failure_message(self, global_context):
        """
        :type global_context: parallels.plesk.source.helm3.global_context.Helm3GlobalMigrationContext
        """
        return messages.ACTION_CHECK_REMOTE_MSSQL_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 read_mssql_copy_method(global_context.config) == MSSQLCopyMethod.TEXT:
            return

        # First, consider we have access to all the servers
        global_context.mssql_servers_without_physical_access.write_servers([])

        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())

        global_context.mssql_servers_without_physical_access.write_servers(failed_servers)

        if len(failed_servers) > 0:
            if is_running_from_cli():
                rpc_agent_path = RPCAgentBuilder().build()
                message = safe_format(
                    messages.REMOTE_MSSQL_SERVER_WARNING_CLI,
                    rpc_agent_path=rpc_agent_path,
                )
            else:
                message = messages.REMOTE_MSSQL_SERVER_WARNING_GUI

            affected_objects = [
                AffectedObject(
                    AffectedObject.TYPE_MSSQL_SERVER,
                    safe_format(
                        messages.REMOTE_MSSQL_SERVER_DESCRIPTION,
                        mssql_server=mssql_server,
                        panel_server=panel_server
                    )
                )
                for panel_server, mssql_server in failed_servers
            ]

            global_context.pre_check_report.add_issue(
                'remote_mssql_server_unavailable', Issue.SEVERITY_WARNING, message,
                affected_objects=affected_objects, solution_download_rpc_agent=True
            )

    @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()
                ):
                    continue
                database_servers.append(database_server)
                yield database_server
