import os
from os.path import exists
from collections import namedtuple
from textwrap import dedent
import ntpath

from paramiko import SSHException

from parallels.core.logging import get_logger
from parallels.core import messages, MigrationNoContextError
from parallels.core.registry import Registry
from parallels.core.utils.common_constants import SSH_DEFAULT_PORT, WINDOWS_SOURCE_DEFAULT_SESSION_DIR, \
    UNIX_SOURCE_DEFAULT_SESSION_DIR
from parallels.core.utils.config_utils import ConfigSection, read_auth, Auth, ConfigSelectionOption, \
    read_selection_option, global_section
from parallels.core.utils.common import format_list, is_run_on_windows, is_empty, merge_dicts, default
from parallels.core.utils.migrator_utils import normalize_domain_name
from . import MigrationError, MigrationConfigurationFileError
from parallels.core.utils.entity import Entity

logger = get_logger(__name__)


class MailContent(ConfigSelectionOption):
    NONE = 'none'
    MESSAGES = 'messages'
    FULL = 'full'

    def __init__(self, is_windows):
        self._is_windows = is_windows

    @property
    def name(self):
        return 'copy-mail-content'

    @property
    def allowed_values(self):
        if self._is_windows:
            return [self.NONE, self.FULL, self.MESSAGES]
        else:
            return [self.NONE, self.FULL]

    @property
    def default(self):
        return self.FULL


class MailImapEncryption(ConfigSelectionOption):
    NONE = 'none'
    SSL = 'ssl'
    TLS = 'tls'

    @property
    def name(self):
        return 'imap-encryption'

    @property
    def allowed_values(self):
        return [self.NONE, self.SSL, self.TLS]

    @property
    def default(self):
        return self.NONE


class MailSettingsConfig(Entity):
    def __init__(self, mail_settings):
        self._mail_settings = mail_settings

    @property
    def mail_settings(self):
        return self._mail_settings


class SourceConfig(MailSettingsConfig):
    """Base class for source settings from configuration file"""

    def __init__(self, source_id, config_section, mail_settings):
        """Class constructor

        :type source_id: str
        :type config_section: parallels.core.utils.config_utils.ConfigSection
        """
        self._source_id = source_id
        self._config_section = config_section
        # noinspection PyCallByClass
        MailSettingsConfig.__init__(self, mail_settings)

    @property
    def source_id(self):
        """Identity of this source config

        :rtype: str
        """
        return self._source_id

    @property
    def config_section(self):
        """Configuration file section that corresponds to the server

        :rtype: parallels.core.utils.config_utils.ConfigSection
        """
        return self._config_section


class ServerConfig(Entity):
    def __init__(self, server_id, session_dir):
        self._server_id = server_id
        self._session_dir = session_dir

    @property
    def server_id(self):
        return self._server_id

    @property
    def id(self):
        """Alias for server_id

        :rtype: str
        """
        return self.server_id

    @property
    def session_dir(self):
        return self._session_dir

    def is_windows(self):
        raise NotImplementedError()


class FileTransportConfig(ServerConfig):
    def __init__(self, server_id, host, username, password, encoding, session_dir):
        super(FileTransportConfig, self).__init__(server_id, session_dir)
        self._host = host
        self._username = username
        self._password = password
        self._encoding = encoding

    @property
    def host(self):
        """
        :rtype: str | unicode
        """
        return self._host

    @property
    def username(self):
        """
        :rtype: str | unicode
        """
        return self._username

    @property
    def password(self):
        """
        :rtype: str | unicode
        """
        return self._password

    @property
    def ftp_encoding(self):
        """
        :rtype: str | unicode
        """
        return self._encoding

    def is_windows(self):
        """
        :rtype: bool
        """
        raise NotImplementedError()


class PhysicalServerConfig(ServerConfig):
    def __init__(self, server_id, ip, session_dir):
        super(PhysicalServerConfig, self).__init__(server_id, session_dir)
        self._ip = ip

    @property
    def ip(self):
        return self._ip

    def is_windows(self):
        raise NotImplementedError()


class PhysicalWindowsServerConfig(PhysicalServerConfig):
    def __init__(self, server_id, ip, session_dir, windows_auth, agent_settings):
        super(PhysicalWindowsServerConfig, self).__init__(server_id, ip, session_dir)
        self._windows_auth = windows_auth
        self._agent_settings = agent_settings

    @property
    def windows_auth(self):
        return self._windows_auth

    @property
    def agent_settings(self):
        """RPC agent settings

        :rtype: parallels.core.migrator_config.WindowsAgentSettings
        """
        return self._agent_settings

    @property
    def is_windows(self):
        return True


