from xml.etree import ElementTree as et
import logging
import base64
from parallels.utils import safe_string_repr

from parallels.common.utils.windows_utils import check_windows_national_symbols_command

logger = logging.getLogger(__name__)

class HclPleskdCtlNonZeroExitCodeError(Exception):
	""" Exception that raised in case of non zero exit code from ssh runner
	"""
	def __init__(self, stdout='', stderr=''):
		self.stdout = stdout
		self.stderr = stderr

def run_hcl(ppa_runner, host_id, script, output_codepage='utf-8', log_script=None, error_policy = 'strict'):
	if log_script is None:
		log_script = script

	logger.debug(u"Process following HCL on host #%d:\n%s", host_id, safe_string_repr(log_script))
	poa_root_path = '/usr/local/pem'
	exit_status, stdout_content, stderr_content = ppa_runner.sh_unchecked(
		u"cat | %(poa_root_path)s/bin/pleskd_ctl -f %(poa_root_path)s/etc/pleskd.props processHCL /dev/stdin %(host_id)s" % {
			'poa_root_path' : poa_root_path, 'host_id' : host_id
		},
		stdin_content=script.encode('utf-8'),
		output_codepage=output_codepage,
		error_policy=error_policy
	)
	if exit_status != 0:
		raise HclPleskdCtlNonZeroExitCodeError(stdout_content, stderr_content)
	return stdout_content, stderr_content

def process_hcl(ppa_runner, host_id, script, output_codepage='utf-8', log_script=None, error_policy = 'strict'):
	"""Returns variables dictionary as returned by processHCL.
	"""
	# Stderr contains pleskd debug messages, they are already logged by ssh_utils.run.
	# Stdout contains variable values in format "name\x00value\x00name2\x00value2\0xx".
	stdout, _ = run_hcl(ppa_runner, host_id, script, output_codepage=output_codepage, log_script=log_script, error_policy=error_policy)

	parts = stdout.split('\x00')[:-1] # Split will return empty string after last '\x00', trim it.
	variables = dict(zip(parts[::2], parts[1::2]))

	return variables

def get_windows_hcl_script(command, env=None):
	script =  u"""<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE HCL PUBLIC "HCL" "HCL 1.0">
<HCL>
	<DECLARE>
		<VAR name="stdout" transient="no" value="" />
		<VAR name="stderr" transient="no" value="" />
		<VAR name="exit_code" transient="no" value="" />
	</DECLARE>
	<PERFORM>
		<EXEC command="{command}" errvar="stderr" outvar="stdout" retvar="exit_code">{env}</EXEC>
	</PERFORM>
</HCL>
""".format(
		command=_encode_hcl_attrib(command),
		env=''.join(
			u'<ENV_VAR name="%s" value="%s"/>'
			% (_encode_hcl_attrib(name), _encode_hcl_attrib(value)) for name, value in env.iteritems()
		) if env else ''
	)
	return script

def windows_exec_unchecked(ppa_runner, host_id, command, codepage, error_policy = 'strict', env=None):
	check_windows_national_symbols_command(command)

	# ElementTree doesn't support doctype, so it will be lost during parsing and then
	# composing HCL document. Thus simple text substitution (with proper XML escaping)
	# is used.
	script = get_windows_hcl_script(command, env)

	variables = process_hcl(ppa_runner, host_id, script, output_codepage=codepage, error_policy=error_policy)
	exit_code, stdout, stderr = int(variables['exit_code']), variables['stdout'], variables['stderr']

	return (exit_code, stdout, stderr)

def hcl_upload(ppa_runner, host_id, file_name, file_contents):
	"""Upload file to Windows host
	"""
	script_template = u"""<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE HCL PUBLIC "HCL" "HCL 1.0">
<HCL>
 <PERFORM>
  <CREATEFILEB64 path="{file_name}" owner="" group="" overwrite="yes">{file_contents_base64}</CREATEFILEB64>
 </PERFORM>
</HCL>"""
	script = script_template.format(
		file_name=_encode_hcl_attrib(file_name),
		file_contents_base64=base64.b64encode(file_contents)
	)

	log_script = None
	if len(script) > 1024 * 1024: # avoid logging large files
		logger.debug("Dumping full HCL in log is skipped, reduced HCL will be logged")
		log_script = script_template.format(
			file_name=_encode_hcl_attrib(file_name),
			file_contents_base64="{CONTENTS OF FILE ENCODED IN BASE64}"
		)

	process_hcl(ppa_runner, host_id, script, log_script=log_script)

def hcl_get_file_contents(ppa_runner, host_id, file_name):
	script_template = u"""<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE HCL PUBLIC "HCL" "HCL 1.0">
<HCL>
	<DECLARE>
		<VAR name="contents_base64" transient="no" value="" />
	</DECLARE>
	<PERFORM>
		<READB64 path="{file_name}" variable="contents_base64" />
	</PERFORM>
</HCL>"""
	script = script_template.format(
		file_name=_encode_hcl_attrib(file_name)
	)
	result = process_hcl(ppa_runner, host_id, script)
	return base64.decodestring(result['contents_base64'])


def get_unix_hcl_script(command, args, stdin_content=None, env=None):
	if args is None:
		args = []
	script_hcl = u"""<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<!DOCTYPE HCL PUBLIC "HCL" "HCL 1.0">
<HCL>
	<DECLARE>
		<VAR name="stdout" transient="no" value="" />
		<VAR name="stderr" transient="no" value="" />
		<VAR name="exit_code" transient="no" value="" />
	</DECLARE>
	<PERFORM>
		<EXECB64 command="{command}" errvar="stderr" outvar="stdout" retvar="exit_code" user="root" group="root"><ARG value="{command}"/>{args}{env}{stdin}</EXECB64>
	</PERFORM>
</HCL>
""".format(
		command=_encode_hcl_attrib(command),
		args=''.join(u'<ARG value="%s"/>' % _encode_hcl_attrib(arg) for arg in args),
		stdin= u'' if stdin_content is None else base64.encodestring(stdin_content),
		env=''.join(
			u'<ENV_VAR name="%s" value="%s"/>'
			% (_encode_hcl_attrib(name), _encode_hcl_attrib(value)) for name, value in env.iteritems()
		) if env else ''
	)
	return script_hcl

def unix_exec_unchecked(ppa_runner, host_id, command, args, stdin_content=None, env=None):
	script = get_unix_hcl_script(command, args, stdin_content, env)
	variables = process_hcl(ppa_runner, host_id, script)
	exit_code, stdout, stderr = int(variables['exit_code']), variables['stdout'], variables['stderr']

	return (exit_code, stdout, stderr)

class HostNotFoundException(Exception):
	pass

def get_host_id_by_ip(poa_api, ip):
	host_id = poa_api.findHost(ip_address = ip)
	if host_id is None:
		raise HostNotFoundException(u"Host ID for IP '%s' not found, is it registered in PPA?" % ip)
	return host_id

def _encode_hcl_attrib(attr):
	return et._escape_attrib(attr, 'utf-8').decode('utf-8')
