import parallels.plesk.config as plesk_config
from parallels.core import MigrationError, MigrationNoContextError
from parallels.core.connections.checker import ConnectionChecker
from parallels.core.connections.checker import PleskConnectionCheckError
from parallels.core.connections.connections import Connections
from parallels.core.logging import get_logger
from parallels.core.migrator_config import read_source_config
from parallels.core.utils.common import merge_dicts, cached, unused
from parallels.core.utils.migrator_utils import version_to_tuple
from parallels.core.utils.plesk_utils import get_remote_smarter_mail_ip
from parallels.plesk.source.plesk import messages
from parallels.plesk.source.plesk.connections.source_remote_smarter_mail_server import \
    PleskSourceRemoteSmarterMailServer
from parallels.plesk.source.plesk.server import PleskSourceServer

logger = get_logger(__name__)


class PleskMigratorConnections(Connections):
    def __init__(self, global_context, target_panel):
        super(PleskMigratorConnections, self).__init__(global_context, target_panel)

        self._source_plesks = self._read_plesk_settings_from_config(
            global_context.config, self._get_source_config_section_names()
        )
        for settings in self.get_source_plesks().values():
            logger.info(messages.LOG_SOURCE_PLESK_HOST, settings.id, settings.ip)

    @cached
    def get_source_by_id(self, source_id):
        """Return source Plesk server with given identifier

        :type source_id: str
        :rtype: parallels.plesk.source.plesk.server.PleskSourceServer
        """
        source_configs = merge_dicts(self.get_source_plesks(), self.get_external_db_servers())
        return PleskSourceServer(source_id, source_configs[source_id], self._global_context.migrator_server)

    def get_stats_server(self):
        """Get source panel server used for statistics reporting (for example, source IP and OS version)

        Source panel could have multiple servers, but this function should return only one of them.
        If there are no source panel servers - return None
        """
        server_ids = self.get_source_plesks().keys()
        if len(server_ids) == 0:
            return None
        first_source_server_id = server_ids[0]
        first_source_server = self.get_source_node(first_source_server_id)
        return first_source_server

    def get_panel_version(self):
        """Get version of source panel
        """
        return self.get_stats_server().plesk_version

    def get_source_plesks(self):
        """Get information about source Plesk servers
        
        Returns dictionary {server_id: server_settings} with connection
        information.
        """
        return self._source_plesks

    def get_dns_servers(self):
        """Get information about source DNS servers
        
        Return dictionary {server_id: server_settings} with connection
        information for each DNS server.
        """
        # For Plesk, DNS servers works on the same server
        return self._source_plesks

    @cached
    def get_remote_smarter_mail_server(self, source_id):
        """Get object to work with remote SmarterMail server. If there is no remote SmarterMail - None is returned.

        :type source_id: str | unicode
        :rtype: parallels.plesk.source.plesk.connections.source_remote_smarter_mail_server.PleskSourceRemoteSmarterMailServer | None
        """
        remote_smarter_mail_ip = self.get_remote_smarter_mail_ip(source_id)
        if remote_smarter_mail_ip is not None:
            remote_smarter_mail_id = '%s-remote-smarter-mail' % source_id
            settings = read_source_config(
                self._config, remote_smarter_mail_id,
                section_optional=True,
                default_ip=remote_smarter_mail_ip,
                default_os='windows'
            )
            return PleskSourceRemoteSmarterMailServer(remote_smarter_mail_id, settings)
        else:
            return None

    @cached
    def get_remote_smarter_mail_ip(self, source_id):
        """Get IP address of remote SmarterMail server. If there is no remote SmarterMail - None is returned.

        :type source_id: str | unicode
        :rtype: str | unicode | None
        """
        return get_remote_smarter_mail_ip(self.get_source_by_id(source_id))

    def check_source_servers(self):
        """Verify remote shell, Plesk API connections to source servers."""
        super(PleskMigratorConnections, self).check_source_servers()
        self._check_connections_plesk_servers()
        self._check_source_version()

    def _check_connections_plesk_servers(self):
        """Check connections (SSH/Samba/Plesk API) to source Plesk servers"""

        check_list = []
        for source_id in self.get_source_plesks():
            check_list.append((
                self.get_source_node(source_id),
                ["ssh_auth", "windows_auth"]
            ))
        self._check_connection_for_servers(check_list)

    @staticmethod
    def _check_connection_for_servers(check_list):
        try:
            ConnectionChecker().check(check_list) 
        except PleskConnectionCheckError:
            # just raise, do not any other text
            raise
        except MigrationNoContextError:
            raise
        except Exception as e:
            logger.exception()
            raise MigrationError(messages.CHECKING_CONNECTIONS_FAILURE % e)

    def _check_source_version(self):
        """Check version of Plesk on source Plesk servers"""
        try:
            for source_id in self.get_source_plesks():
                source_server = self.get_source_node(source_id)

                try:
                    # Try to detect source Plesk directory, and if detection failed for any reason
                    # then consider that Plesk is not installed on the server
                    unused(source_server.plesk_dir)
                except MigrationNoContextError:
                    raise
                except Exception:
                    logger.exception()

                    if source_server.is_windows():
                        error_message = messages.SOURCE_PLESK_NOT_INSTALLED_WINDOWS
                    else:
                        error_message = messages.SOURCE_PLESK_NOT_INSTALLED_LINUX
                    raise MigrationNoContextError(error_message)

                if source_server.plesk_version >= version_to_tuple(b'18.1'):
                    raise MigrationNoContextError(
                        messages.SOURCE_PLESK_VERSION_IS_NOT_SUPPORTED % source_server.plesk_version_str
                    )
        except MigrationError:
            raise
        except Exception:
            logger.exception()
            logger.error(messages.INTERNAL_FAILURE_UNABLE_TO_DETECT_SOURCE_PANEL_VERSION)

    @staticmethod
    def _read_plesk_settings_from_config(config, section_names):
        settings = {}
        for section_name in section_names:
            settings[section_name] = plesk_config.read_source_plesk_settings(config, section_name)
        return settings
