#!/opt/panel-migrator-python/bin/python
# vim:filetype=python

import sys
import os.path
import argparse
import codecs
from xml.etree import ElementTree as et

# include all directories where migrator source code is located to sys.path
script_dir = os.path.dirname(__file__)
sys.path.extend(os.path.join(script_dir, name) for name in (
	'utils', 'common', 'hosting-check', 'plesks-migrator'
))

from parallels.common.checking import Report, Problem
from parallels.common.utils.migrator_utils import format_report
from parallels.common.run_command import LocalUnixRunner

from parallels.common.plesk_backup.plesk_backup_xml import load_backup
from parallels.common.hosting_check.entity_source.web import \
		HostingObjectWebSubscription
from parallels.common.hosting_check.entity_source.mail import \
		HostingObjectMailSubscription
from parallels.common.hosting_check.entity_source.users import \
		HostingObjectSubscriptionWithUsers
from parallels.common.hosting_check.entity_source.dns import \
		HostingObjectDNSSubscription
from parallels.common.migrated_subscription import MigrationSubscription
from parallels.common.hosting_check.entity_source.common import \
		ServerBackupsCheckSource
from parallels.common.plesk_backup.data_model import Ips
from parallels.utils.xml import elem, text_elem, seq
from parallels.utils import obj
from parallels.common.converter.dns import replace_resource_record_ips
from parallels.common.hosting_check.entity_source.compound import \
	CompoundHostingObject

from parallels.hosting_check import DomainWebService
from parallels.hosting_check import DomainMailService
from parallels.hosting_check import DomainFTPService
from parallels.hosting_check import DomainRDPService
from parallels.hosting_check import DomainSSHService
from parallels.hosting_check import DomainDNSService
from parallels.hosting_check import User
from parallels.hosting_check import DNSRecord
from parallels.hosting_check.utils.entity_meta import \
		Entity, Property, \
		PropertyType, PropertyTypeString, \
		PropertyTypeList, PropertyTypeEntity

def main():
	parser = argparse.ArgumentParser(
		description=\
			'Generate XML that could be used to perform post-migration '
			'hosting checks. Use Plesk backup archive as source. '
	)
	parser.add_argument(
		'--backup-archive', 
		required=True, 
		help="Backup archive in Plesk format (either zip or tar.gz)"
	)
	parser.add_argument(
		'--output-xml-file', 
		required=True,
		help="Filename to put XML which describes checks to be performed"
	)
	parser.add_argument(
		'--source-ip', 
		required=True,
		help="IP address of the source server"
	)
	parser.add_argument(
		'--target-ip', 
		required=True,
		help="IP address of the target server"
	)
	parser.add_argument(
		'--domains-list-file', 
		help=u"File with list of domains to check. "
			u"Each domain should be on new line, "
			u"nothing except for domains is allowed"
	)
	parser.add_argument(
		'--format-with-xmllint', 
		help="Format XML with xmllint or not",
		action='store_true'
	)
	args = parser.parse_args()
	generate_xml(
		args.backup_archive, args.output_xml_file, args.format_with_xmllint,
		args.target_ip, args.source_ip, args.domains_list_file
	)

def generate_xml(
	backup_archive, output_xml, format_with_xmllint, 
	target_ip, source_ip, 
	domains_list_file
):
	"""Generate checks XML file from Plesk backup archive"""
	if domains_list_file:
		with open(domains_list_file) as fp:
			domains = [
				line.strip() for line in fp if line.strip() != ''
			]
		# Create migration list-like object
		migration_list_data = obj(
			subscriptions_mapping={domain: None for domain in domains},
			customers_mapping={},
			resellers=[],
			plans=[]
		)
	else:
		migration_list_data = None

	backup = load_backup(backup_archive, migration_list_data)
	
	replace_resource_record_ips(backup, {
		subscription.name: obj(web_ips=obj(v4=target_ip, v6=None))
		for subscription in backup.iter_all_subscriptions()
	})

	web_checks_source = ServerBackupsCheckSource(
		{'': backup}, 
		lambda backup, subscription: create_web_check_subscription_class(
			backup, subscription, target_ip, source_ip
		)
	)
	mail_checks_source = ServerBackupsCheckSource(
		{'': backup}, 
		lambda backup, subscription: create_mail_check_subscription_class(
			backup, subscription, target_ip, source_ip
		)
	)
	users_checks_source = ServerBackupsCheckSource(
		{'': backup}, 
		lambda backup, subscription: create_users_check_subscription_class(
			backup, subscription, target_ip, source_ip
		)
	)
	dns_checks_source = ServerBackupsCheckSource(
		{'': backup}, 
		lambda backup, subscription: create_dns_check_subscription_class(
			backup, subscription, target_ip, source_ip
		)
	)

	root_node = et.Element('root')
	transform_to_xml_hosting_object(
		CompoundHostingObject([
			mail_checks_source.get_root_hosting_object(),
			web_checks_source.get_root_hosting_object(),
			users_checks_source.get_root_hosting_object(),
			dns_checks_source.get_root_hosting_object()
		]), root_node
	)
	with codecs.open(output_xml, 'w', 'utf-8') as fp:
		xml_string = ''.join([
			'<?xml version="1.0" encoding="UTF-8"?>',
			et.tostring(root_node, encoding='utf-8')
		])

		if format_with_xmllint:
			xml_string = LocalUnixRunner().sh(
				'xmllint --format -', stdin_content=xml_string
			)
		else:
			xml_string = xml_string.decode('utf-8')

		fp.write(xml_string)

