import ntpath

from parallels.core.logging import get_logger
from parallels.core import messages
from parallels.core.utils.migrator_utils import ip_file_path
from parallels.core.utils.yaml_utils import read_yaml, write_yaml
from parallels.core.windows_rsync import RsyncInstaller, RsyncClient, RsyncServer, RsyncConfig, RsyncControl
from parallels.core.utils.common import generate_random_password
from parallels.core.utils.common.threading_utils import synchronized_by_args

logger = get_logger(__name__)


class RsyncPool(object):
    """
    :type _servers: dict [str, parallels.core.windows_rsync.RsyncServer]
    """
    def __init__(self, storage_path_prefix):
        self._storage_path_prefix = storage_path_prefix
        self._clients = {}
        self._servers = {}
        self._server_passwords = {}

    @synchronized_by_args
    def get(self, source, target, vhosts_dir=None, source_ip=None):
        """
        :rtype: parallels.core.windows_rsync.RsyncControl
        """
        if source is None and source_ip is None:
            raise Exception(messages.UNABLE_GET_RSYNC_SOURCE_SERVER_OBJECT)

        client = self._prepare_client(target)
        server = self._prepare_server(source, vhosts_dir) if source is not None else None

        #  update config with passed vhost_dir
        if server is not None and server.vhosts_dir != vhosts_dir:
            self._update_server_vhosts_dir(source, vhosts_dir)

        return RsyncControl(
            target=target,
            target_rsync_bin=ntpath.join(client.base_path, 'panel-migrator-rsync.exe'),
            source_ip=source.ip() if source is not None else source_ip,
            source_login=server.login if server is not None else None,
            source_password=server.password if server is not None else None,
            server=server
        )

    def stop_all(self):
        for ip in self._servers:
            self._servers[ip].stop()
        self._servers.clear()

    def stop(self, ip, is_terminated=False):
        """Remove rsync server with given ip from pool
        :type ip: str
        :param is_terminated: if rsync server process was already terminated, no necessary to call stop command,
        so pass True to just remove it from pool
        """
        if ip not in self._servers:
            return
        if not is_terminated:
            self._servers[ip].stop()
        del self._servers[ip]

    def _prepare_client(self, server):
        ip = server.ip()
        if ip not in self._clients:
            self._clients[ip] = RsyncClient(self._deploy(server))
        return self._clients[ip]

    def _prepare_server(self, server, vhosts_dir):
        ip = server.ip()
        if ip not in self._servers:
            rsync_server = RsyncServer(self._deploy(server), server)

            if ip not in self._server_passwords:
                self._server_passwords[ip] = generate_random_password()

            rsync_config = RsyncConfig(
                vhosts_dir=vhosts_dir,
                migrator_dir=server.get_session_dir_path(),
                user='admin',
                password=self._server_passwords[ip]
            )

            rsync_server.configure(rsync_config)
            rsync_server.restart()

            self._servers[ip] = rsync_server

        return self._servers[ip]

    def _deploy(self, server):
        ip = server.ip()
        storage_path = self._storage_path_prefix + ip_file_path(ip)
        base_path = read_yaml(storage_path, True)

        if base_path is not None:
            with server.runner() as runner:
                if not runner.file_exists(base_path):
                    # If rsync was remove from the server - deploy it again
                    base_path = None

        if base_path is None:
            logger.debug(messages.DEBUG_INSTALL_RSYNC, ip)
            rsync_installer = RsyncInstaller(server)
            base_path = rsync_installer.install()
            write_yaml(storage_path, base_path)
            logger.debug(messages.DEBUG_RSYNC_INSTALLED_TO % (ip, base_path))

        return base_path

    def _update_server_vhosts_dir(self, server, vhosts_dir):
        self._servers[server.ip()].set_vhosts_dir(vhosts_dir)
