from contextlib import closing
import xml.etree.ElementTree as et

from parallels.core.utils.common import xml
from parallels.core.utils.windows_utils import path_join as windows_path_join
from parallels.core.actions.hosting_settings.transfer_virtual_directories import TransferVirtualDirectoriesBase
from parallels.core.utils.vdir_utils import VirtualDirectoryXMLConverter
from parallels.ppa.source.hsphere import hsphere_utils


class TransferVirtualDirectories(TransferVirtualDirectoriesBase):
	"""Transfer settings of virtual directories from H-Sphere

	Virtual directories from H-Sphere on Windows are transferred differently than from Plesk:
	1) different source path - includes system user name instead of subscription name
		H-Sphere also allows to place virtual directories' files outside of domain's directory,
		so we put these virtual directories on the same level with domain's directory under PPA webspace
	2) all virtual directories shall be transferred
	3) virtual directories info XML is composed by migrator (in Plesk it's composed by Plesk CLI tool)
	The rest (fixing paths in the info XML, restoring them with Plesk's CLI tool on PPA service node) -
	used from Plesks migrator without change
	"""

	def _get_site_vdirs_xml(self, global_context, subscription, site):
		"""Get virtual directories XML for specified site

		Returned virtual directories XML contains all
		paths according to the target server, not source server.
		Returns XML as string.

		:type global_context: parallels.core.global_context.GlobalMigrationContext
		:type subscription: parallels.core.migrated_subscription.MigratedSubscription
		"""
		raw_vdirs_xml = self._get_site_raw_vdirs_xml(global_context, site)
		converter = VirtualDirectoryXMLConverter()
		path_converter = hsphere_utils.PathConverter(global_context)
		converter.set_path_convert_function(path_converter.convert)
		converted_vdirs_xml = converter.convert(subscription, site, raw_vdirs_xml)
		return converted_vdirs_xml

	def _get_site_raw_vdirs_xml(self, global_context, site):
		with closing(global_context.conn.hsphere.db()) as cursor:
			vhost_settings = hsphere_utils.get_site_vhost_settings(cursor, site.name)
			if vhost_settings is not None:
				(vhost_id, sysuser_name, vhosts_dir) = vhost_settings
			else:
				return None

			# directory indexes - a getter function
			def get_directory_indexes(parent_id):
				"""Get ordered list of directory indexes assigned to given parent_id, which can be one of:
				* identifier of a virtual host
				* identifier of a virtual dir
				"""
				cursor.execute(u"""
					SELECT di.name
					FROM directory_ind di
					JOIN parent_child pc ON pc.child_id = di.id AND pc.child_type = 55	-- directory index
					WHERE pc.parent_id = %s
					ORDER BY di.id ASC	-- it is important to preserve the order
				""" % parent_id)
				return [idx for (idx,) in cursor.fetchall()]

			def get_default_indexes(parent_id):
				"""This function tries to follow H-Sphere behavior and predict the order of default documents in index.
				Don't try to find some logic in there.
				"""
				dir_indexes = ['default.htm', 'default.html', 'index.html', 'index.htm']
				type_id_to_extensions_map = {
					27: ['default.asp', 'index.asp'],
					63: ['default.aspx', 'index.aspx'],
				}

				# select information for indexes
				# there are 2 features that have fixed indexes: asp, asp_net
				# and there are 2 features that has configurable indexes: php, ssi
				# select former features and latter indexes, all of them ordered in one queue
				cursor.execute(u"""
					SELECT type, id, ext FROM (
						SELECT child_type AS type, child_id AS id, '' AS ext
						FROM parent_child
						WHERE parent_id = %s AND child_type IN (27, 63)	-- asp, asp_net
						UNION ALL
						SELECT pc_php.child_type, pc_php.child_id, im.ext AS ext
						FROM parent_child pc
						JOIN parent_child pc_php ON pc_php.parent_id = pc.child_id AND pc_php.child_type = 12	-- php3entry
						JOIN iis_mime im ON im.id = pc_php.child_id
						WHERE pc.parent_id = %s AND pc.child_type = 11		-- php3
						UNION ALL
						SELECT pc_ssi.child_type, pc_ssi.child_id, im.ext AS ext
						FROM parent_child pc
						JOIN parent_child pc_ssi ON pc_ssi.parent_id = pc.child_id AND pc_ssi.child_type = 6300	-- ssi_entry
						JOIN iis_mime im ON im.id = pc_ssi.child_id
						WHERE pc.parent_id = %s AND pc.child_type = 15		-- ssi
					) AS foo
					ORDER BY 2 ASC
				""" % (parent_id, parent_id, parent_id))
				for (type_id, _, ext) in cursor.fetchall():
					if type_id in type_id_to_extensions_map:
						dir_indexes += type_id_to_extensions_map[type_id]
					elif type_id == 12:  # standard and custom directory indexes defined for PHP module
						dir_indexes.append("index%s" % ext)
					elif type_id == 6300:  # standard and custom directory indexes defined for SSI
						dir_indexes.append("index%s" % ext)
				return dir_indexes

			def get_web_app_id(vdir_id):
				cursor.execute(u"""
					SELECT child_id
					FROM parent_child
					WHERE parent_id = %s AND child_type = 72	-- web_app
				""" % vdir_id)
				row = cursor.fetchone()
				return row[0] if row is not None else None

			def get_scriptings(parent_id):
				scriptings = []
				cursor.execute(u"""
					SELECT 1
					FROM parent_child
					WHERE parent_id = %s AND child_type = 15	-- ssi
				""" % parent_id)
				if cursor.fetchone() is not None:
					scriptings.append('ssi')

				cursor.execute(u"""
					SELECT rv.type_id, rv.version
					FROM resource_version rv
					JOIN parent_child pc_scripting ON pc_scripting.child_id = rv.resource_id
					WHERE pc_scripting.parent_id = %s
				""" % parent_id)

				for row in cursor.fetchall():
					if row[0] == 63:  # ASP.NET
						versions_hs_to_ppa = {
							'aspnet1_1': 'asp_dot_net2',
							'aspnet2_0': 'asp_dot_net2',  # '4.0' is Plesk's version string for ASP.NET 4.0.30319
						}
						scriptings.append(versions_hs_to_ppa.get(row[1], 'asp_dot_net2'))
					elif row[0] == 11:  # PHP
						versions_hs_to_ppa = {
							'php4_4': 'php',
							'php4_4_9': 'php',
							'php5_2': 'php5',
							'php5_2_17': 'php5',
							'php5_3': 'php5_3',
							'php5_3_17': 'php5_3',
						}
						scriptings.append(versions_hs_to_ppa.get(row[1], 'php5'))

				return u",".join(scriptings)

			# virtual host
			def get_vdir_params_as_xml_attrs(row, directory_indexes, scriptings=None):
				attrs = {
					'name': row[0],
					'path': windows_path_join(vhosts_dir, sysuser_name, row[1]),
					'authAnonymous': 'true' if row[2] == 0 else 'false',
					'accessRead': 'true' if row[3] == 1 else 'false',
					'accessWrite': 'true' if row[4] == 1 else 'false',
					'accessExecute': 'true' if row[5] == 2 else 'false',  # scripts and executables => executables enabled
					'accessScript': 'true' if (row[5] == 1 or row[5] == 2) else 'false',
					# scripts, scripts and executables => scripts enabled
					'accessSource': 'true' if row[6] == 1 else 'false',
					'enableDirBrowsing': 'true' if row[7] == 1 else 'false',
					'enableDefaultDoc': 'true',
					# it's always on in H-Sphere. either the standard default indexes are used or custom defined ones.
					# list of custom defined indexes can be empty, but the setting is still on.
					'defaultDoc': u",".join(directory_indexes),
				}
				if scriptings is not None:
					attrs['scriptMaps'] = scriptings

				return attrs

			cursor.execute(u"""
				SELECT '%s', path, auth, read, write, execute, source, browse
				FROM iis_web_dir
				WHERE id = %s
			""" % (site.name, vhost_id))
			vhost_row = cursor.fetchone()
			row = vhost_row or (site.name, site.name, 0, 1, 0, 1, 0, 0)

			cursor.execute(u"""
				SELECT 1 FROM parent_child WHERE parent_id = %s AND child_type = 55
			""" % vhost_id)
			if cursor.fetchone() is not None:
				vhost_dir_indexes = get_directory_indexes(vhost_id)
			else:
				vhost_dir_indexes = get_default_indexes(vhost_id)

			if row[
				1] == '\\':  # incorrect path that H-Sphere stores in db for a virtual host. should be replaced with vhost's site_name.
				row[1] = site.name
			vhost_node = xml.elem('vhost', [xml.elem('vdirs', [], {})],
								  get_vdir_params_as_xml_attrs(row, vhost_dir_indexes))

			# virtual directories
			def add_vdir_into_xml_tree(parent_node, vdir_name, vdir_params, vdir_dir_indexes, scriptings=None):
				# given vdir1/vdir2/vdir3 path, handle vdir1 at this level
				vdir_name_parts = vdir_name.split('/', 1)

				# look if parent_node has element with same name already
				vdirs_at_this_level = {vdir.attrib['name']: vdir for vdir in list(parent_node)}
				current_vdir_name = vdir_name_parts[
					0]  # given vdir1/vdir2 as vdir_name, it'll be vdir1. given just vdir3, it'll be vdir3
				if current_vdir_name in vdirs_at_this_level:
					# use the existing element
					current_vdir = vdirs_at_this_level[current_vdir_name]
					if vdir_name == current_vdir_name:
						# updating its attributes when we have values to set
						for key, value in get_vdir_params_as_xml_attrs(vdir_params, vdir_dir_indexes,
																	   scriptings).iteritems():
							current_vdir.set(key, value)
					else:
						# or not updating its attributes when we don't have the values to set
						pass
				else:
					# append an xml element
					if vdir_name != current_vdir_name:
						# append a stub, just to create a virtual directory, without any access to it
						params = (current_vdir_name, vdir_params[1], 0, 0, 0, 0, 0, 0)
					else:
						# append a virtual directory with real settings
						params = [current_vdir_name] + list(vdir_params[1:])
					current_vdir = xml.elem('vdir', [xml.elem('vdirs', [], {})],
											get_vdir_params_as_xml_attrs(params, vdir_dir_indexes, scriptings))
					parent_node.append(current_vdir)

				if len(vdir_name_parts) > 1:
					# given vdir1/vdir2/vdir3 path, handle the rest - vdir2/vdir3 - recursively
					add_vdir_into_xml_tree(current_vdir.find('vdirs'), vdir_name_parts[1], vdir_params,
										   vdir_dir_indexes, scriptings)

			cursor.execute(u"""
				SELECT wd.id, pc_idx.child_id,
				       wd.dirname, wd.path, wd.auth, wd.read, wd.write, wd.execute, wd.source, wd.browse
				FROM iis_web_dir wd
				JOIN parent_child pc ON pc.child_id = wd.id
				LEFT JOIN parent_child pc_idx ON pc_idx.parent_id = pc.child_id AND pc_idx.child_type = 55	-- directory_ind
				WHERE pc.parent_id = %s
			""" % vhost_id)

			for row in cursor.fetchall():
				web_app_id = get_web_app_id(row[0])

				if row[1] is not None:
					vdir_dir_indexes = get_directory_indexes(row[0])
				elif web_app_id is not None:
					vdir_dir_indexes = get_default_indexes(web_app_id)
				else:
					vdir_dir_indexes = ['default.htm', 'default.html', 'index.html', 'index.htm']

				if web_app_id is not None:
					scriptings = get_scriptings(web_app_id)
				else:
					scriptings = ""  # scripting is not enabled on virtual directory

				add_vdir_into_xml_tree(vhost_node.find('vdirs'), row[2], row[2:], vdir_dir_indexes, scriptings)

			if any([
				vhost_row is not None,  # if there were vhost settings
				len(vhost_dir_indexes) > 0,  # or there were vhost directory indexes
				len(list(vhost_node.findall('vdirs/vdir'))) > 0,  # or virtual dirs
			]):
				# then return the tree, and let migrator transfer these settings
				return et.tostring(vhost_node, 'utf-8', 'xml')
			else:
				return None  # don't return a tree, let migrator skip transferring vdirs/directory indexes for this site