def create_users_check_subscription_class(backup, subscription, target_ip):
	"""Create class that provides checks for single subscription from backup"""
	class FakeUsersInfoSource(UsersInfoSourceInterface):
		def get_target_web_ip(self, subscription_name):
			return target_ip

		def is_windows(self, subscription_name):
			return True

		def get_web_target_server(self, subscription_name):
			return None

	return HostingObjectSubscriptionWithUsers(
		backup, subscription, 
		FakeUsersInfoSource()
	)

def create_users_check_subscription_class(backup, subscription, target_ip, source_ip):
	"""Create class that provides checks for single subscription from backup"""
	return HostingObjectSubscriptionWithUsers(
		backup, subscription, 
		create_migrated_subscription_class(backup, target_ip, source_ip)
	)

def create_web_check_subscription_class(backup, subscription, target_ip, source_ip):
	"""Create class that provides checks for single subscription from backup"""
	return HostingObjectWebSubscription(
		backup, subscription, 
		create_migrated_subscription_class(backup, target_ip, source_ip), 
	)

def create_mail_check_subscription_class(backup, subscription, target_ip, source_ip):
	"""Create class that provides checks for single subscription from backup"""
	return HostingObjectMailSubscription(
		None, subscription, 
		create_migrated_subscription_class(backup, target_ip, source_ip), 
		None
	)

def create_dns_check_subscription_class(backup, subscription, target_ip, source_ip):
	"""Create class that provides checks for single subscription from backup"""
	return HostingObjectDNSSubscription(
		None, subscription, 
		create_migrated_subscription_class(backup, target_ip, source_ip), 
		skip_dns_forwarding_test=True, skip_dns_external_test=True
	)

def create_migrated_subscription_class(backup, target_ip, source_ip):
	class MigrationSubscriptionImpl(MigrationSubscription):
		def __init__(self, name):
			self.name = name

		@property
		def raw_backup(self):
			return backup.get_subscription(self.name)

		@property
		def converted_backup(self):
			return backup.get_subscription(self.name)

		@property
		def target_mail_ip(self):
			return target_ip

		@property
		def source_mail_ip(self):
			return source_ip

		@property
		def target_web_ip(self):
			return target_ip

		@property
		def target_dns_ips(self):
			return [target_ip]

		@property
		def source_dns_ips(self):
			return [source_ip]

		@property
		def is_windows(self):
			return True

		@property
		def is_fake(self):
			# Do not check fake domains of Helm 3 - all they 
			# end with ".package" suffix.
			return self.name.endswith('.package')

		@property
		def web_target_server(self):
			return None

	return MigrationSubscriptionImpl

def transform_to_xml_hosting_object(hosting_object, node):
	"""Transform hosting check object 

	Hosting object is a container for checks and other child hosting objects
	Transform hosting object to XML, put as child of provided XML node
	
	Arguments:
	- hosting_object - hosting object which provides child 
	hosting objects and check entities
	(common.hosting_check.BaseHostingObject)
	- node - XML node
	"""
	for child_hosting_object in \
		hosting_object.get_child_hosting_objects().child_hosting_objects:
			child_node = elem('report', seq(
				text_elem('type', child_hosting_object.type),
				text_elem('name', child_hosting_object.name),
			))
			node.append(child_node)
			transform_to_xml_hosting_object(child_hosting_object, child_node)

	get_hosting_check_entities_result = \
			hosting_object.get_hosting_check_entities()
	child_node = elem('checks', [
		transform_to_xml_hosting_check_entity(hosting_check_entity)
		for hosting_check_entity 
		in get_hosting_check_entities_result.hosting_check_entities
	])
	node.append(child_node)

def transform_to_xml_hosting_check_entity(hosting_check_entity):
	"""Transform hosting check entity - a single entity passed to checker
	
	Arguments:
	- hosting_check_entity - check entity
	
	Examples of check entities: 
	- hosting_check.DomainWebService
	- hosting_check.DomainDNSService
	- hosting_check.DomainMailService
	- hosting_check.DomainFTPService
	"""
	if isinstance(hosting_check_entity, Entity):
		prop_nodes = []
		for prop in hosting_check_entity.properties:
			if type(prop.type) != PropertyType:
				prop_value = getattr(hosting_check_entity, prop.name)
				prop_node = transform_to_xml_prop(prop.name, prop_value)
				if prop_node is not None:
					prop_nodes.append(
						prop_node
					)
			
		return elem(entity_node_name(hosting_check_entity), prop_nodes)
	else:
		return None # not serializable

def entity_node_name(entity):
	entity_class = entity.__class__.__name__
	return {
		DomainWebService: 'domain-web-service',
		DomainMailService: 'domain-mail-service',
		User: 'user',
		DomainFTPService: 'domain-ftp-service',
		DomainRDPService: 'domain-rdp-service',
		DomainSSHService: 'domain-ssh-service',
		DomainDNSService: 'domain-dns-service',
		DNSRecord: 'dns-record',
	}[type(entity)]

def transform_to_xml_prop(prop_name, prop_value):
	"""Transform property of hosting check entity to XML"""
	if prop_value is not None:
		tag_name = prop_name.replace('_', '-')
		if isinstance(prop_value, basestring):
			return text_elem(tag_name, prop_value)
		elif isinstance(prop_value, list):
			return elem(tag_name, [
				transform_to_xml_prop(prop_name, e)
				for e in prop_value
			])
		elif isinstance(prop_value, Entity):
			return transform_to_xml_hosting_check_entity(prop_value)

if __name__ == '__main__':
	main()
