"""Common functions for command line interface of:
- migration between different panel instances
- move subscriptions between nodes within the same panel instance
"""

from parallels.core import messages

import sys
import logging
import logging.config
import argparse
import os.path
import yaml
import textwrap
from StringIO import StringIO
from ConfigParser import RawConfigParser, NoOptionError, NoSectionError

from parallels.core import MigrationError, MigrationConfigurationFileError, MigrationNoContextError
from parallels.core.registry import Registry
from parallels.core.utils.common import open_unicode_file
from parallels.api.plesk.core import PleskError
from parallels.api.poa.poa_api import PoaApiError

logger = logging.getLogger('parallels')


def run(script_dir, args_parser, create_migrator_func, args):
	# determine base panel migrator directory and store it into registry
	base_dir = Registry.get_instance().set_base_dir(os.path.abspath(os.path.join(script_dir, '..')))

	_set_up_encoding()
	try:
		options = args_parser.parse_args(args)

		logs_dir = os.path.join(base_dir, 'logs')
		if not os.path.exists(os.path.join(logs_dir)):
			os.mkdir(logs_dir)

		default_config_file_path = os.path.join(base_dir, 'conf', 'panel-migrator.yaml')

		panel_migrator_config_file = None
		if os.path.exists(options.panel_migrator_config):
			panel_migrator_config_file = options.panel_migrator_config
		elif os.path.exists(default_config_file_path):
			panel_migrator_config_file = default_config_file_path
		if panel_migrator_config_file is not None:
			with open(panel_migrator_config_file) as f:
				logging_config = yaml.load(f)['logging']
				if 'handlers' in logging_config:
					handlers = logging_config['handlers']
					if 'info_file' in handlers and 'filename' not in handlers['info_file']:
						handlers['info_file']['filename'] = os.path.join(logs_dir, 'panel-migrator.log')
					if 'debug_file' in handlers and 'filename' not in handlers['debug_file']:
						handlers['debug_file']['filename'] = os.path.join(logs_dir, 'panel-migrator.debug.log')
				logging.config.dictConfig(logging_config)

		logging.captureWarnings(True)

		if options.migrator_required:
			config_path = options.CONFIG_FILE
			migrator = create_migrator_func(_read_config(config_path))
			if hasattr(migrator, 'set_options'):
				migrator.set_options(options)
			options.method(migrator, options)
		else:
			options.method(options)

		return 0
	except PoaApiError as e:
		logger.debug(u'Exception:', exc_info=e)
		logger.error(
			u"An internal error occurred in POA XML-RPC: %s.\n"
			u"If you referring to a technical support representative, please attach a stack trace with the error "
			u"message from debug.log.\n"
			u"To execute a POA XML-RPC request manually: \n"
			u"1. Open debug.log, copy the POA XML-RPC request from the end of the file, and paste it into a new file "
			u"(/tmp/xmlrpc-packet for example)\n"
			u"""2. Execute the request with the help of the command
			"curl https://<management_node_ip>:8440/RPC2 -uadmin:<password> -s -k -d @/tmp/xmlrpc-packet"\n"""
			u"   Replace <management_node_ip> and <password> with the appropriate IP address and password."
			% unicode(e)
		)
	except PleskError as e:
		logger.debug(u'Exception:', exc_info=e)
		logger.error(
			u"Unexpected Plesk API Error: %s.\n"
			u"If you referring to a technical support representative, please attach a stack trace with the error "
			u"message from debug.log.\n"
			u"To execute a Plesk API request manually: \n"
			u"1. Open debug.log and copy the Plesk API request from the end of the file.\n"
			u"""2. Execute it with the help of the command
			"curl https://<plesk_api_server_ip>:8443/enterprise/control/agent.php -s -d "<quoted_packet>"
			-H "HTTP_AUTH_LOGIN: admin" -H "HTTP_AUTH_PASSWD: <password>" -H "Content-Type: text/xml" --insecure"\n"""
			u"   Replace <plesk_api_server_ip> with the required IP address, <quoted_packet> with the Plesk API "
			u"request from debug.log, and <password> with the appropriate password."
			% unicode(e)
		)
		return 1
	except MigrationError as e:
		context = getattr(e, 'context', '')
		logger.debug(u"Context: %s", context, exc_info=e)
		errmsg = unicode(e)
		if errmsg.endswith('.'):
			errmsg = errmsg[:-1]
		if context != '':
			logger.error(u"%s\nContext: %s.", errmsg, context)
		else:
			logger.error(u"%s.", errmsg)
		return 1
	except KeyboardInterrupt as e:
		logger.debug(u"Exception:", exc_info=e)
		logger.info(messages.MIGRATION_STOPPED_BY_USER_REQUEST)
	except MigrationNoContextError as e:
		logger.error(u"%s", e)
		return 1
	except UnicodeError as e:
		context = getattr(e, 'context', '')
		context_msg = u"\nContext: %s" % (context,) if context != '' else ''
		logger.debug(u"Unicode exception:", exc_info=e)
		logger.debug(messages.ERROR_OCCURRED_WHILE_WORKING_FOLLOWING_STRING, e.object)
		logger.error(
			messages.INTERNAL_MIGRATOR_ERROR_CAUSED_BY_UNICODE % (e,) + context_msg
		)
		return 1
	except NoOptionError as e:
		logger.debug(u"Exception:", exc_info=e)
		logger.error(
			u"Failed to read configuration file: no option '%s' in section '[%s]'. Please fix migration tool "
			u"configuration file ('%s') and run migration tool again." % (e.option, e.section, config_path)
		)
	except NoSectionError as e:
		logger.debug(u"Exception:", exc_info=e)
		logger.error(
			messages.FAILED_READ_CONFIGURATION_FILE_THERE_IS % (e.section, config_path)
		)
	except MigrationConfigurationFileError as e:
		logger.debug(u"Exception:", exc_info=e)
		logger.error(messages.S_PLEASE_FIX_MIGRATION_TOOL_CONFIGURATION % (
			e, config_path
		))
	except Exception as e:
		context = getattr(e, 'context', '')
		context_msg = u"\nContext: %s" % (context,) if context != '' else ''
		logger.debug(u"Exception:", exc_info=e)
		logger.error(
			messages.INTERNAL_MIGRATOR_ERROR_S_MIGRATION_IS % (e,) +
			context_msg
		)
		return 1
	finally:
		logging.shutdown()


