"""Command line interface for migration between different panel instances"""

from parallels.core import messages

import argparse
from datetime import datetime

from parallels.core.cli.common_cli import MigratorHelpCommand, MigratorShellCommand, MigratorScriptCommand, \
    create_arguments_parser, run as run_migrator
from parallels.core.cli.command import CommandTypes, Command, CommandOperation
from parallels.core import MigrationConfigurationFileError
from parallels.core.panels import list_target_panels, list_built_in_source_panels, load_source_panel_class, \
    load_target_panel_class, list_all_source_panels
from parallels.core.registry import Registry
from parallels.core.statistics import StatisticsReporter
from parallels.core.utils.common import obj, get_executable_file_name, format_list
from parallels.core.utils.config_utils import global_section
from parallels.core.utils.steps_profiler import get_default_steps_profiler
from parallels.core.utils.steps_profiler import get_default_steps_report
from parallels.core.utils.type_checker import get_default_type_report
from parallels.core.utils.windows_agent_remote_object import RPCAgentBuilder


def run(base_dir, var_dir, execution_path, args):
    try:
        with get_default_steps_profiler().measure_time(
            'migration', 'Migration'
        ):
            # Workaround for race condition in Python code when using threading:
            # datetime.strptime sometimes (rare) fails when used from multiple threads.
            # To fix we call datetime.strptime before any threading is used.
            # See https://stackoverflow.com/questions/32245560/module-object-has-no-attribute-strptime-with-several-threads-python
            # for more information.
            datetime.strptime("2017-01-01", "%Y-%m-%d")

            registry = Registry.get_instance()
            registry.set_base_dir(base_dir)
            registry.set_var_dir(var_dir)
            registry.set_execution_path(execution_path)
            return run_migrator(
                args_parser=_create_migration_arguments_parser(),
                create_migration_func=lambda config, options: _create_migration(config, options),
                args=args
            )
    finally:
        get_default_steps_report().save_reports()
        get_default_type_report().save_reports()


class CommonMigrationOptions(object):
    """Common migrator options - that are used for several commands defined in different files"""
    def __init__(self, global_options_list, services_checks):
        self.global_options_list = global_options_list
        self.services_checks = services_checks
        self.post_migration_options_list = None


def _create_common_options():
    async_opt = argparse.ArgumentParser(add_help=False)
    async_opt.add_argument(
        '--async', action='store_true',
        help=messages.OPTION_ASYNC_HELP
    )
    quiet_opt = argparse.ArgumentParser(add_help=False)
    quiet_opt.add_argument(
        '--quiet', action='store_true',
        help=messages.OPTION_QUIET_HELP
    )
    profiling_opts = argparse.ArgumentParser(add_help=False)
    profiling_group = profiling_opts.add_mutually_exclusive_group()
    profiling_group.add_argument(
        '--disable-profiling', action='store_const',
        dest='enable_profiling', const=False,
        help=messages.OPTION_DISABLE_PROFILING_HELP
    )
    profiling_group.add_argument(
        '--enable-profiling', action='store_const',
        dest='enable_profiling', const=True,
        help=messages.OPTION_DISABLE_PROFILING_HELP
    )
    progress_task_id_opt = argparse.ArgumentParser(add_help=False)
    progress_task_id_opt.add_argument(
        '--progress-task-id', action='store',
        help=messages.OPTION_DISABLE_PROFILING_HELP
    )
    stop_mark_file_opt = argparse.ArgumentParser(add_help=False)
    stop_mark_file_opt.add_argument(
        '--stop-mark-file', action='store',
        help=messages.OPTION_STOP_MARK_FILE_HELP
    )
    debug_opt = argparse.ArgumentParser(add_help=False)
    debug_opt.add_argument(
        '--debug', action='store_true',
        help=messages.OPTION_DEBUG_HELP
    )
    type_checker_opt = argparse.ArgumentParser(add_help=False)
    type_checker_opt.add_argument(
        '--type-checker', action='store_true',
        help=argparse.SUPPRESS
    )

    global_opts_list = [
        async_opt,
        quiet_opt,
        profiling_opts,
        stop_mark_file_opt,
        debug_opt,
        type_checker_opt,
        progress_task_id_opt,
    ]

    services_checks_opts = argparse.ArgumentParser(add_help=False)
    services_checks_opts.add_argument(
        '--skip-services-checks', action='store_true',
        help=messages.DO_NOT_CHECK_SERVICES_FOR_ANY)

    return CommonMigrationOptions(
        global_options_list=global_opts_list,
        services_checks=services_checks_opts
    )


