from parallels.core import MigrationError
from parallels.core import safe_format
from parallels.core.logging import get_logger
from parallels.core.migrator_config import SSHAuthKeyFilename
from parallels.core.registry import Registry
from parallels.core.utils import config_utils
from parallels.core.utils.common import cached, is_empty
from parallels.core.utils.common_constants import UNIX_SOURCE_DEFAULT_SESSION_DIR, SSH_DEFAULT_PORT
from parallels.core.utils.config_utils import ConfigSection
from parallels.core.utils.migrator_utils import normalize_domain_name
from parallels.core.utils.session_dir import UnixSessionDir
from parallels.plesk.config import TargetPleskUnixConfig
from parallels.plesk.connections.target_server import PleskTargetServer
from parallels.plesk.utils.xml_rpc.plesk.operator.extension import ExtensionCallOperator
from parallels.plesk_multi_server import messages
from parallels.plesk_multi_server.connections.ssh_key_pool import ServiceNodeSSHKeyPool

logger = get_logger(__name__)


class ServicePleskTargetServer(PleskTargetServer):
    def __init__(self, conn, settings):
        super(ServicePleskTargetServer, self).__init__(conn, settings)

    def is_windows(self):
        # Plesk Multi Server supports Unix only
        return False

    def description(self):
        return safe_format(messages.PLESK_MULTI_SERVER_NODE_DESCRIPTION, ip=self.ip())

    def ip(self):
        return self.settings().ip

    def _create_session_dir(self):
        return UnixSessionDir(
            self.runner, self.settings().session_dir, secure_subdir=True
        )

    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.settings(), 'ssh_auth'):
            return None

        return self.settings().ssh_auth.key_filename


@cached
def get_service_node_by_subscription_name(pms_management_server, subscription_name):
    """Create service node object for specified subscription.

    :type pms_management_server: parallels.plesk.plesk_multi_server.connections.target_server.PleskMultiServerManagementServer
    :type subscription_name: str | unicode
    :rtype: parallels.plesk_multi_server.connections.service_plesk_target_server.ServicePleskTargetServer
    """
    subscription_id = _get_subscription_id_by_name(pms_management_server, subscription_name)
    node_ip = _get_node_ip_of_subscription(pms_management_server, subscription_id, subscription_name)
    return _get_service_node_by_ip(pms_management_server, node_ip)


@cached
def _get_service_node_by_ip(pms_management_server, node_ip):
    """Get service node object by service node IP

    :type pms_management_server: parallels.plesk.plesk_multi_server.connections.target_server.PleskMultiServerManagementServer
    :type node_ip: str | unicode
    :rtype: parallels.plesk_multi_server.connections.service_plesk_target_server.ServicePleskTargetServer
    """
    private_key_filename = ServiceNodeSSHKeyPool.get_instance().get(pms_management_server, node_ip)

    ssh_ports = _read_ssh_ports_of_service_nodes_from_config()
    ssh_auth = SSHAuthKeyFilename(
        port=ssh_ports.get(node_ip, SSH_DEFAULT_PORT), username='root', key_filename=private_key_filename
    )
    return ServicePleskTargetServer(
        Registry.get_instance().get_context().conn.target, TargetPleskUnixConfig(
            ip=node_ip, is_local=False, ssh_auth=ssh_auth, plesk_api=None,
            session_dir=UNIX_SOURCE_DEFAULT_SESSION_DIR, is_skip_extensions=True
        )
    )


@cached
def _read_ssh_ports_of_service_nodes_from_config():
    """Read custom SSH ports of service nodes from configuration file.
    
    :rtype: dict[str | unicode, int] 
    """
    service_node_ports = {}
    config = Registry.get_instance().get_context().config
    section_names = config_utils.get_sections_list(config, 'GLOBAL', 'pms-service-nodes')
    for section_name in section_names:
        config_section = ConfigSection(config, section_name)
        ip = config_section.get('ip')
        ssh_port = config_section.get('ssh-port')

        try:
            ssh_port = int(ssh_port)
        except ValueError:
            logger.fwarn(messages.FAILED_TO_READ_SERVICE_NODE_SSH_PORT, port=ssh_port)
            continue

        if not is_empty(ip) and not is_empty(ssh_port):
            service_node_ports[ip] = int(ssh_port)

    return service_node_ports


def _get_node_ip_of_subscription(pms_management_server, subscription_id, subscription_name):
    """Get IP address of service node where specified subscription resides

    :type pms_management_server: parallels.plesk.plesk_multi_server.connections.target_server.PleskMultiServerManagementServer
    :type subscription_id: str | unicode
    :type subscription_name: str | unicode
    :rtype: str | unicode
    """
    pms_api = pms_management_server.plesk_multi_server_api()

    task_lists_data = pms_api.send_multi_server(
        ExtensionCallOperator.PleskMultiServer.TaskListOperator(filter_subscription_ids=[subscription_id])
    ).data
    """:type: list[parallels.plesk.utils.xml_rpc.plesk.operator.extension.TaskListInfo]"""
    node_ids = [task_list.node_id for task_list in task_lists_data]
    if len(node_ids) == 0:
        raise MigrationError(safe_format(messages.ERROR_FAILED_TO_GET_NODE_ID, subscription=subscription_name))
    node_id = node_ids[0]
    logger.fdebug(
        messages.DEBUG_NODE_ID_FOR_SUBSCRIPTION,
        subscription_name=subscription_name, node_id=node_id
    )

    nodes_data = pms_api.send_multi_server(
        ExtensionCallOperator.PleskMultiServer.NodesListOperator(filter_id=[node_id])
    ).data
    """:type: list[parallels.plesk.utils.xml_rpc.plesk.operator.extension.NodeInfo]"""
    if len(nodes_data) == 0:
        raise MigrationError(
            safe_format(messages.ERROR_FAILED_TO_GET_NODE_INFO, node_id=node_id, subscription=subscription_name)
        )
    node_ip = nodes_data[0].ip
    logger.fdebug(
        messages.DEBUG_NODE_IP_FOR_SUBSCRIPTION,
        subscription_name=subscription_name, node_ip=node_ip
    )
    return node_ip


def _get_subscription_id_by_name(pms_management_server, subscription_name):
    """Get subscription ID by its name

    :type pms_management_server: parallels.plesk.plesk_multi_server.connections.target_server.PleskMultiServerManagementServer
    :type subscription_name: str | unicode
    """
    query = """
        SELECT
          domains.id AS domain_id
        FROM
          Subscriptions JOIN domains ON Subscriptions.object_id = domains.id
        WHERE
          Subscriptions.object_type = "domain" AND
          domains.webspace_id = 0 AND
          LOWER(domains.name) = %(subscription_name)s
    """
    rows = pms_management_server.query_panel_db(
        query, dict(subscription_name=normalize_domain_name(subscription_name))
    )
    if len(rows) == 0:
        raise MigrationError(
            safe_format(messages.ERROR_FAILED_TO_GET_SUBSCRIPTION_ID_BY_NAME, subscription=subscription_name)
        )
    subscription_id = rows[0]['domain_id']
    return subscription_id
