from contextlib import contextmanager
import ntpath
import posixpath

from parallels.core.registry import Registry
from parallels.core.runners.entities import ExecutionOptions
from parallels.core.runners.windows.agent import WindowsAgentRunner
from parallels.core import messages
from parallels.core.connections.source import Source
from parallels.core.connections.unix_server import UnixServer
from parallels.core.connections.ssh.connection_pool import SSHConnectionPool
from parallels.core.utils.common import cached, default
from parallels.core.utils import session_dir
from parallels.core.utils import migrator_utils


class SourceServer(Source, UnixServer):
    """This is a class to interact with source server and get various common information about it
    """

    def __init__(self, source_id, config, migrator_server=None):
        """Constructor of SourceServer object.
        
        Args:
            node_id: server's section name specified in config.ini
            node_settings: server properties specified in config.ini
            migrator_server: server where migration tools are installed,
                do not pass if you don't need local execution of commands
                on Windows server

        :type source_id: str
        :type config: parallels.core.migrator_config.PhysicalServerConfig
        :type migrator_server: parallels.core.connections.migrator_server.MigratorServer
        """
        migrator_server = default(migrator_server, Registry.get_instance().get_context().migrator_server)

        Source.__init__(self, source_id, config, migrator_server)
        UnixServer.__init__(self)
        if not self.config.is_windows:
            self._ssh = SSHConnectionPool.get_instance().get(
                self.node_settings, self
            )

    @property
    def config(self):
        """Return source server config

        :rtype: parallels.core.migrator_config.PhysicalServerConfig
        """
        return super(SourceServer, self).config

    def description(self):
        """Return brief server description which should help end customer
        to identify server and which should be used in all messages

        :rtype: str | unicode
        """
        return messages.SOURCE_SERVER_DESCRIPTION % (self.node_id, self.ip())

    def is_windows(self):
        """Return True if source server is Windows server, 
        False if source server is Unix server

        :rtype: bool
        """
        return self.node_settings.is_windows

    def ip(self):
        """Return main IP address of the server

        :rtype: str | unicode
        """
        return self.node_settings.ip

    def user(self):
        """Return user we use to communicate with the server

        :rtype: str | unicode
        """
        if not self.is_windows():
            return self.node_settings.ssh_auth.username
        else:
            return self.node_settings.windows_auth.username

    def ssh_key(self):
        """Return path to SSH key used to connect to that server, if any

        :rtype: str | unicode | None
        """
        if self.is_windows():
            return None
        if not hasattr(self.node_settings, 'ssh_auth'):
            return None

        return self.node_settings.ssh_auth.key_filename

    @property
    def mail_settings(self):
        """Return copy mail content configuration"""
        return self.node_settings.mail_settings

    def get_database_server_password(self, database_server):
        return database_server.model.password

    def get_path_to_mysqldump(self):
        """Get path to mysqldump.exe on that source server

        :rtype: str
        """
        raise NotImplementedError()

    def get_path_to_mysql(self):
        """Get path to mysql.exe on that source server

        :rtype: str
        """
        return 'mysql'

    def mysql_use_skip_secure_auth(self):
        """Whether to pass --skip-secure-auth flag to MySQL client

        This flag is necessary when client version is greater than server version.
        Do not pass it when it is not necessary, or when client is of the same version as server.
        """
        return False

    @property
    @cached
    def rsync_bin(self):
        with self.runner() as runner:
            return migrator_utils.detect_rsync_path(
                runner, self.description()
            )

    @property
    def python_bin(self):
        if self.is_windows():
            # use Python from panel migrator agent
            return ntpath.join(self.node_settings.agent_settings.agent_path, 'python\panel-migrator-python.exe')
        else:
            # use default OS Python
            return 'python'

    @property
    def vhosts_dir(self):
        """Directory where virtual hosts are located on the source server

        If we don't know it for any reasons, this function returns None.

        :rtype: str | unicode | None
        """
        return None

    @contextmanager
    def runner(self):
        """Get runner object to execute commands on the source server"""
        if self.is_windows():
            yield self._get_windows_runner()
        else:
            with self._ssh.runner() as runner:
                yield runner

    @contextmanager
    def proxy_runner(self, host):
        """Get runner that executes command on remote host through this source server

        Implemented only for Windows with RPC agent proxy.

        :type host: str | unicode
        """
        if self.is_windows():
            yield WindowsAgentRunner(self.node_settings, proxy_to=host)
        else:
            raise NotImplementedError()

    def _create_session_dir(self):
        if self.config.is_windows:
            return session_dir.WindowsSessionDir(self.runner, self.node_settings.session_dir)
        else:
            return session_dir.UnixSessionDir(self.runner, self.node_settings.session_dir)

    @cached
    def _get_windows_runner(self):
        return WindowsAgentRunner(self.node_settings, self.description())

    def __repr__(self):
        return 'SourceServer(%r)' % self.node_id


    @property
    @cached
    def nginx_enabled(self):
        if self.is_windows():
            raise NotImplementedError()
        else:
            with self.runner() as runner:
                status = runner.execute_command(
                    u'{admin_sbin} --status',
                    dict(admin_sbin=posixpath.join(self.plesk_dir, u'admin/sbin/nginxmng')),
                    ExecutionOptions(
                        log_output=False
                    )
                ).stdout.rstrip('\n')
            return status == "Enabled"


    @cached
    def get_chroot_directories(self):
        """Get list of chroot environment directories"""
        if self.is_windows():
            raise NotImplementedError()
        else:
            chroot_dir = posixpath.join(self.vhosts_dir, 'chroot')
            with self.runner() as runner:
                dirs = runner.execute_command(
                    'ls ' + chroot_dir
                ).stdout
            dirs_array = dirs.split()
            if isinstance(dirs_array, list) and len(dirs_array) > 0:
                return dirs_array
        return []


class SingleServerPanelSourceServer(SourceServer):
    """Source server of a single-server control panel.
    
    This represents a server, on which, from migrator point of view, all
    hosting resources are stored on local file system. This can be either a single
    server hosting panel (Plesk, Confixx), or a panel, for which each server is
    configured in a separate section of 'config.ini' file (such as Plesk Expand).
    """

    def __init__(self, node_id, node_settings, migrator_server):
        super(SingleServerPanelSourceServer, self).__init__(
            node_id, node_settings, migrator_server
        )

    def get_unix_vhost_dirs(self, runner, domain_name):
        """Return a list of directories with domain hosting content."""
        raise NotImplementedError()

    def get_unix_domain_mail_dirs(self, runner, domain_name):
        """Return a list of directories with mail content."""
        raise NotImplementedError()
