import posixpath

from parallels.core.logging import get_logger
from parallels.core.registry import Registry
from parallels.core.utils.plesk_cli_runner import PleskCLIRunnerCLI
from parallels.plesk.source.plesk import messages
from parallels.plesk.source.plesk.pmm_agent.utils import create_pmm_agent
from parallels.plesk.utils.xml_rpc import plesk as plesk_api
from parallels.plesk.utils.xml_rpc.plesk import operator as plesk_ops
from parallels.plesk.utils.xml_rpc.plesk import get_max_supported_api_version
from parallels.core.utils import plesk_utils
from parallels.core.utils.windows_utils import path_join as windows_path_join
from parallels.core.connections.plesk_server import PleskServer
from parallels.core.connections.source_server import SingleServerPanelSourceServer
from parallels.core.utils.common import cached

logger = get_logger(__name__)


class PleskSourceServer(SingleServerPanelSourceServer, PleskServer):
    def get_unix_vhost_dirs(self, runner, domain_name):
        """Return a list of domain hosting content directories."""
        return [posixpath.join(
            self.vhosts_dir, domain_name.encode('idna')
        )]

    def get_unix_domain_mail_dirs(self, runner, domain_name):
        """Return a list of mail content directories."""
        return [posixpath.join(
            self.mail_dir, domain_name.encode('idna')
        )]

    @property
    @cached
    def panel_admin_password(self):
        config_password = self.node_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 super(PleskSourceServer, self).panel_admin_password

    @property
    @cached
    def plesk_major_version(self):
        return self.plesk_version[0]

    @property
    @cached
    def plesk_components(self):
        """Return components installed on Plesk

        Returns dictionary with key - component name, value - information about
        the component.
        
        Example of result : {
            'mailman': ServerComponent(
                name='mailman', 
                version='2.1.9-6.el5_6.1'
            ), 
            'psa-spamassassin': ServerComponent(
                name='psa-spamassassin', 
                version='10.13.4-cos5.build1013111102.18'
            )
        }
        """
        logger.debug(u"Get installed Plesk components")
        result = self.plesk_api().send(
            plesk_ops.server.ServerOperator.Get([
                plesk_ops.server.ServerOperator.Dataset.COMPONENTS,
            ])
        )
        server_info = result.data
        logger.debug(u"Server info: %s", server_info)
        return server_info.components

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

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

    @property
    def dump_agent(self):
        """Dump agent of this source server

        :rtype: parallels.core.utils.pmm.agent.PmmMigrationAgentBase
        """
        return create_pmm_agent(Registry.get_instance().get_context(), self)

    @cached
    def plesk_api(self):
        """Get Plesk API client object for this server"""

        # Minimal supported Plesk version is 9.0.2 which uses API version 1.6.0.2.
        # However, we can't just talk to each Plesk with this API version. Say, 10.2 introduced
        # support for IPv6 and if we ask for webspaces list with 1.6.3.0 protocol and some webspace
        # uses IPv6, error will be returned.
        # So it is needed to ask Plesk for it's version and use corresponding protocol version.
        # Returned client is cached to avoid doing this discovery every time.
        client = plesk_api.Client(
            self.node_settings.plesk_api.url,
            self.node_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.node_settings.plesk_api.host
        )

        # Get Plesk version.
        logger.debug(messages.LOG_GET_SERVER_INFO, self.node_id)
        result = client.send(
            plesk_ops.server.ServerOperator.Get([
                plesk_ops.server.ServerOperator.Dataset.STAT,
            ])
        )
        server_info = result.data

        client.api_version = get_max_supported_api_version(
            server_info.statistics.versions.plesk_version
        )
        logger.debug(
            messages.LOG_PLESK_SERVER_INFO,
            server_info.statistics.versions.plesk_version,
            client.api_version
        )

        return client

    @cached
    def get_database_server_password(self, database_server):
        if self.plesk_version >= (11, 5):
            if self.is_windows():
                _, password = plesk_utils.get_windows_db_server_credentials(
                    self,
                    database_server.type(),
                    database_server.host(),
                    database_server.port()
                )
                return password
            else:
                _, password = plesk_utils.get_unix_db_server_credentials(
                    self,
                    database_server.type(),
                    database_server.host(),
                    database_server.port()
                )
                return password
        else:
            return None

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

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

    @property
    def vhosts_dir(self):
        return PleskServer.vhosts_dir.fget(self)

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

        :rtype: parallels.plesk.config.SourcePleskUnixConfig | parallels.plesk.config.SourcePleskWindowsConfig
        """
        return super(PleskSourceServer, self).config

    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]
        """
        return self.dump_agent.execute_sql(query_str, query_args)
