import ntpath
import os
from contextlib import contextmanager

from parallels.core.connections.hosting_server import HostingServer
from parallels.core.connections.plesk_server import PleskServer
from parallels.core.connections.ssh.connection_pool import SSHConnectionPool
from parallels.core.connections.target_servers import TargetServer
from parallels.core.logging import get_logger
from parallels.core.migrator_config import PhysicalWindowsServerConfig
from parallels.core.runners.unix.local import LocalUnixRunner
from parallels.core.runners.windows.agent import WindowsAgentRunner
from parallels.core.runners.windows.local import LocalWindowsRunner
from parallels.core.utils.common import cached, is_empty
from parallels.core.utils.config_utils import Auth
from parallels.core.utils.mysql import mysql_query_local, mysql_query_with_agent_unix
from parallels.core.utils.plesk_cli_runner import PleskCLIRunnerLocalCLIGate, CLIGateAuthPassword, PleskCLIRunnerCLI, \
    CLIGateAuthSecretKey
from parallels.core.utils.plesk_utils import get_plesk_ips_with_cli, get_plesk_db_connection_args
from parallels.core.utils.windows_utils import path_join as windows_path_join
from parallels.plesk import messages

logger = get_logger(__name__)


class PleskTargetServer(TargetServer, PleskServer):
    def __init__(self, conn, settings):
        self._settings = settings
        self.conn = conn
        TargetServer.__init__(self)
        if not self._settings.is_windows and not self._settings.is_local:
            self._ssh = SSHConnectionPool.get_instance().get(self._settings, self)

    @contextmanager
    def runner(self):
        if not self._settings.is_windows:
            if self._settings.is_local:
                yield LocalUnixRunner()
            else:
                with self._ssh.runner() as runner:
                    yield runner
        else:
            if self._settings.is_local:
                yield LocalWindowsRunner.get_instance()
            else:
                yield WindowsAgentRunner(self._settings, self.description())

    def cli_runner(self):
        """Retrieve target Plesk command line utilities runner

        :rtype: parallels.core.utils.plesk_cli_runner.PleskCLIRunnerBase
        """
        if self.is_local() and self.is_windows():
            return self.cligate_runner()
        else:
            return PleskCLIRunnerCLI(self)

    def cligate_runner(self):
        """Get runner object to execute Plesk utilities with CLIGate"""
        if (
            (
                not self.is_windows() or
                # starting from Plesk 17.8, we don't know Plesk admin's panel password,
                # so we have to use secret keys to use CLIGate
                self.get_plesk_version() >= (17, 8)
            )
            and not is_empty(self.panel_secret_key_local)
        ):
            auth = CLIGateAuthSecretKey(self.panel_secret_key_local)
        else:
            # retrieve password of Plesk administrator from config
            plesk_admin_password = self._settings.plesk_api.auth.password
            if not plesk_admin_password:
                # if Plesk administrator password not specified in config, retrieve it from from server
                plesk_admin_password = self.panel_admin_password
            auth = CLIGateAuthPassword(plesk_admin_password)

        return PleskCLIRunnerLocalCLIGate(
            auth,
            self.is_windows(),
            self._settings.cligate_ip,
            self._settings.cligate_host,
            self._settings.plesk_api.protocol,
            self._settings.plesk_api.port
        )

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

        Implemented only for Windows, used only to copy MSSQL databases with native dumps.

        :type host: str | unicode
        """
        if self.is_windows():
            if not self.is_local():
                yield WindowsAgentRunner(self._settings, proxy_to=host)
            else:
                with self.runner() as runner:
                    remote_ip = runner.resolve(host)
                settings = PhysicalWindowsServerConfig(
                    server_id='proxy_%s' % host, ip=remote_ip,
                    session_dir=self._settings.remote_mssql_session_dir,
                    windows_auth=Auth(username=None, password=None),
                    # use the same settings for RPC agent, except for RPC agent directory
                    agent_settings=self._settings.agent_settings.clone(
                        agent_path=ntpath.join(self._settings.remote_mssql_session_dir, 'rpc-agent')
                    )
                )
                yield WindowsAgentRunner(settings, allow_autodeploy=False)
        else:
            raise NotImplementedError()

    def settings(self):
        return self.config

    @property
    def python_bin(self):
        if self.is_windows():
            python_bin = os.path.join(self.plesk_dir, 'python', 'python.exe')
            return python_bin
        else:
            # use default OS Python
            return 'python'

    @property
    def config(self):
        """Configuration of this target Plesk server

        :rtype: parallels.plesk.config.TargetPleskUnixConfig | parallels.plesk.config.TargetPleskWindowsConfig
        """
        return self._settings

    @cached
    def plesk_api(self):
        return self.conn.plesk_api()

    def get_hosting_server(self):
        return HostingServer(self)

    def is_windows(self):
        return self.conn.is_windows

    def is_local(self):
        return self._settings.is_local

    def description(self):
        return messages.TARGET_PLESK_SERVER_TITLE

    def ip(self):
        return self.conn.main_node_ip

    def ips(self):
        return {self.ip()}

    def get_path_to_mysql(self):
        if self.is_windows():
            return windows_path_join(self.plesk_dir, 'MySQL\\bin\\mysql')
        else:
            return 'mysql'

    @cached
    def get_all_ips(self):
        """Get list of all IP addresses of Plesk server

        Information includes IP address, its type and corresponding public IP address.
        """
        return get_plesk_ips_with_cli(self)

    def mysql_query(self, connection_args, query_str, query_args=None, is_result_as_dict=False):
        if self.is_local():
            return mysql_query_local(connection_args, query_str, query_args, is_result_as_dict)
        else:
            if self.is_windows():
                raise NotImplementedError()
            else:
                if not is_result_as_dict:
                    raise NotImplementedError()

                return mysql_query_with_agent_unix(self, connection_args, query_str, query_args)

    def query_panel_db(self, query_str, query_args=None):
        """Execute SQL query on Plesk database ('psa'), return results as list of dictionaries

        :type query_str: str | unicode
        :type query_args: dict | None
        :rtype: list[dict]
        """
        connection_args = get_plesk_db_connection_args(self)
        return self.mysql_query(connection_args, query_str, query_args, is_result_as_dict=True)

    def _create_session_dir(self):
        return self.conn.session_dir

    def __hash__(self):
        return hash(self.ip())

    def __eq__(self, another): 
        return isinstance(another, PleskTargetServer) and another.ip() == self.ip()