def _read_config(raw_config_path):
	default_config_path = os.path.join(Registry.get_instance().get_base_dir(), 'conf', raw_config_path)
	config_path = default_config_path if os.path.exists(default_config_path) else raw_config_path

	config = RawConfigParser()

	try:
		# work with encodings so config.ini with UTF-8 BOM
		# is readable on Windows (notepad saves in UTF-8 BOM by default)
		with open_unicode_file(config_path) as fp:
			contents = fp.read().encode('utf-8')

		config.readfp(StringIO(contents))
	except Exception as e:
		logger.debug(u"Exception:", exc_info=True)
		raise MigrationError(u"Failed to read configuration file '%s': %s" % (config_path, str(e)))

	return config


def _set_up_encoding():
	"""Set up encoding to 'UTF-8', so python does not fail with "UnicodeEncodeError: 'ascii' codec can't encode
	characters in position ..." even in cases:
	1) if we do not use terminal (redirect output to pipe, like 'python migrator config.ini check | less')
	2) if we use some encoding, that do not support all range of symbols we output (for example, 'C' and any cyrillic
	symbol)
	Also there is another way described at
	http://stackoverflow.com/questions/1473577/writing-unicode-strings-via-sys-stdout-in-python
	but in case #2 it could make migration stop (while in the current solution we continue migration,
	but output "bad" symbols)
	"""
	import codecs
	sys.stdout = codecs.getwriter('utf-8')(sys.stdout)


class CommandTypes(object):
	MACRO = 'macro'
	MICRO = 'micro'
	INTERNAL = 'internal'


class Command(object):
	def __init__(self, name, type, help, method, parents=None, args=None, migrator_required=True):
		self.name = name
		self.type = type
		self.help = help
		self.method = method
		if parents is not None:
			self.parents = parents
		else:
			self.parents = []
		if args is not None:
			self.args = args
		else:
			self.args = []
		self.migrator_required = migrator_required