class PhysicalUnixServerConfig(PhysicalServerConfig):
    def __init__(self, server_id, ip, session_dir, ssh_auth):
        super(PhysicalUnixServerConfig, self).__init__(server_id, ip, session_dir)
        self._ssh_auth = ssh_auth

    @property
    def ssh_auth(self):
        return self._ssh_auth

    @property
    def is_windows(self):
        return False


class ExternalDBConfigBase(Entity):
    def __init__(self, host, db_type):
        self._host = host
        self._db_type = db_type

    @property
    def host(self):
        return self._host

    @property
    def db_type(self):
        return self._db_type


class ExternalDBConfigUnix(ExternalDBConfigBase, PhysicalUnixServerConfig):
    def __init__(self, server_id, host, db_type, ip, session_dir, ssh_auth):
        ExternalDBConfigBase.__init__(self, host, db_type)
        PhysicalUnixServerConfig.__init__(self, server_id, ip, session_dir, ssh_auth)


class ExternalDBConfigWindows(ExternalDBConfigBase, PhysicalWindowsServerConfig):
    def __init__(self, server_id, host, db_type, ip, session_dir, windows_auth, agent_settings):
        ExternalDBConfigBase.__init__(self, host, db_type)
        PhysicalWindowsServerConfig.__init__(self, server_id, ip, session_dir, windows_auth, agent_settings)


class CopyMailContentSettings(Entity):
    """Set of additional options for copy mail messages operation"""

    def __init__(
        self, mode, source_provider, target_provider,
        source_log_enabled, target_log_enabled,
        source_additional_options, target_additional_options
    ):
        self._mode = mode
        self._source_provider = source_provider
        self._target_provider = target_provider
        self._source_log_enabled = source_log_enabled
        self._target_log_enabled = target_log_enabled
        self._source_additional_options = source_additional_options
        self._target_additional_options = target_additional_options

    @property
    def mode(self):
        """Mail migration mode - value of MailContent"""
        return self._mode

    @property
    def source_provider(self):
        """Mail provider to use when MailMigrator.exe on the source server

        :rtype: str | unicode | None
        """
        return self._source_provider

    @property
    def target_provider(self):
        """Mail provider to use when MailMigrator.exe on the target server

        :rtype: str | unicode | None
        """
        return self._target_provider

    @property
    def source_log_enabled(self):
        """Whether logging is enabled when running MailMigrator.exe on the source server

        :rtype: bool
        """
        return self._source_log_enabled

    @property
    def target_log_enabled(self):
        """Whether logging is enabled when running MailMigrator.exe on the target server

        :rtype: bool
        """
        return self._target_log_enabled

    @property
    def source_additional_options(self):
        """Additional options to pass to MailMigrator.exe on the source server

        :rtype: str | unicode | None
        """
        return self._source_additional_options

    @property
    def target_additional_options(self):
        """Additional options to pass to MailMigrator.exe on the target server

        :rtype: str | unicode | None
        """
        return self._target_additional_options


def read_copy_mail_content_settings(section, is_windows):
    """Set additional options for copy mail messages operation

    :rtype: parallels.core.migrator_config.CopyMailContentSettings
    """
    copy_mail_content = read_selection_option(section, MailContent(is_windows))
    source_provider = section.get('mail-source-provider', None)
    target_provider = section.get('mail-target-provider', None)
    source_log_enabled = section.getboolean('mail-source-log-enabled', None)
    target_log_enabled = section.getboolean('mail-target-log-enabled', None)
    source_additional_options = section.get('mail-source-additional-options', None)
    target_additional_options = section.get('mail-target-additional-options', None)

    return CopyMailContentSettings(
        mode=copy_mail_content,
        source_provider=source_provider,
        target_provider=target_provider,
        source_log_enabled=source_log_enabled,
        target_log_enabled=target_log_enabled,
        source_additional_options=source_additional_options,
        target_additional_options=target_additional_options,
    )


