from contextlib import contextmanager

from parallels.core.logging import get_logger
from parallels.core.registry import Registry
from parallels.core.utils.common_constants import WINDOWS_SOURCE_DEFAULT_SESSION_DIR
from parallels.core.utils.common import cached
from parallels.core import MigrationError
from parallels.core.connections.connections import Connections
from parallels.core.utils.config_utils import ConfigSection, get_sections_list
from parallels.core.migrator_config import read_copy_mail_content_settings, SourceWindowsServerConfig
from parallels.core.migrator_config import read_windows_agent_settings
from parallels.core.utils.common import obj
from parallels.core.utils.common import find_first
from parallels.plesk.source.helm3 import messages
from parallels.plesk.source.helm3.server import Helm3SourceServer

logger = get_logger(__name__)


class Helm3MigratorConnections(Connections):
    """Connections for migration from source Helm 3 to some target"""
    def __init__(self, global_context, target_panel, migrator_server):
        super(Helm3MigratorConnections, self).__init__(global_context, target_panel)
        self.helm3 = Helm3Connections(global_context.config, migrator_server)

    @cached
    def get_source_config_by_id(self, source_id):
        """Return configuration of source with given identifier

        :type source_id: str
        :rtype: parallels.plesk.source.helm3.connections.SourceWindowsServerConfig
        """
        return self.helm3.get_main_source_server().node_settings

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

        :type source_id: str
        :rtype: parallels.plesk.source.helm3.server.Helm3SourceServer
        """
        source_config = self.get_source_config_by_id(source_id)
        return Helm3SourceServer(source_id, source_config, 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
        """
        return self.helm3.get_main_source_server()

    def _get_source_config_section_names(self):
        """Get names of sections describing sources of data

        :rtype: list[str | unicode]
        """
        return ['helm3']


class Helm3Connections(object):
    """Connections to Helm 3 servers"""

    def __init__(self, config, migrator_server):
        """Class constructor

        :type config: ConfigParser.RawConfigParser
        :type migrator_server: parallels.core.connections.migrator_server.MigratorServer
        """
        self.source_servers = []
        source_server_settings = self._read_source_server_settings(config)
        for server_id, settings in source_server_settings.items():
            self.source_servers.append(
                Helm3SourceServer(server_id, settings, migrator_server)
            )

        self._config = config

    @contextmanager
    def runner(self):
        """Get runner of main Helm node"""

        with self.get_main_source_server().runner() as runner:
            yield runner

    def get_source_server_by_ip(self, ip):
        """Get Helm source server by IP address

        :type ip: str | unicode
        :rtype: parallels.plesk.source.helm3.server.Helm3SourceServer
        """

        server = find_first(self.source_servers, lambda s: s.ip() == ip)
        if server is not None:
            return server
        else:
            return self._create_source_server_by_ip(ip)

    def get_source_server_by_id(self, server_id):
        """Get Helm source server by ID from configuration file

        :type server_id: str | unicode
        :rtype: parallels.plesk.source.helm3.server.Helm3SourceServer
        """

        server = find_first(self.source_servers, lambda s: s.node_id == server_id)
        if server is not None:
            return server
        else:
            raise MigrationError(messages.CANNOT_FIND_SOURCE_SERVER_BY_ID % server_id)

    def get_main_source_server(self):
        """Get Helm main source server

        :rtype: parallels.plesk.source.helm3.server.Helm3SourceServer
        """

        return self.get_source_server_by_id('helm3')

    @cached
    def _create_source_server_by_ip(self, ip):
        """Create object for source Helm server, that is not specified in configuration file

        If server is not specified in configuration file, consider it has the same settings
        as the main server. That is necessary to simplify configuration, but it has a strong
        requirement:
        1) Either all Windows credentials for all Helm servers should be the same.
        2) Or you should deploy Windows RPC agent manually to all Helm servers.

        :type ip: str | unicode
        :rtype: parallels.plesk.source.helm3.server.Helm3SourceServer
        """
        server_id = "helm_source_%s" % ip.replace('.', '_')
        helm3_section = ConfigSection(self._config, 'helm3')
        mail_settings = read_copy_mail_content_settings(
            helm3_section, is_windows=True
        )
        session_dir = helm3_section.get('session-dir', WINDOWS_SOURCE_DEFAULT_SESSION_DIR)

        settings = SourceWindowsServerConfig(
            config_section=helm3_section,
            server_id=server_id,
            ip=ip,
            windows_auth=obj(
                username=helm3_section.get('windows-username', 'HELM_ADMIN'),
                password=helm3_section.get_password('windows-password')
            ),
            session_dir=session_dir,
            mail_settings=mail_settings,
            agent_settings=read_windows_agent_settings(
                self._config, 'helm3', session_dir
            )
        )

        migrator_server = Registry.get_instance().get_context().migrator_server

        return Helm3SourceServer(server_id, settings, migrator_server)

    @staticmethod
    def _read_source_server_settings(config):
        """Read configuration of Helm servers that are specified in configuration file

        :rtype: dict[str, parallels.plesk.source.helm3.connections.SourceWindowsServerConfig]
        """

        source_helm_servers_auth = {}

        # retrieve names of configuration file sections describing sources of data
        if config.has_option('GLOBAL', 'sources'):
            source_sections = get_sections_list(config, 'GLOBAL', 'sources')
        else:
            # backward compatible check of previous option name (was changed in scope of PMT-2798)
            source_sections = get_sections_list(config, 'GLOBAL', 'source-servers')

        helm3_section = ConfigSection(config, 'helm3')

        for section_name in set(source_sections) | {'helm3'}:
            section = ConfigSection(config, section_name)
            mail_settings = read_copy_mail_content_settings(
                section, is_windows=True
            )
            session_dir = section.get('session-dir', WINDOWS_SOURCE_DEFAULT_SESSION_DIR)
            source_helm_servers_auth[section_name] = SourceWindowsServerConfig(
                config_section=section,
                server_id=section_name,
                ip=section['ip'],
                windows_auth=obj(
                    username=section.get('windows-username', helm3_section.get('windows-username', 'HELM_ADMIN')),
                    password=section.get_password('windows-password', helm3_section.get_password('windows-password'))
                ),
                session_dir=session_dir,
                mail_settings=mail_settings,
                agent_settings=read_windows_agent_settings(
                    config, section_name, session_dir
                )
            )

        return source_helm_servers_auth
