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

from parallels.common.utils import ssh_utils
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(ssh, 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 = ssh_utils.run_unchecked(ssh,
		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
		},
		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(ssh, 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(ssh, 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):
	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" />
 </PERFORM>
</HCL>
""".format(command=et._escape_attrib(command, 'utf-8').decode('utf-8'))
	return script

def windows_exec_unchecked(ppa_ssh, host_id, command, codepage, error_policy = 'strict'):
	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)

	variables = process_hcl(ppa_ssh, 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 windows_upload(ppa_ssh, 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=et._escape_attrib(file_name, 'utf-8'),
		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=et._escape_attrib(file_name, 'utf-8'),
			file_contents_base64="{CONTENTS OF FILE ENCODED IN BASE64}"
		)

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

def get_unix_hcl_script(command, args):
	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>
		<EXEC command="{command}" errvar="stderr" outvar="stdout" retvar="exit_code" user="root" group="root">
			<ARG value="{command}"/>
			{args}
		</EXEC>
	</PERFORM>
</HCL>
""".format(
		command=et._escape_attrib(command, 'utf-8').decode('utf-8'),
		args = ''.join(u'<ARG value="%s"/>' % et._escape_attrib(arg, 'utf-8').decode('utf-8') for arg in args)
	)
	return script_hcl

def unix_exec_unchecked(ppa_ssh, host_id, command, args):
	script = get_unix_hcl_script(command, args)
	variables = process_hcl(ppa_ssh, 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