class WindowsAgentSettings(Entity):
    def __init__(
        self, enabled, use_ssl, port, client_cert, client_key, server_cert, agent_path,
        tcp_keepalive_time, piped_command_full_logging, skip_agent_deployment=False
    ):
        self._enabled = enabled
        self._use_ssl = use_ssl
        self._port = port
        self._client_cert = client_cert
        self._client_key = client_key
        self._server_cert = server_cert
        self._agent_path = agent_path
        self._tcp_keepalive_time = tcp_keepalive_time
        self._piped_command_full_logging = piped_command_full_logging
        self._skip_agent_deployment = skip_agent_deployment

    @property
    def enabled(self):
        return self._enabled

    @property
    def use_ssl(self):
        return self._use_ssl

    @property
    def port(self):
        return self._port

    @property
    def client_cert(self):
        return self._client_cert

    @property
    def client_key(self):
        return self._client_key

    @property
    def server_cert(self):
        return self._server_cert

    @property
    def agent_path(self):
        return self._agent_path

    @property
    def tcp_keepalive_time(self):
        return self._tcp_keepalive_time

    @property
    def piped_command_full_logging(self):
        return self._piped_command_full_logging

    @property
    def skip_agent_deployment(self):
        """Whether we should try to deploy RPC agent automatically with paexec

        :rtype: bool
        """
        return self._skip_agent_deployment


def read_windows_agent_settings(config, section_name, session_dir, section_optional=False):
    """
    :rtype: parallels.core.migrator_config.WindowsAgentSettings
    """
    section = ConfigSection(config, section_name, optional=section_optional)
    return WindowsAgentSettings(
        enabled=section.getboolean(
            'windows-agent-enabled',
            True if is_run_on_windows() else False
        ),
        port=section.getint('windows-agent-port', 10155),
        use_ssl=section.get('windows-agent-use-ssl', True),
        client_cert=section.get('windows-agent-client-cert', None), 
        client_key=section.get('windows-agent-client-key', None), 
        server_cert=section.get('windows-agent-server-cert', None),
        agent_path=section.get('windows-agent-path', ntpath.join(session_dir, 'rpc-agent')),
        tcp_keepalive_time=section.getint('windows-agent-tcp-keepalive-time', 30000),  # time in milliseconds
        piped_command_full_logging=section.getboolean('windows-agent-piped-command-full-logging', False),
        skip_agent_deployment=section.getboolean('windows-agent-skip-deployment', False)
    )


def read_physical_server_settings(config, section_name, section_optional=False, default_ip=None, default_os=None):
    section = ConfigSection(config, section_name, optional=section_optional)

    if 'ip' not in section:
        if default_ip is None:
            # ip-address is the main attribute of physical server, so if it is missed we consider
            # what given section does not contain physical server settings
            return None
        else:
            ip = default_ip
    else:
        ip = section['ip']

    if 'os' not in section and default_os is not None:
        os_type = default_os
    else:
        os_type = section['os']

    if os_type == 'windows':
        session_dir = section.get('session-dir', WINDOWS_SOURCE_DEFAULT_SESSION_DIR)
        windows_auth = read_windows_auth(section, not section_optional)
        windows_agent_settings = read_windows_agent_settings(
            config, section_name, session_dir, section_optional=section_optional
        )
        return PhysicalWindowsServerConfig(
            server_id=section_name, ip=ip, session_dir=session_dir,
            windows_auth=windows_auth, agent_settings=windows_agent_settings
        )
    else:
        session_dir = section.get('session-dir', UNIX_SOURCE_DEFAULT_SESSION_DIR)
        ssh_auth = read_ssh_auth(section)
        return PhysicalUnixServerConfig(
            server_id=section_name, ip=ip, session_dir=session_dir,
            ssh_auth=ssh_auth
        )


def read_windows_physical_server_settings(config, section_name, default_ip=None):
    section = ConfigSection(config, section_name, optional=True)
    ip = section.get('ip', default_ip)
    session_dir = section.get('session-dir', WINDOWS_SOURCE_DEFAULT_SESSION_DIR)
    windows_auth = read_windows_auth(section, required=False)
    windows_agent_settings = read_windows_agent_settings(config, section_name, session_dir, True)
    return PhysicalWindowsServerConfig(
        server_id=section_name, ip=ip, session_dir=session_dir,
        windows_auth=windows_auth, agent_settings=windows_agent_settings
    )