def _create_migration_arguments_parser():
    indent = ' ' * 4

    # ************************** Common options *******************************
    common_options = _create_common_options()
    reload_source_data_opt = argparse.ArgumentParser(add_help=False)
    reload_source_data_opt.add_argument(
        '--reload-source-data', action='store_true', 
        help=messages.FORCE_FETCHING_DATA_FROM_SOURCE_PANELS)
    # reverse "reload-source-data" option for fetch micro command,
    # which should ignore caches by default
    use_cached = argparse.ArgumentParser(add_help=False)
    use_cached.add_argument(
        '--use-cached', action='store_false', dest='reload_source_data',
        help=(
            messages.DO_NOT_USE_RELOAD_SOURCE_DATA)
    )

    use_cache_default_true = argparse.ArgumentParser(add_help=False)
    use_cache_default_true.add_mutually_exclusive_group(required=False)
    use_cache_default_true.add_argument('--use-cache', dest='use_cache', action='store_true')
    use_cache_default_true.add_argument('--no-use-cache', dest='use_cache', action='store_false')
    use_cache_default_true.set_defaults(use_cache=True)

    use_cache_default_false = argparse.ArgumentParser(add_help=False)
    use_cache_default_false.add_mutually_exclusive_group(required=False)
    use_cache_default_false.add_argument('--use-cache', dest='use_cache', action='store_true')
    use_cache_default_false.add_argument('--no-use-cache', dest='use_cache', action='store_false')
    use_cache_default_false.set_defaults(use_cache=False)

    migration_list_file_opts = argparse.ArgumentParser(add_help=False)
    migration_list_file_opts.add_argument(
        '--migration-list-file',
        help=messages.MIGRATION_LIST_FILENAME_DEFAULT_VALUE_IS
    )
    migration_list_file_opts.add_argument(
        '--migration-list-format',
        help=messages.OPTION_MIGRATION_LIST_FORMAT
    )
    migration_list_file_opts.add_argument(
        '--ignore-migration-list-errors',
        help=messages.IGNORE_MIGRATION_LIST_ERRORS,
        action='store_true'
    )

    ip_mapping_file_opt = argparse.ArgumentParser(add_help=False)
    ip_mapping_file_opt.add_argument(
        '--ip-mapping-file',
        help=messages.IP_MAPPING_FILENAME_FILE_CONTAINS_SOURCE)

    convert_opts = argparse.ArgumentParser(add_help=False)
    convert_opts.add_argument(
        '--ignore-pre-migration-errors', action='store_true',
        help=(
            messages.IGNORE_PREMIGRATION_ERRORS_AND_CONTINUE_MIGRATION)
    )
    convert_opts.add_argument(
        '--allocate-only-required-resources', action='store_true',
        help=(
            messages.SWITCH_STRICT_MODE_IN_WHICH_NEW)
    )

    skip_copy_content_opts = argparse.ArgumentParser(add_help=False)
    skip_copy_content_opts.add_argument(
        '--skip-copy-web-content', action='store_true',
        help=messages.CLI_OPTION_SKIP_COPY_WEB_CONTENT
    )
    skip_copy_content_opts.add_argument(
        '--skip-copy-mail-content', action='store_true',
        help=messages.CLI_OPTION_SKIP_COPY_MAIL_CONTENT
    )
    skip_copy_content_opts.add_argument(
        '--skip-copy-db-content', action='store_true',
        help=messages.CLI_OPTION_SKIP_COPY_DB_CONTENT
    )

    skip_copy_external_id_opts = argparse.ArgumentParser(add_help=False)
    skip_copy_external_id_opts.add_argument(
        '--skip-copy-external-id', action='store_true',
        help=messages.CLI_OPTION_SKIP_COPY_EXTERNAL_IDS
    )

    skip_test_all_opts = argparse.ArgumentParser(add_help=False)
    skip_test_all_opts.add_argument(
        '--skip-test-sites', action='store_true',
        help=messages.CLI_OPTION_SKIP_TEST_SITES
    )
    skip_test_all_opts.add_argument(
        '--skip-test-mail', action='store_true',
        help=messages.CLI_OPTION_SKIP_TEST_MAIL
    )
    skip_test_all_opts.add_argument(
        '--skip-test-databases', action='store_true',
        help=messages.CLI_OPTION_SKIP_TEST_DATABASES
    )
    skip_test_all_opts.add_argument(
        '--skip-test-dns', action='store_true',
        help=messages.CLI_OPTION_SKIP_TEST_DNS
    )
    skip_test_all_opts.add_argument(
        '--skip-test-users', action='store_true',
        help=messages.CLI_OPTION_SKIP_TEST_USERS
    )

    license_check_opts = argparse.ArgumentParser(add_help=False)
    license_check_opts.add_argument(
        '--skip-license-checks', action='store_true', 
        help=messages.DO_NOT_CHECK_ANY_LICENSES_ON_TARGET)
    infrastructure_checks_opts = argparse.ArgumentParser(add_help=False)
    infrastructure_checks_opts.add_argument(
        '--skip-infrastructure-checks', action='store_true',
        help=messages.DO_NOT_CHECK_INFRASTRUCTURE_FOR_ANY)
    infrastructure_checks_opts.add_argument(
        '--skip-main-node-disk-space-checks', action='store_true',
        help=messages.DO_NOT_CHECK_DISK_SPACE_REQUIREMENTS)
    skip_capability_checks_opts = argparse.ArgumentParser(add_help=False)
    skip_capability_checks_opts.add_argument(
        '--skip-capability-checks', action='store_true',
        help=messages.SKIP_CAPABILITY_CHECKS_OPTION
    )
    skip_remote_mssql_checks_opts = argparse.ArgumentParser(add_help=False)
    skip_remote_mssql_checks_opts.add_argument(
        '--skip-remote-mssql-servers-check', action='store_true',
        help=messages.SKIP_REMOTE_MSSQL_CHECKS_OPTION
    )

    test_dns_opts = argparse.ArgumentParser(add_help=False)
    test_dns_opts.add_argument(
        '--skip-dns-forwarding-test', action='store_true', 
        help=messages.DO_NOT_CHECK_THAT_DNS_QUERIES)

    get_options_opts = argparse.ArgumentParser(add_help=False)
    get_options_opts.add_argument('--command')

    resync_content_opts = argparse.ArgumentParser(add_help=False)
    resync_content_opts.add_argument(
        '--resync-content', action='store_true',
        help=messages.RESYNC_CONTENT
    )

    run_post_migration_checks_opts = argparse.ArgumentParser(add_help=False)
    run_post_migration_checks_opts.add_argument(
        '--run-post-migration-checks', action='store_true',
        help=messages.RUN_POST_MIGRATION_CHECKS
    )

    configure_opts = argparse.ArgumentParser(add_help=False)
    configure_opts.add_argument(
        'session_name', help=messages.COMMAND_CONFIGURE_CREATE_SESSION_FOLDER, nargs='?'
    )

    import_opts_list = [
        convert_opts,
        migration_list_file_opts,
        ip_mapping_file_opt,
        reload_source_data_opt,
    ]

    post_migration_opts_list = [
        migration_list_file_opts,
        ip_mapping_file_opt,
        common_options.services_checks,
        license_check_opts,
        reload_source_data_opt,
        convert_opts,
    ]
    common_options.post_migration_options_list = post_migration_opts_list

    commands = []
    migrator_command_name = get_executable_file_name()

    commands.extend([
        # ************************** Macro-commands ***************************
        # "Macro" commands are commands that could be used by end-user of
        # migrator. Examples are: transfer-accounts, check, copy-mail-content,
        # test-all, etc.

        # Get progress of previously executed command in current session, called by 'plesk-migrator get-progress'
        Command(
            'get-progress', CommandTypes.MACRO,
            messages.COMMAND_GET_PROGRESS,
            lambda runner: runner.run_entry_point('get-progress'),
            common_options.global_options_list,
            is_skip_progress_reporting=True,
            is_skip_logging=True,
            is_allow_parallel_execution=True,
            is_enable_profiling_by_default=False,
        ),

        # Check ability to perform upgrade gracefully, called by 'plesk-migrator is-allow-upgrade'
        Command(
            'check-upgrade-allowed', CommandTypes.MACRO,
            messages.COMMAND_CHECK_UPGRADE_ALLOWED,
            lambda runner: runner.run_entry_point('check-upgrade-allowed'),
            common_options.global_options_list,
            is_skip_progress_reporting=True,
            is_skip_logging=True,
            is_allow_parallel_execution=True,
            is_configuration_required=False,
            is_enable_profiling_by_default=False,
        ),

        # =========== Preparing to transfer: migration list and pre-checks ====
        Command(
            'prepare', CommandTypes.MACRO,
            messages.COMMAND_PREPARE,
            lambda runner: runner.run_entry_point('prepare'),
            common_options.global_options_list + [
                migration_list_file_opts,
                reload_source_data_opt,
                common_options.services_checks,
                license_check_opts,
            ],
        ),
        # =========== Prepare config file using interactive dialog in CLI ====
        Command(
            'configure', CommandTypes.MACRO,
            messages.COMMAND_CONFIGURE,
            lambda runner: runner.run_entry_point('configure'),
            [
                configure_opts,
            ],
            is_skip_progress_reporting=True,
            is_configuration_required=False,
            is_skip_logging=True
        ),
        Command(
            'generate-migration-list', CommandTypes.MACRO,
            messages.GENERATE_MIGRATION_LIST_FILE_FILE_COULD,
            lambda runner: runner.run_entry_point('generate-migration-list'),
            common_options.global_options_list + [
                migration_list_file_opts,
                reload_source_data_opt, 
                common_options.services_checks,
                license_check_opts,
            ],
            [
                [
                    '--overwrite',
                    messages.OVERWRITE_FILE_IF_IT_ALREADY_EXISTS,
                    'store_true'
                ],
                [
                    '--include-existing-subscriptions',
                    messages.OPTION_INCLUDE_EXISTING_SUBSCRIPTIONS,
                    'store_true'
                ]
            ]
        ),
        # Run pre-migration checks, called by 'panel-transfer check'
        Command(
            'check', CommandTypes.MACRO,
            messages.CHECK_FOR_POTENTIAL_MIGRATION_ISSUES, 
            lambda runner: runner.run_entry_point('check'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                use_cache_default_false
            ],
        ),
        # Deploy and start transfer agents on all Windows servers, called by 'panel-transfer start-transfer-agent'
        Command(
            'start-transfer-agent', CommandTypes.MACRO,
            messages.COMMAND_START_TRANSFER_AGENT,
            lambda runner: runner.run_entry_point('start-transfer-agent'),
            common_options.global_options_list,
            is_enable_profiling_by_default=False,
        ),
        # Run hosting OS resources and file content analysis, called by 'panel-transfer analyse-hosting'
        Command(
            'analyse-hosting', CommandTypes.MACRO,
            messages.COMMAND_ANALYSE_HOSTING,
            lambda runner: runner.run_entry_point('analyse-hosting'),
            common_options.global_options_list,
            is_enable_profiling_by_default=False,
        ),
        # ===================== Main transfer commands ========================
        Command(
            'deploy-resellers', CommandTypes.MACRO,
            messages.IMPORT_RESELLERS_NOTE_THAT_ONLY_CONTACT,
            lambda runner: runner.run_entry_point('deploy-resellers'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                use_cache_default_false,
            ]
        ),
        Command(
            'deploy-hosting-plans', CommandTypes.MACRO,
            u"Deploy source Plesk hosting plans to target",
            lambda runner: runner.run_entry_point('deploy-hosting-plans'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                use_cache_default_false,
            ]
        ),
        Command(
            'deploy-extensions', CommandTypes.MACRO,
            u"Deploy source Plesk extensions data to target",
            lambda runner: runner.run_entry_point('deploy-extensions'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                use_cache_default_false,
            ]
        ),
        # The main transfer operation called by 'panel-transfer transfer-accounts'
        Command(
            'transfer-accounts', CommandTypes.MACRO,
            messages.COMMAND_TRANSFER_ACCOUNTS,
            lambda runner: runner.run_entry_point('transfer-accounts'),
            common_options.global_options_list + import_opts_list + [
                license_check_opts,
                infrastructure_checks_opts,
                common_options.services_checks,
                resync_content_opts,
                run_post_migration_checks_opts,
                skip_capability_checks_opts,
                skip_remote_mssql_checks_opts,
                use_cache_default_false,
                skip_copy_content_opts,
                skip_copy_external_id_opts,
            ]
        ),
        # ===================== Copy content commands =========================
        Command(
            'copy-content', CommandTypes.MACRO,
            messages.COPY_HOSTING_CONTENT_DESTINATION_SERVERS,
            lambda runner: runner.run_entry_point('copy-content'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                use_cache_default_true,
                skip_copy_content_opts,
            ]
        ),
        Command(
            'copy-web-content', CommandTypes.MACRO,
            messages.COMMAND_COPY_WEB_CONTENT,
            lambda runner: runner.run_entry_point('copy-web-content'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                use_cache_default_true,
            ]
        ),
        Command(
            'copy-mail-content', CommandTypes.MACRO,
            messages.COMMAND_COPY_MAIL_CONTENT,
            lambda runner: runner.run_entry_point('copy-mail-content'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                use_cache_default_true,
            ]
        ),
        Command(
            'copy-db-content', CommandTypes.MACRO,
            messages.COMMAND_COPY_DB_CONTENT,
            lambda runner: runner.run_entry_point('copy-db-content'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                skip_remote_mssql_checks_opts,
                use_cache_default_true,
            ]
        ),
        # ===================== DNS commands ==================================
        Command(
            'set-low-dns-timings',  CommandTypes.MACRO,
            messages.SET_LOW_DNS_TIMING_VALUES_TTL,
            lambda runner: runner.run_entry_point('set-low-dns-timings'),
            common_options.global_options_list + [
                reload_source_data_opt, 
                common_options.services_checks,
                license_check_opts,
            ],
        ),
        Command(
            'set-dns-forwarding', CommandTypes.MACRO,
            messages.COMMAND_SET_DNS_FORWARDING,
            lambda runner: runner.run_entry_point('set-dns-forwarding'),
            common_options.global_options_list + [
                migration_list_file_opts,
                ip_mapping_file_opt,
                common_options.services_checks,
                license_check_opts,
                reload_source_data_opt,
                convert_opts,
                use_cache_default_true,
            ]
        ),
        Command(
            'undo-dns-forwarding', CommandTypes.MACRO,
            messages.COMMAND_UNDO_DNS_FORWARDING,
            lambda runner: runner.run_entry_point('undo-dns-forwarding'),
            common_options.global_options_list + [
                migration_list_file_opts,
                ip_mapping_file_opt,
                common_options.services_checks,
                license_check_opts,
                reload_source_data_opt,
                convert_opts,
                use_cache_default_true,
            ]
        ),
        Command(
            'set-external-id', CommandTypes.MACRO,
            messages.COMMAND_SET_EXTERNAL_ID,
            lambda runner: runner.run_entry_point('set-external-id'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                use_cache_default_false,
            ]
        ),
        # ===================== Post-migration checks =========================
        Command(
            'test-all', CommandTypes.MACRO,
            messages.COMMAND_TEST_ALL,
            lambda runner: runner.run_entry_point('test-all'),
            common_options.global_options_list + import_opts_list + [
                test_dns_opts, 
                common_options.services_checks,
                license_check_opts,
                use_cache_default_true,
                skip_test_all_opts
            ]
        ),
        Command(
            'test-services', CommandTypes.MACRO,
            messages.COMMAND_TEST_SERVICES,
            lambda runner: runner.run_entry_point('test-services'),
            common_options.global_options_list + import_opts_list + [
                license_check_opts,
                use_cache_default_true,
            ]
        ),
        Command(
            'test-sites', CommandTypes.MACRO,
            messages.COMMAND_TEST_SITES,
            lambda runner: runner.run_entry_point('test-sites'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                use_cache_default_true,
            ]
        ),
        Command(
            'test-dns', CommandTypes.MACRO,
            messages.COMMAND_TEST_DNS,
            lambda runner: runner.run_entry_point('test-dns'),
            common_options.global_options_list + import_opts_list + [
                test_dns_opts, 
                common_options.services_checks,
                license_check_opts,
                use_cache_default_true,
            ]
        ),
        Command(
            'test-mail', CommandTypes.MACRO,
            messages.COMMAND_TEST_MAIL,
            lambda runner: runner.run_entry_point('test-mail'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                use_cache_default_true,
            ]
        ),
        Command(
            'test-users', CommandTypes.MACRO,
            messages.COMMAND_TEST_USERS,
            lambda runner: runner.run_entry_point('test-users'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                use_cache_default_true,
            ]
        ),
        Command(
            'test-databases', CommandTypes.MACRO,
            messages.COMMAND_TEST_DATABASES,
            lambda runner: runner.run_entry_point('test-databases'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
                use_cache_default_true,
            ]
        ),
        # ===================== Other auxiliary commands ======================
        MigratorHelpCommand(migrator_command_name, commands, indent),

        # ***************** Micro and internal commands ***********************
        # "Micro" commands are commands that are mostly used for debugging and
        # development and which are included as a part of macro commands.
        # "Internal" commands are other auxiliary commands for debugging and
        # development. In general case none of these commands should be
        # used by end-user of migration tools.
        Command(
            'fetch-source', CommandTypes.MICRO,
            messages.COMMAND_FETCH_SOURCE,
            lambda m, o: m.fetch_source(o),
            common_options.global_options_list + [
                use_cached,
                common_options.services_checks
            ],
            is_legacy=True
        ),
        Command(
            'convert', CommandTypes.MICRO,
            messages.COMMAND_CONVERT,
            lambda m, o: m.convert(o),
            common_options.global_options_list + [convert_opts, migration_list_file_opts, ip_mapping_file_opt],
            is_legacy=True
        ),
        Command(
            'convert-hosting', CommandTypes.MICRO,
            messages.COMMAND_CONVERT_HOSTING_SETTINGS,
            lambda m, o: m.convert_hosting(o),
            common_options.global_options_list + [
                migration_list_file_opts, ip_mapping_file_opt, common_options.services_checks
            ],
            is_legacy=True
        ),
        Command(
            'restore-hosting', CommandTypes.MICRO,
            messages.COMMAND_RESTORE_HOSTING,
            lambda m, o: m.restore_hosting(o),
            common_options.global_options_list + [
                migration_list_file_opts, ip_mapping_file_opt, common_options.services_checks
            ],
            is_legacy=True
        ),
        Command(
            'verify-hosting', CommandTypes.MICRO,
            messages.COMMAND_VERIFY_HOSTING,
            lambda runner: runner.run_entry_point('verify-hosting'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
            ]
        ),
        Command(
            'restore-status', CommandTypes.MICRO,
            messages.COMMAND_RESTORE_STATUS,
            lambda m, o: m.restore_status(o),
            common_options.global_options_list + [migration_list_file_opts, ip_mapping_file_opt],
            is_legacy=True
        ),
        Command(
            'check-main-node-disk-space-requirements', CommandTypes.MICRO,
            messages.COMMAND_CHECK_MAIN_NODE_DISK_SPACE_REQUIREMENTS,
            lambda m, o: m.check_main_node_disk_space_requirements(
                obj(skip_main_node_disk_space_checks=False), show_skip_message=False
            ),
            common_options.global_options_list,
            is_legacy=True
        ),
        Command(
            'check-infrastructure', CommandTypes.MICRO,
            messages.COMMAND_CHECK_INFRASTRUCTURE,
            lambda runner: runner.run_entry_point('check-infrastructure'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                infrastructure_checks_opts,
            ]
        ),
        Command(
            'check-target-licenses', CommandTypes.MICRO,
            messages.COMMAND_CHECK_TARGET_PANEL_LICENSES,
            lambda runner: runner.run_entry_point('check-target-licenses'),
            common_options.global_options_list + [license_check_opts]
        ),
        Command(
            'check-target-account', CommandTypes.MICRO,
            messages.COMMAND_CHECK_ADMINISTRATOR_ACCOUNT,
            lambda runner: runner.run_entry_point('check-target-account'),
            common_options.global_options_list,
            [
                ['--username', messages.OVERWRITE_FILE_IF_IT_ALREADY_EXISTS, 'store']
            ],
            is_configuration_required=False,
        ),
        MigratorShellCommand(),
        MigratorScriptCommand(),
        Command(
            'run-tasks', CommandTypes.INTERNAL,
            messages.COMMAND_RUN_QUEUE,
            lambda runner: runner.run_entry_point('run-tasks'),
            common_options.global_options_list,
            is_skip_progress_reporting=True,
            is_allow_parallel_execution=True,
            command_operation=CommandOperation.TASK_RUNNER,
            is_enable_profiling_by_default=False,
        ),
        Command(
            'add-task', CommandTypes.INTERNAL,
            messages.COMMAND_ADD_TASK,
            lambda runner: runner.run_entry_point('add-task'),
            common_options.global_options_list,
            [
                ['--command-file', messages.ADD_QUEUE_TASK_COMMAND_FILE, 'store'],
                ['--migration-list-file', messages.ADD_QUEUE_TASK_MIGRATION_LIST_FILE, 'store'],
                ['--task-id', messages.ADD_QUEUE_TASK_ID, 'store']
            ],
            is_skip_progress_reporting=True,
            is_allow_parallel_execution=True,
            command_operation=CommandOperation.TASK_MANAGER,
            is_enable_profiling_by_default=False,
        ),
        Command(
            'cancel-task', CommandTypes.INTERNAL,
            messages.COMMAND_CANCEL_TASK,
            lambda runner: runner.run_entry_point('cancel-task'),
            common_options.global_options_list,
            [
                ['--task-id', messages.ADD_QUEUE_TASK_ID, 'store']
            ],
            is_skip_progress_reporting=True,
            is_allow_parallel_execution=True,
            command_operation=CommandOperation.TASK_MANAGER,
            is_enable_profiling_by_default=False,
        ),
        Command(
            'stop-migration', CommandTypes.INTERNAL,
            messages.COMMAND_STOP_MIGRATION,
            lambda runner: runner.run_entry_point('stop-migration'),
            common_options.global_options_list,
            is_skip_progress_reporting=True,
            is_allow_parallel_execution=True,
            is_allow_stop=False,
            is_skip_logging=True,
            is_enable_profiling_by_default=False,
        ),
        Command(
            'list-ip-addresses', CommandTypes.INTERNAL,
            messages.COMMAND_LIST_IP_ADDRESSES,
            lambda runner: runner.run_entry_point('list-ip-addresses'),
            is_enable_profiling_by_default=False,
        ),
        Command(
            'summarize-statistics', CommandTypes.INTERNAL,
            messages.COMMAND_SUMMARIZE_STATISTICS,
            lambda m, o: StatisticsReporter().summarize(),
            common_options.global_options_list,
            is_configuration_required=False,
            is_legacy=True,
            is_enable_profiling_by_default=False,
        ),
        Command(
            'get-rpc-agent-path', CommandTypes.INTERNAL,
            messages.GET_PATH_TO_RPC_AGENT_BINARY,
            lambda m, o: RPCAgentBuilder().build_and_print_path(),
            common_options.global_options_list,
            is_configuration_required=False,
            is_legacy=True,
            is_enable_profiling_by_default=False,
        ),
        Command(
            'unpack-backups', CommandTypes.INTERNAL,
            messages.COMMAND_UNPACK_BACKUPS,
            lambda m, o: m.unpack_backups(),
            common_options.global_options_list,
            is_legacy=True,
            is_enable_profiling_by_default=False,
        ),
        Command(
            'pack-backups', CommandTypes.INTERNAL,
            messages.COMMAND_PACK_BACKUPS,
            lambda m, o: m.pack_backups(),
            common_options.global_options_list,
            is_legacy=True,
            is_enable_profiling_by_default=False,
        ),
        Command(
            'list-files-to-copy', CommandTypes.INTERNAL,
            messages.COMMAND_LIST_FILES_TO_COPY,
            lambda runner: runner.run_entry_point('list-files-to-copy'),
            common_options.global_options_list + import_opts_list + [
                common_options.services_checks,
                license_check_opts,
            ],
            is_enable_profiling_by_default=False,
        ),
        Command(
            'remove-subscriptions-from-target', CommandTypes.INTERNAL,
            messages.COMMAND_REMOVE_SUBSCRIPTIONS_FROM_TARGET,
            lambda runner: runner.run_entry_point('remove-subscriptions-from-target'),
            common_options.global_options_list + [migration_list_file_opts],
            is_enable_profiling_by_default=False,
        ),
        Command(
            'cancel-subscriptions', CommandTypes.INTERNAL,
            messages.COMMAND_REMOVE_SUBSCRIPTIONS_FROM_QUEUE,
            lambda runner: runner.run_entry_point('cancel-subscriptions'),
            common_options.global_options_list + [migration_list_file_opts],
            is_skip_progress_reporting=True,
            is_allow_parallel_execution=True,
            command_operation=CommandOperation.TASK_MANAGER,
            is_enable_profiling_by_default=False,
        ),
        Command(
            'transfer-wpb-sites', CommandTypes.MICRO,
            messages.COMMAND_TRANSFER_WPB,
            lambda m, o: m.transfer_wpb_sites(o),
            common_options.global_options_list + [migration_list_file_opts, ip_mapping_file_opt],
            is_legacy=True
        ),
        Command(
            'transfer-aps-packages', CommandTypes.MICRO,
            messages.COMMAND_TRANSFER_APS_PACKAGES,
            lambda runner: runner.run_entry_point('transfer-aps-packages'),
            common_options.global_options_list + post_migration_opts_list
        ),
        Command(
            'restore-aps-applications', CommandTypes.MICRO,
            messages.COMMAND_RESTORE_APS_APPLICATIONS,
            lambda runner: runner.run_entry_point('restore-aps-applications'),
            common_options.global_options_list + post_migration_opts_list
        ),
        Command(
            'transfer-vdirs', CommandTypes.MICRO,
            messages.COMMAND_TRANSFER_VDIRS,
            lambda m, o: m.transfer_vdirs(o),
            common_options.global_options_list + [migration_list_file_opts, ip_mapping_file_opt],
            is_legacy=True
        ),
        Command(
            'print-workflow', CommandTypes.INTERNAL,
            messages.COMMAND_PRINT_WORKFLOW,
            lambda runner: runner.run_entry_point('print-workflow'),
            common_options.global_options_list,
            is_skip_progress_reporting=True,
            is_allow_parallel_execution=True,
            is_enable_profiling_by_default=False,
        ),
        # Used by bash completion
        Command(
            'list-options', CommandTypes.MICRO,
            messages.COMMAND_LIST_OPTIONS,
            lambda m, o: _get_list_options(commands, o),
            common_options.global_options_list + [get_options_opts],
            is_configuration_required=False,
            is_legacy=True,
            is_enable_profiling_by_default=False
        ),
    ])

    _load_source_and_target_specific_commands(commands, common_options)

    return create_arguments_parser(
        migrator_command_name=migrator_command_name,
        description=messages.MIGRATOR_HELP_TITLE,
        commands=commands,
        indent=indent
    )