class MigratorHelpCommand(Command):
	def __init__(self, migrator_command_name, commands, indent):
		super(MigratorHelpCommand, self).__init__(
			'help', CommandTypes.MACRO,
			u"Show help", 
			self.print_help,
			args=[
				['--advanced', messages.SHOW_HELP_ADVANCED_COMMANDS, 'store_true'],
				['--internal', messages.SHOW_HELP_INTERNAL_COMMANDS, 'store_true']
			],
			migrator_required=False
		)

		self.migrator_command_name = migrator_command_name
		self.commands = commands
		self.indent = indent

	def print_help(self, options):
		print u'Available commands:'

		def print_command(command):
			print self.indent + command.name
			print textwrap.fill(
				command.help,
				width=80,
				initial_indent=self.indent * 2,
				subsequent_indent=self.indent * 2
			)

		for command in self.commands:
			if command.type == CommandTypes.MACRO:
				print_command(command)

		if options.advanced:
			print 
			print textwrap.fill(
				messages.ADVANCED_COMMANDS_ARE_EXECUTED_AS_SEPARATE,
				width=80
			)
			for command in self.commands:
				if command.type == CommandTypes.MICRO:
					print_command(command)

		if options.internal:
			print 
			print textwrap.fill(
				messages.INTERNAL_COMMANDS_ARE_INTENDED_USE_BY,
				width=80
			)
			for command in self.commands:
				if command.type == CommandTypes.INTERNAL:
					print_command(command)

		if not options.advanced and not options.internal:
			print u"To see more commands run"
			print u"%s%s help --advanced" % (
				self.indent,
				self.migrator_command_name
			)

		print """
	Optional arguments:
	{indent}--panel-migrator-config PANEL_MIGRATOR_CONFIG_FILE
	{indent}{indent}Path to Panel Migrator configuration file.
	{indent}{indent}See http://docs.python.org/library/logging.config.html#configuration-file-format for configure logging
	{indent}-h, --help Show help message of a command
	""".format(indent=self.indent)

		sys.exit(1)


class MigratorShellCommand(Command):
	def __init__(self):
		super(MigratorShellCommand, self).__init__(
			'shell', CommandTypes.INTERNAL,
			messages.START_PYTHON_INTERPRETER_IN_MIGRATOR_ENVIRONMENT,
			lambda m, o: self._shell(m, o)
		)
	
	@staticmethod
	def _shell(migrator, options):
		import code
		banner = messages.AVAILABLE_VARIABLES_MIGRATOR_OPTIONS
		code.interact(banner, local=dict(migrator=migrator, options=options))


class MigratorScriptCommand(Command):
	def __init__(self):
		super(MigratorScriptCommand, self).__init__(
			'script', CommandTypes.INTERNAL,
			messages.START_PYTHON_SCRIPT_IN_MIGRATOR_ENVIRONMENT,
			lambda m, o: self._script(m, o),
			[],
			[
				['script', u"Script to execute", 'store']
			]
		)

	@staticmethod
	def _script(migrator, options):
		import runpy
		runpy.run_path(options.script, init_globals=dict(migrator=migrator, options=options), run_name='__main__')


def create_arguments_parser(migrator_command_name, description, commands, script_dir, indent):
	usage = u"""%%(prog)s [--panel-migrator-config PANEL_MIGRATOR_CONFIG_FILE] COMMAND
commands: 
%s
To view detailed help run "%%(prog)s help".
To view arguments of a command COMMAND run "%%(prog)s COMMAND -h".
""" % (
		textwrap.fill(
			', '.join([command.name for command in commands if command.type == CommandTypes.MACRO]),
			width=80,
			break_on_hyphens=False,
			initial_indent=indent,
			subsequent_indent=indent
		)
	)

	args_parser = argparse.ArgumentParser(
		prog=migrator_command_name, 
		description=description, 
		usage=usage, 
		add_help=False
	)
	args_parser.add_argument(
		'--panel-migrator-config',
		default=os.path.join(script_dir, '..', 'conf', "panel-migrator.yaml"),
		help=messages.PATH_PANEL_MIGRATOR_CONFIGURATION_FILE_SEE)

	subparsers = args_parser.add_subparsers()

	for command in commands:
		usage = u"%s %s %sARGS" % (migrator_command_name, command.name, u"CONFIG_FILE " if command.migrator_required else u"")
		cmd = subparsers.add_parser(command.name, help=command.help, parents=command.parents, usage=usage)
		cmd.set_defaults(method=command.method, migrator_required=command.migrator_required)
		for (name, help, action) in command.args:
			cmd.add_argument(name, help=help, action=action)
		if command.migrator_required:
			cmd.add_argument('CONFIG_FILE', help=u'migrator configuration file', default='config.ini', nargs='?')

	return args_parser