class SourceUnixServerConfig(SourceConfig, PhysicalUnixServerConfig):
    def __init__(self, config_section, server_id, ip, session_dir, ssh_auth, mail_settings):
        # noinspection PyCallByClass
        SourceConfig.__init__(self, server_id, config_section, mail_settings)
        # noinspection PyCallByClass
        PhysicalUnixServerConfig.__init__(self, server_id, ip, session_dir, ssh_auth)


class SourceWindowsServerConfig(SourceConfig, PhysicalWindowsServerConfig):
    def __init__(
        self, config_section, server_id, ip, session_dir, windows_auth,
        agent_settings, mail_settings, remote_mssql_session_dir=None
    ):
        # noinspection PyCallByClass
        SourceConfig.__init__(self, server_id, config_section, mail_settings)
        # noinspection PyCallByClass
        PhysicalWindowsServerConfig.__init__(self, server_id, ip, session_dir, windows_auth, agent_settings)
        self._remote_mssql_session_dir = default(
            remote_mssql_session_dir, default(self.session_dir, WINDOWS_SOURCE_DEFAULT_SESSION_DIR)
        )

    @property
    def remote_mssql_session_dir(self):
        """A directory for temporary files on source remote MSSQL servers

        :rtype: str | unicode
        """
        return self._remote_mssql_session_dir


def read_mail_settings_config(config, section_name, section_optional=False):
    """
    :type config: ConfigParser.RawConfigParser
    :type section_name: str | unicode
    :type section_optional: bool
    :rtype: parallels.core.migrator_config.MailSettingsConfig
    """
    section = ConfigSection(config, section_name, optional=section_optional)
    return MailSettingsConfig(read_copy_mail_content_settings(section, False))


def read_source_config(config, section_name, section_optional=False, default_ip=None, default_os=None):
    """Read generic source server settings

    :type config: ConfigParser.RawConfigParser
    :type section_name: str | unicode
    :type section_optional: bool
    :type default_ip: str | unicode | None
    :type default_os: str | unicode | None
    """
    config_section = ConfigSection(config, section_name, optional=section_optional)
    mail_settings_config = read_mail_settings_config(config, section_name, section_optional)
    physical_server_config = read_physical_server_settings(
        config, section_name, section_optional, default_ip, default_os
    )

    if physical_server_config is None:
        return SourceConfig(section_name, config_section, **mail_settings_config.as_dictionary())

    source_class = SourceWindowsServerConfig if physical_server_config.is_windows else SourceUnixServerConfig
    return source_class(
        config_section=config_section,
        **merge_dicts(physical_server_config.as_dictionary(), mail_settings_config.as_dictionary())
    )


def read_external_db_settings(config, section_name):
    """
    :type config: ConfigParser.RawConfigParser
    :type section_name: str
    :rtype: parallels.core.migrator_config.ExternalDBConfigBase
    """
    section = ConfigSection(config, section_name)
    host = section['host']
    ip = section['ip']
    os_type = section['os']
    db_type = section['type']

    if os_type == 'windows':
        session_dir = section.get('session-dir', WINDOWS_SOURCE_DEFAULT_SESSION_DIR)
        windows_auth = read_windows_auth(section)
        windows_agent_settings = read_windows_agent_settings(config, section_name, session_dir)
        return ExternalDBConfigWindows(
            server_id=section_name, host=host, db_type=db_type, ip=ip, session_dir=session_dir,
            windows_auth=windows_auth, agent_settings=windows_agent_settings
        )
    else:
        session_dir = section.get('session-dir', UNIX_SOURCE_DEFAULT_SESSION_DIR)
        ssh_auth = read_ssh_auth(section)
        return ExternalDBConfigUnix(
            server_id=section_name, host=host, db_type=db_type, ip=ip, session_dir=session_dir,
            ssh_auth=ssh_auth
        )


class ExternalMailUnixServerConfig(SourceConfig, PhysicalUnixServerConfig):
    """Configuration of external Unix mail server ("external-mail-servers" option of config)"""
    def __init__(self, config_section, server_id, ip, session_dir, ssh_auth, mail_settings):
        # noinspection PyCallByClass
        SourceConfig.__init__(self, server_id, config_section, mail_settings)
        # noinspection PyCallByClass
        PhysicalUnixServerConfig.__init__(self, server_id, ip, session_dir, ssh_auth)


