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

from parallels.core import messages

import argparse

from parallels.core.cli.common_cli import Command, CommandTypes, \
	MigratorHelpCommand, MigratorShellCommand, MigratorScriptCommand, \
	create_arguments_parser, run as run_migrator
from parallels.core import imapsync
from parallels.core import MigrationConfigurationFileError
from parallels.core.panels import list_target_panels, list_source_panels, load_source_panel_class, \
	load_target_panel_class
from parallels.core.utils.common import obj, get_executable_file_name, format_list
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


def run(base_dir, var_dir, execution_path, args):
	try:
		with get_default_steps_profiler().measure_time(
			'migration', 'Migration'
		):
			return run_migrator(
				base_dir=base_dir,
				var_dir=var_dir,
				execution_path=execution_path,
				args_parser=_create_migrator_arguments_parser(),
				create_migrator_func=lambda config: _create_migrator(config),
				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


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
	)
	skip_profiling_opt = argparse.ArgumentParser(add_help=False)
	skip_profiling_opt.add_argument(
		'--skip-profiling', action='store_true',
		help=messages.OPTION_SKIP_PROFILING_HELP
	)
	type_checker_opt = argparse.ArgumentParser(add_help=False)
	type_checker_opt.add_argument(
		'--type-checker', action='store_true',
		help=messages.OPTION_TYPE_CHECKER_HELP
	)

	global_opts_list = [
		async_opt,
		quiet_opt,
		skip_profiling_opt,
		type_checker_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_migrator_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)
	)

	migration_list_file_opt = argparse.ArgumentParser(add_help=False)
	migration_list_file_opt.add_argument(
		'--migration-list-file',
		help=messages.MIGRATION_LIST_FILENAME_DEFAULT_VALUE_IS)

	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)
	)

	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_ip_mapping_opts = argparse.ArgumentParser(add_help=False)
	skip_ip_mapping_opts.add_argument(
		'--skip-ip-mapping', action='store_true',
		help=messages.SKIP_IP_MAPPING_CHECKS
	)

	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)

	fetch_options_opts = argparse.ArgumentParser(add_help=False)
	fetch_options_opts.add_argument(
		'--ignore-fetch-source-errors', action='store_true',
		help=messages.IGNORE_ERRORS_FETCHSOURCE_STAGE_HSPHERE_MIGRATION)

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

	suspend_opts = argparse.ArgumentParser(add_help=False)
	suspend_opts.add_argument(
		'--accept-automatic-deletion', action='store_true',
		help=messages.SUSPEND_ACCOUNTS_IN_HSPHERE_EVEN_IF)

	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
	)

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

	import_opts_list = [
		convert_opts,
		migration_list_file_opt,
		ip_mapping_file_opt,
		reload_source_data_opt,
		fetch_options_opts
	]

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

	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 'panel-transfer 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_reporting=True
		),

		# Initiate callback of given command from first source server via transfer agent,
		# used to execute some actions with administrator privileges,
		# called by 'panel-transfer callback --command="<command>"'
		Command(
			'callback', CommandTypes.MACRO,
			messages.COMMAND_CALLBACK,
			lambda runner: runner.run_entry_point('callback'),
			common_options.global_options_list + [
				callback_opt
			],
			is_skip_reporting=True
		),

		# =========== Preparing to transfer: migration list and pre-checks ====
		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_opt, 
				reload_source_data_opt, 
				fetch_options_opts, 
				common_options.services_checks,
				license_check_opts,
			],
			[
				['--overwrite', messages.OVERWRITE_FILE_IF_IT_ALREADY_EXISTS, 'store_true']
			]
		),
		# write stdin into migration list
		Command(
			'write-migration-list', CommandTypes.MACRO,
			messages.WRITE_MIGRATION_LIST_FILE_FILE_COULD,
			lambda runner: runner.run_entry_point('write-migration-list'),
			common_options.global_options_list + [
				migration_list_file_opt
			],
			[
				['--overwrite', messages.OVERWRITE_FILE_IF_IT_ALREADY_EXISTS, '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,
				skip_ip_mapping_opts
			],
		),
		# 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
		),
		# 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
		),
		# ===================== Main transfer commands ========================
		Command(
			'import-resellers', CommandTypes.MACRO,
			messages.IMPORT_RESELLERS_NOTE_THAT_ONLY_CONTACT,
			lambda runner: runner.run_entry_point('import-resellers'),
			common_options.global_options_list + import_opts_list + [
				common_options.services_checks,
				license_check_opts,
			]
		),
		Command(
			'import-plans', CommandTypes.MACRO,
			u"Import service plans", 
			lambda runner: runner.run_entry_point('import-plans'),
			common_options.global_options_list + import_opts_list + [
				common_options.services_checks,
				license_check_opts,
			]
		),
		# 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,
				run_post_migration_checks_opts,
				skip_capability_checks_opts
			]
		),
		# ===================== H-Sphere billing commands =====================
		Command(
			'transfer-billing', CommandTypes.MACRO,
			messages.TRANSFER_BILLING_DATA_FROM_HSPHERE,
			lambda m, o: m.transfer_billing(o),
			common_options.global_options_list + import_opts_list + [
				suspend_opts,
				license_check_opts,
			],
			is_legacy=True
		),
		Command(
			'check-billing', CommandTypes.MACRO,
			messages.CHECK_IF_BILLING_DATA_CAN_SUCCESSFULLY,
			lambda m, o: m.check_billing(o),
			common_options.global_options_list + import_opts_list + [
				suspend_opts,
				license_check_opts,
			],
			is_legacy=True
		),
		# ===================== 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,
			]
		),
		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,
			]
		),
		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,
			]
		),
		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,
			]
		),
		# ===================== 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, 
				fetch_options_opts, 
				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_opt,
				ip_mapping_file_opt,
				common_options.services_checks,
				license_check_opts,
				reload_source_data_opt,
				convert_opts,
			]
		),
		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_opt,
				ip_mapping_file_opt,
				common_options.services_checks,
				license_check_opts,
				reload_source_data_opt,
				convert_opts,
			]
		),
		# ===================== 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,
			]
		),
		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,
			]
		),
		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,
			]
		),
		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,
			]
		),
		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,
			]
		),
		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,
			]
		),
		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,
			]
		),
		# ===================== Other auxiliary commands ======================
		MigratorHelpCommand(migrator_command_name, commands, indent),
		Command(
			'install-imapsync', CommandTypes.MACRO,
			messages.COMMAND_INSTALL_IMAPSYNC,
			lambda o: imapsync.install(),
			common_options.global_options_list,
			is_migrator_required=False
		),

		# ***************** 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,
				fetch_options_opts,
				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_opt, 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_opt, 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_opt, 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_opt, 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 + [
				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]
		),
		MigratorShellCommand(),
		MigratorScriptCommand(),
		Command(
			'list-ip-addresses', CommandTypes.INTERNAL,
			messages.COMMAND_LIST_IP_ADDRESSES,
			lambda runner: runner.run_entry_point('list-ip-addresses')
		),
		Command(
			'unpack-backups', CommandTypes.INTERNAL,
			messages.COMMAND_UNPACK_BACKUPS,
			lambda m, o: m.unpack_backups(),
			common_options.global_options_list,
			is_legacy=True
		),
		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_opt, 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_opt, ip_mapping_file_opt],
			is_legacy=True
		),
		Command(
			'transfer-resource-limits', CommandTypes.MICRO,
			messages.COMMAND_TRANSFER_RESOURCE_LIMITS,
			lambda m, o: m.transfer_resource_limits(o),
			common_options.global_options_list + [migration_list_file_opt, ip_mapping_file_opt],
			is_legacy=True
		),
		# Used by bash completion
		Command(
			'list-options', CommandTypes.MICRO,
			messages.COMMAND_LIST_OPTIONS,
			lambda o: _get_list_options(commands, o),
			common_options.global_options_list + [get_options_opts],
			is_migrator_required=False
		),
	])

	_load_source_and_target_specific_commands(commands, common_options)

	return create_arguments_parser(
		migrator_command_name=migrator_command_name,
		description=messages.MIGRATOR_FROM_PARALLELS_PLESKS_PARALLELS_PLESK,
		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_source_panels(target_panel):
			source_cli_extension_class = load_source_panel_class(
				target_panel, 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_migrator(config):
	source_type = config.get('GLOBAL', 'source-type')
	target_type = config.get('GLOBAL', 'target-type')
	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_panels = list_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(target_type, source_type, 'migrator', 'Migrator')
	migrator_object = migrator_class(config)
	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
			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)