def _load_source_and_target_specific_commands(commands, common_options):
    """Load CLI commands that are specific to source and target panels, not shared between all migrations"""

    for target_panel in list_target_panels():
        target_cli_extension_class = load_target_panel_class(target_panel, 'cli', 'CLIExtension', optional=True)
        if target_cli_extension_class is not None:
            commands.extend(target_cli_extension_class().get_additional_commands(common_options))

        for source_panel in list_built_in_source_panels(target_panel):
            source_cli_extension_class = load_source_panel_class(
                source_panel, 'cli', 'CLIExtension', optional=True
            )
            if source_cli_extension_class is not None:
                commands.extend(source_cli_extension_class().get_additional_commands(common_options))


def _create_migration(config, options):
    if config is not None:
        target_type = global_section(config).get('target-type', 'plesk')
        target_panels = list_target_panels()
        if target_type not in target_panels:
            raise MigrationConfigurationFileError(
                messages.TARGET_TYPE_NOT_SUPPORTED.format(
                    target_type=target_type,
                    supported_types=format_list(target_panels)
                )
            )

        source_type = config.get('GLOBAL', 'source-type')
        source_panels = list_all_source_panels(target_type)
        if source_type not in source_panels:
            raise MigrationConfigurationFileError(
                messages.SOURCE_TYPE_NOT_SUPPORTED.format(
                    source_type=source_type,
                    supported_types=format_list(source_panels)
                )
            )

        migrator_class = load_source_panel_class(source_type, 'migrator', 'Migrator')
    else:
        from parallels.core.migrator import Migrator
        migrator_class = Migrator

    migrator_object = migrator_class(config, options)
    return migrator_object


def _get_list_options(commands, options):
    return_list = []
    main_list = [c.name for c in commands if c.name != 'list-options']
    if options.command is None:
        return_list = main_list
    elif options.command in main_list:
        command = [c for c in commands if c.name == options.command][0]

        def get_actions_from_argument_parser(arg_parser):
            # There are not any other way to get action's name from ArgumentParser object
            # so we've used private properties
            # noinspection PyProtectedMember
            return [a.option_strings[0] for a in arg_parser._actions]

        for p in command.parents:
            return_list.extend(get_actions_from_argument_parser(p))

        for (name, _, _) in command.args:
            return_list.append(name)

    print(' '.join(return_list))