class ExternalMailWindowsServerConfig(SourceConfig, PhysicalWindowsServerConfig):
    """Configuration of external Windows mail server ("external-mail-servers" option of config)"""
    def __init__(
        self, config_section, server_id, ip, session_dir, windows_auth,
        agent_settings, mail_settings
    ):
        # noinspection PyCallByClass
        SourceConfig.__init__(self, server_id, config_section, mail_settings)
        # noinspection PyCallByClass
        PhysicalWindowsServerConfig.__init__(self, server_id, ip, session_dir, windows_auth, agent_settings)


def read_external_mail_settings(config, section_name):
    """Read settings of external mail server from config

    :type config: ConfigParser.RawConfigParser
    :type section_name: str
    :rtype: parallels.core.migrator_config.ExternalMailWindowsServerConfig |
            parallels.core.migrator_config.ExternalMailUnixServerConfig
    """
    config_section = ConfigSection(config, section_name)
    mail_settings_config = read_mail_settings_config(config, section_name)
    physical_server_config = read_physical_server_settings(config, section_name)

    if physical_server_config.is_windows:
        source_class = ExternalMailWindowsServerConfig
    else:
        source_class = ExternalMailUnixServerConfig

    return source_class(
        config_section=config_section,
        **merge_dicts(physical_server_config.as_dictionary(), mail_settings_config.as_dictionary())
    )


class SSHAuthBase(Entity):
    def __init__(self, port, username):
        """
        :type port: int
        :type username: str | unicode
        """
        self._port = port
        self._username = username

    @property
    def port(self):
        """
        :rtype: int
        """
        return self._port

    @property
    def username(self):
        """
        :rtype: str | unicode
        """
        return self._username

    def connect(self, ip, client):
        """
        :param str ip: server IP address to connect by SSH to
        :param paramiko.SSHClient client: paramiko SSH client object
        """
        raise NotImplementedError()


class SSHAuthPassword(SSHAuthBase):
    def __init__(self, port, username, password, tcp_timeout=None):
        """
        :type port: int
        :type username: str | unicode
        :type password: str | unicode
        :type tcp_timeout: int | None
        """
        super(SSHAuthPassword, self).__init__(port, username)
        self._password = password
        self._tcp_timeout = tcp_timeout

    @property
    def password(self):
        """
        :rtype: str | unicode
        """
        return self._password

    @property
    def key_filename(self):
        """
        :rtype: str | unicode | None
        """
        return None

    @property
    def tcp_timeout(self):
        """
        :rtype: int | None
        """
        return self._tcp_timeout

    def connect(self, ip, client):
        """
        :param str ip: server IP address to connect by SSH to
        :param paramiko.SSHClient client: paramiko SSH client object
        """
        client.connect(
            ip, port=self.port,
            username=self.username, password=self.password,
            look_for_keys=False, timeout=self._tcp_timeout
        )


class SSHAuthKeyFilename(SSHAuthBase):
    def __init__(self, port, username, key_filename):
        """
        :type port: int
        :type username: str | unicode
        :type key_filename: str | unicode
        """
        super(SSHAuthKeyFilename, self).__init__(port, username)
        self._key_filename = key_filename

    @property
    def key_filename(self):
        """
        :rtype: str | unicode
        """
        return self._key_filename

    def connect(self, ip, client):
        """
        :param str ip: server IP address to connect by SSH to
        :param paramiko.SSHClient client: paramiko SSH client object
        """
        client.connect(
            ip, port=self.port, username=self.username,
            look_for_keys=False, key_filename=self.key_filename
        )


class SSHAuthKeyDefault(SSHAuthBase):
    RSA_KEY_PATH = '~/.ssh/id_rsa'
    DSA_KEY_PATH = '~/.ssh/id_dsa'

    def __init__(self, port, username):
        """
        :type port: int
        :type username: str | unicode
        """
        super(SSHAuthKeyDefault, self).__init__(port, username)
        self._key_filename = None

    @property
    def key_filename(self):
        """
        :rtype: str | unicode | None
        """
        return self._key_filename

    def connect(self, ip, client):
        """
        :param str ip: server IP address to connect by SSH to
        :param paramiko.SSHClient client: paramiko SSH client object
        """
        key_filenames = []

        rsa_key = os.path.expanduser(self.RSA_KEY_PATH)
        dsa_key = os.path.expanduser(self.DSA_KEY_PATH)
        if os.path.isfile(rsa_key):
            key_filenames.append(rsa_key)
        if os.path.isfile(dsa_key):
            key_filenames.append(dsa_key)

        if len(key_filenames) == 0:
            raise MigrationError(messages.NO_SSH_KEYS_AVAILABLE.format(server=ip))

        connected = False

        for key_filename in key_filenames:
            try:
                client.connect(
                    ip, port=self.port, username=self.username,
                    look_for_keys=False, key_filename=key_filename
                )
                connected = True
                self._key_filename = key_filename
                break
            except SSHException:
                # log error to debug log only, try the next key
                logger.exception()

        if not connected:
            raise MigrationError(messages.NO_SSH_KEY_SUCCEEDED.format(
                keys=format_list(key_filenames), server=ip
            ))


