from contextlib import contextmanager

from parallels.core import MigrationError
from parallels.core.logging import get_logger
from parallels.core.migrator_config import read_windows_physical_server_settings
from parallels.core.utils.plesk_utils import get_remote_smarter_mail_ip
from parallels.plesk import messages
import parallels.plesk.config as connections_config
from parallels.plesk.connections.target_remote_smarter_mail_server import PleskTargetRemoteSmarterMailServer
from parallels.plesk.utils.xml_rpc import plesk as plesk_api
from parallels.core.connections.checker import ConnectionChecker
from parallels.core.connections.target_connections import TargetConnections
from parallels.core.utils.session_dir import UnixSessionDir
from parallels.core.utils.session_dir import WindowsSessionDir
from parallels.plesk.utils.xml_rpc.plesk import get_max_supported_api_version
from parallels.plesk.utils.xml_rpc.plesk.operator import ServerOperator
from parallels.plesk.connections.target_server import PleskTargetServer
from parallels.core.utils.common import cached

logger = get_logger(__name__)


class PleskTargetConnections(TargetConnections):
    def __init__(self, config):
        self.config = config
        self._plesk_settings = connections_config.read_target_plesk_settings(config, 'plesk')

        logger.info(messages.LOG_TARGET_PLESK_SERVER.format(ip_address=self._plesk_settings.ip))
        if self._plesk_settings.is_windows:
            self._session_dir = WindowsSessionDir(self.main_node_runner, self._plesk_settings.session_dir)
        else:
            self._session_dir = UnixSessionDir(
                self.main_node_runner, self._plesk_settings.session_dir,
                secure_subdir=not self._plesk_settings.is_local
            )

        self._remote_smarter_mail_settings = read_windows_physical_server_settings(
            config, 'target-remote-smarter-mail',
            default_ip=self.remote_smarter_mail_ip
        )

    @property
    def settings(self):
        return self._plesk_settings

    @property
    @cached
    def plesk_server(self):
        """
        :rtype: parallels.plesk.connections.target_server.PleskTargetServer 
        """
        return PleskTargetServer(self, self._plesk_settings)

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

        :rtype: parallels.plesk.connections.target_remote_smarter_mail_server.PleskTargetRemoteSmarterMailServer | None
        """
        if self.remote_smarter_mail_ip is not None:
            return PleskTargetRemoteSmarterMailServer(self._remote_smarter_mail_settings)
        else:
            return None

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

        :rtype: str | unicode | None
        """
        return get_remote_smarter_mail_ip(self.plesk_server)

    @property
    def main_node_ip(self):
        return self._plesk_settings.ip

    @contextmanager
    def main_node_runner(self):
        with self.plesk_server.runner() as runner:
            yield runner

    def main_node_session_file_path(self, filename):
        return self._session_dir.get_file_path(filename)

    def main_node_description(self):
        return messages.TARGET_PLESK_SERVER_TITLE_WITH_IP % (self.main_node_ip,)

    @property
    def panel_port(self):
        return self._plesk_settings.plesk_api.port

    @property
    def panel_admin_password(self):
        config_password = self._plesk_settings.plesk_api.auth.password
        if config_password:
            # If specified in config explicitly - use it
            return config_password
        else:
            # If not specified in config - try to detect
            return self.plesk_server.panel_admin_password

    @property
    def panel_secret_key(self):
        return self.plesk_server.panel_secret_key

    @cached
    def plesk_api(self):
        if self.panel_secret_key is None:
            if self._plesk_settings.plesk_api.auth.username is None or self.panel_admin_password is None:
                raise MigrationError(messages.MIGRATION_ERROR_PLESK_CREDENTIALS_NOT_SPECIFIED.format(
                    plesk_server=self.plesk_server.description()
                ))
        client = plesk_api.Client(
            self._plesk_settings.plesk_api.url,
            self._plesk_settings.plesk_api.auth.username,
            self.panel_admin_password,
            self.panel_secret_key,
            plesk_api.api_versions['8.4'],
            pretty_print=True,
            is_windows=self.is_windows,
            host=self._plesk_settings.plesk_api.host
        )

        # Get Plesk version.
        result = client.send(
            ServerOperator.Get([
                ServerOperator.Dataset.STAT,
            ])
        )
        server_info = result.data

        client.api_version = get_max_supported_api_version(
            server_info.statistics.versions.plesk_version
        )

        return client

    @property
    def session_dir_path(self):
        return self._plesk_settings.session_dir

    @property
    def session_dir(self):
        return self._session_dir
    
    def check_connections(self):
        connection_checker = ConnectionChecker()
        connection_checker.check_plesk_api(self.plesk_server)
        if (
            not self._plesk_settings.is_windows and
            not self._plesk_settings.is_local
        ):
            connection_checker.check_ssh(self.main_node_description(), self._plesk_settings)
        else:
            with self.main_node_runner() as runner:
                runner.check(self.main_node_description())

        if self.remote_smarter_mail_server is not None:
            with self.remote_smarter_mail_server.runner() as runner:
                runner.check(self.remote_smarter_mail_server.description())

    @property
    def is_windows(self):
        return self._plesk_settings.is_windows

    @property
    def is_local(self):
        """If main node of target server is local or remote

        Local server means that migrator works on the target node, remote means
        that migrator's node and target node are different servers
        """
        return self._plesk_settings.is_local

    @property
    @cached
    def plesk_cli_runner(self):
        """Retrieve Plesk command line utilities runner

        :rtype: parallels.core.utils.plesk_cli_runner.PleskCLIRunnerBase
        """
        return self.plesk_server.cli_runner()
