"""Utility functions to transfer settings of virtual directories"""

from xml.etree import ElementTree

from parallels.utils.xml import elem
from parallels.utils.xml import replace_root_node_if_name_matches


class VirtualDirectoryXMLConverter(object):
	"""Converts virtual directory XML file to get ready for import

	Could change paths in XML according to target layout,
	remove reserved virtual directories
	(virtual directories that should not be transferred)
	and replace "<vhost>" node with "<vdir>" node so XML
	could be passed to Plesk CLI utilities
	"""
	def __init__(self):
		self.reserved_vdirs = None
		self.path_convert_function = None

	def set_reserved_vdirs(self, reserved_vdirs):
		self.reserved_vdirs = reserved_vdirs

	def set_path_convert_function(self, path_convert_function):
		self.path_convert_function = path_convert_function

	def convert(self, subscription, site, xml_str):
		vdir_xml = ElementTree.XML(xml_str)
		vdir_xml = self._replace_vhost_with_vdir(vdir_xml)

		if self.path_convert_function is not None:
			self._convert_paths(subscription, site, vdir_xml, self.path_convert_function)
		if self.reserved_vdirs is not None:
			self._remove_reserved_vdirs(vdir_xml)
		self._remove_duplicate_index_docs(vdir_xml)

		return ElementTree.tostring(vdir_xml, 'utf-8', 'xml')

	def _remove_duplicate_index_docs(self, vdir_xml):
		"""Remove duplicate index documents, for example "Index.cnf" and "index.cnf".

		Such documents make no sense and may cause issue when restoring virtual directories.
		Error message looks like:
		Cannot add duplicate collection entry of type 'add' with unique key attribute 'value' set to 'index.cfm'
		"""
		def remove_duplicate_recursive(node):
			if node.tag in ['vdir', 'vhost'] and 'defaultDoc' in node.attrib:
				node.attrib['defaultDoc'] = ",".join(
					self._remove_list_duplicates_case_insensitive(
						[item.strip() for item in node.attrib['defaultDoc'].split(",")]
					)
				)
			for child in node:
				remove_duplicate_recursive(child)

		remove_duplicate_recursive(vdir_xml)

	@staticmethod
	def _remove_list_duplicates_case_insensitive(lst):
		"""Take a list and make its elements unique, case insensitive, preserving order.
		The first one duplicate item is left, all the others are removed.

		:param lst: list[basestring]
		:return: list[basestring]
		"""
		result = []
		added_set = set()
		for item in lst:
			if item.lower() not in added_set:
				result.append(item)
				added_set.add(item.lower())

		return result

	@staticmethod
	def _convert_paths(subscription, site, xml, path_convert_function):
		"""

		Arguments:
		- path_convert_function - function with signature path_convert_function(subscription, site, vdir_path),
		where vdir_path is a path of a virtual directory in source virtual directory XML. The function should return
		corresponding path to the provided directory on the target server.
		"""

		# logic of XML conversion is taken from Plesk's RestoreVirtualDirectories function at windows/binary/plesk-utils/PMM/psadumpagent/RestoreEnv.cs
		# considering some things in Plesk's code are already not necessary or look buggy

		def fix_path_recursive(node):
			if node.tag in ['vdir', 'vhost'] and 'path' in node.attrib:
				node.attrib['path'] = path_convert_function(
					subscription,
					site,
					node.attrib['path'],
				)
			for child in node:
				fix_path_recursive(child)

		fix_path_recursive(xml)

	def _remove_reserved_vdirs(self, vdir_xml):
		"""Remove information about auto-generated virtual directories"""
		vdirs_node = vdir_xml.find('vdirs')
		if vdirs_node is not None:
			for reserved_vdir in self.reserved_vdirs:
				nodes_to_remove = list(vdirs_node.findall('vdir[@name="%s"]' % (reserved_vdir,)))
				for node_to_remove in nodes_to_remove:
					vdirs_node.remove(node_to_remove)

	@staticmethod
	def _replace_vhost_with_vdir(vdir_xml):
		"""In the vhost description xml file, replace vhost element with vdir element.
		"""
		root_vdir = elem('vdir')
		for k, v in vdir_xml.attrib.iteritems():
			root_vdir.attrib[k] = v
		root_vdir.attrib['name'] = ''  # name of the root vdir node should be an empty string - that is a requirement of Plesk

		return replace_root_node_if_name_matches(vdir_xml, 'vhost', root_vdir)


def get_vdirs_list(vdirs_xml_str):
	vdir_xml = ElementTree.XML(vdirs_xml_str.encode('utf-8'))
	vdirs_list = set()
	for node in vdir_xml:
		vdirs_list.update(
			_get_node_vdirs_list(node, None)
		)
	return vdirs_list


def _get_node_vdirs_list(node, parent_name):
	vdirs_list = []
	if node.tag == 'vdir' and 'name' in node.attrib:
		if parent_name is not None:
			vdir_name = parent_name + '\\' + node.attrib['name']
		else:
			vdir_name = node.attrib['name']
		vdirs_list.append(vdir_name)
	else:
		vdir_name = parent_name

	for child in node:
		vdirs_list.extend(
			_get_node_vdirs_list(child, vdir_name)
		)
	return set(vdirs_list)