def read_ssh_auth(section, required=False):
    if (
        not required and
        ('ssh-auth-type' not in section or section['ssh-auth-type'].strip().lower() in ('', 'none')) and
        not ('ssh-password' in section and 'ssh-auth-type' not in section)
    ):
        # No SSH auth is specified. Consider local connections, no SSH connection.
        return None
    port = int(section.get('ssh-port', SSH_DEFAULT_PORT))
    if section.get('ssh-auth-type') == 'password' or ('ssh-password' in section and 'ssh-auth-type' not in section):
        return SSHAuthPassword(
            port=port, username=section.get('ssh-username', 'root'), password=section.get_password('ssh-password')
        )
    elif section['ssh-auth-type'] == 'key':
        if 'ssh-key' in section:
            if section['ssh-key'] == '':
                return SSHAuthKeyDefault(port=port, username=section.get('ssh-username', 'root'))
            else:
                if exists(section['ssh-key']):
                    return SSHAuthKeyFilename(
                        port=port, username=section.get('ssh-username', 'root'), key_filename=section['ssh-key']
                    )
                else:
                    raise MigrationNoContextError(
                        messages.SSH_KEY_FILE_DOES_NOT_EXIST.format(section_name=section.section_name)
                    )
        else:
            return SSHAuthKeyDefault(port=port, username=section.get('ssh-username', 'root'))
    else:
        if required:
            allowed_values = ['password', 'key']
        else:
            allowed_values = ['password', 'key', 'none']

        error_message = messages.INVALID_SSH_AUTH_VALUE
        if not required:
            error_message += " " + messages.IF_NOT_SPECIFIED_NONE_IS_SPECIFIED

        raise MigrationConfigurationFileError(
            error_message % (
                section['ssh-auth-type'],
                section.section_name,
                format_list(allowed_values)
            )
        )


def is_transfer_resource_limits_enabled(config, target_panel):
    if 'transfer-resource-limits' in config.options('GLOBAL'):
        return config.getboolean('GLOBAL', 'transfer-resource-limits')
    else:
        default_value = target_panel.is_transfer_resource_limits_by_default()
        return default_value


MultithreadingParams = namedtuple(
    'MultithreadingParams', (
        'status',
        'num_workers',
    )
)


class MultithreadingStatus(ConfigSelectionOption):
    DISABLED = 'disabled'
    DEFAULT = 'default'
    FULL = 'full'

    @property
    def name(self):
        return 'multithreading-status'

    @property
    def allowed_values(self):
        return [self.DEFAULT, self.DISABLED, self.FULL]

    @property
    def default(self):
        return self.FULL


def read_multithreading_params(config):
    status = read_selection_option(global_section(config), MultithreadingStatus())
    return MultithreadingParams(
        status=status,
        num_workers=int(global_section(config).get('multithreading-num-workers', '5')),
    )


def read_actions_to_skip(config):
    """Read list of actions which should not be executed during migration

    Returns dictionary where:
    - key is action identifier
    - value is
      * None if action should be completely skipped
      * list of normalized subscription names if action should be skipped for specific subscriptions

    :type config: ConfigParser.RawConfigParser
    :rtype: dict[str|unicode, list[str|unicode]|None]
    """
    actions_to_skip = {}

    for item in global_section(config).get('skip-actions', '').split(','):
        item = item.strip()

        if item == '':
            continue

        if ':' in item:
            action_id, subscription_name = item.split(':', 1)
            subscription_name = normalize_domain_name(subscription_name)
            if action_id not in actions_to_skip:
                actions_to_skip[action_id] = []

            if actions_to_skip[action_id] is not None:
                actions_to_skip[action_id].append(subscription_name)
        else:
            action_id = item
            actions_to_skip[action_id] = None

    return actions_to_skip


def read_rsync_additional_args(config):
    default_rsync_additional_args = ''
    return global_section(config).get(
        'rsync-additional-args', 
        default_rsync_additional_args
    ).split()


def read_event_scripts_dir(config):
    """Read location of a directory for scripts that should be called on migration events

    :type config: ConfigParser.RawConfigParser
    :rtype: str | unicode
    """
    var_dir = Registry.get_instance().get_var_dir()
    default_scripts_dir = os.path.join(var_dir, 'events')

    return global_section(config).get(
        'event-scripts-dir', default_scripts_dir
    )


def read_adjust_applications_enabled(config):
    """Read where web applications should be adjusted once web content is copied

    :type config: ConfigParser.RawConfigParser
    :rtype: bool
    """
    return global_section(config).getboolean('adjust-applications', True)


def read_adjust_applications_remove_absent_handlers_enabled(config):
    """Read whether absent handlers should be removed from web.config files during adjustment

    :type config: ConfigParser.RawConfigParser
    :rtype: bool
    """
    return global_section(config).getboolean('adjust-web-config-remove-absent-handlers', False)


def read_detect_scripting_settings_enabled(config):
    """Read where to detect scripting settings (PHP version, ASP.NET version, etc) by uploading probe to source server

    :type config: ConfigParser.RawConfigParser
    :rtype: bool
    """
    return global_section(config).getboolean('detect-scripting-settings', False)


def read_skip_check_same_webspace(config):
    """Read where we should skip check that source and target webspaces are the same

    :type config: ConfigParser.RawConfigParser
    :rtype: bool
    """
    return global_section(config).getboolean('skip-check-same-webspace', False)


# 2 hours (for 1 second intervals)
DEFAULT_RESTORE_HOSTING_SETTINGS_POLL_LIMIT = 7200


def read_restore_hosting_settings_poll_limit(config):
    """Read maximum number of attempts to poll the restore hosting task before we consider the task hanged.

    :type config: ConfigParser.RawConfigParser
    :rtype: bool
    """
    return global_section(config).getint(
        'restore-hosting-settings-poll-limit', DEFAULT_RESTORE_HOSTING_SETTINGS_POLL_LIMIT
    )


DEFAULT_RESTORE_HOSTING_SETTINGS_POLL_INTERVAL = 1


def read_restore_hosting_settings_poll_interval(config):
    """Read interval between poll attempts for the restore hosting task.

    :type config: ConfigParser.RawConfigParser
    :rtype: bool
    """
    return global_section(config).getint(
        'restore-hosting-settings-poll-interval', DEFAULT_RESTORE_HOSTING_SETTINGS_POLL_INTERVAL
    )


def get_local_session_dir(config):
    session_dir_suffix = global_section(config).get('session-dir', 'migration-session')
    return os.path.join(Registry.get_instance().get_var_dir(), 'sessions', session_dir_suffix)


class MSSQLCopyMethod(ConfigSelectionOption):
    """Way to copy MSSQL databases"""

    # Automatically select available copy method. If we have access to both source and target servers - use
    # native backups, otherwise use SMO text dump that does not require direct access
    AUTO = 'auto'
    # Copy databases with Plesk dbbackup.exe utility, '--copy' command, which uses text dump created with SMO
    TEXT = 'text'
    # Copy databases with MSSQL native backups - T-SQL "BACKUP" and "RESTORE" procedures
    NATIVE = 'native'

    @property
    def name(self):
        return 'copy-mssql-content'

    @property
    def allowed_values(self):
        return [self.AUTO, self.TEXT, self.NATIVE]

    @property
    def default(self):
        return self.AUTO


def read_mssql_copy_method(config):
    return read_selection_option(global_section(config), MSSQLCopyMethod())


def read_windows_auth(section, required=True):
    auth = read_auth(section.prefixed('windows-'))

    if is_empty(auth.username):
        auth = Auth(username='Administrator', password=auth.password)

    if required and is_empty(auth.password):
        raise MigrationError(dedent(messages.WINDOWS_PASSWORD_IS_NOT_SPECIFIED) % (
            section.section_name, section.section_name
        ))

    return auth


def is_hosting_analyser_enabled(config):
    return global_section(config).get('use-hosting-analyser', 'false').lower() == 'true'
