import json
import uuid
import os
import logging
from parallels.common import MigrationError

from parallels.common.utils.yaml_utils import read_yaml
from xml.etree import ElementTree
from parallels.common.plesk_backup import save_backup_tar
from parallels.utils import xml, if_not_none, group_by_id
from parallels.utils.xml import elem, text_elem, seq, seq_iter, xml_to_string_pretty


logger = logging.getLogger(__name__)


class BackupCreator(object):
	def write_full_backup(self, input_filename, input_format, target_backup_filename):
		input_formats_by_name = group_by_id(INPUT_FORMATS, lambda f: f.name)
		if input_format not in input_formats_by_name:
			raise MigrationError("Invalid file format specified for light backup file '%s'" % input_filename)
		data = input_formats_by_name[input_format].read(input_filename)
		self._write_full_backup_for_data(data, target_backup_filename)

	def _write_full_backup_for_data(self, data, target_backup_filename):
		backup_tree = ElementTree.ElementTree(
			xml.elem('migration-dump', [], {
				'content-included': 'false',
				'agent-name': 'PleskX',
				'dump-format': 'panel',
				'dump-version': '11.0.9'
			})
		)
		root = backup_tree.getroot()
		root.append(xml.elem('admin', [], {'guid': str(uuid.uuid1())}))
		root.append(xml.elem('server'))
		root.find('admin').append(xml.elem('clients', [
			self._client_node(client) for client in data
		]))

		self._save(backup_tree, target_backup_filename)

	def _client_node(self, client):
		return elem(
			'client',
			[
				self._client_preferences_node(),
				self._client_properties_node(),
				self._client_domains_node(client)
			],
			{
				'name': client['login']
			}
		)

	def _client_preferences_node(self):
		return elem('preferences', [
			text_elem('pinfo', 'Test', {'name': 'name'})
		])

	def _client_properties_node(self):
		return elem('properties', [
			text_elem('password', '123qwe', {'type': 'plain'}),
			elem('status', [elem('enabled')])
		])

	def _client_domains_node(self, client):
		return elem('domains', [self._domain_node(domain) for domain in client['webspaces']])

	def _domain_node(self, domain):
		return elem(
			'domain', seq(
				elem('preferences'),
				self._domain_properties_node(),
				self._mailsystem_node(domain),
				self._databases_node(domain),
				self._phosting_node(domain)
			),
			{'www': 'false', 'name': domain['name']}
		)

	def _domain_properties_node(self):
		return elem(
			'properties',
			[
				elem('ip', [
					text_elem('ip-type', 'shared'),
					text_elem('ip-address', '127.0.0.1')
				]),
				elem('status', [elem('enabled')])
			]
		)

	def _mailsystem_node(self, domain):
		return elem(
			'mailsystem', seq(
				elem('properties', [elem('status', [elem('enabled')])]),
				self._mailusers_node(domain),
				elem('preferences'),
			)
		)

	def _mailusers_node(self, domain):
		mailboxes = domain.get('mailboxes')
		if mailboxes is None:
			return None
		else:
			return elem('mailusers', [
				self._mailuser_node(mailbox) for mailbox in mailboxes
			])

	def _mailuser_node(self, mailbox):
		return elem('mailuser',
			[
				elem('properties', [self._password_node('123qwe')]),
				elem('preferences', [
					elem('mailbox', [], {'enabled': 'true', 'type': 'mdir'})
				])
			],
			{
				'name': self._short_mailbox_name(mailbox['name']),
				'forwarding-enabled': 'false',
			}
		)

	def _password_node(self, password):
		return text_elem('password', password, {'type': 'plain'})

	def _phosting_node(self, domain):
		return elem(
			'phosting',
			seq(
				elem('preferences', [
					elem(
						'sysuser', [
							text_elem('password', '123qwe', {'type': 'plain'})
						],
						{
							'name': domain['sys_user']['login']
						}
					)
				]),
				elem('limits-and-permissions', [elem('scripting')]),
				self._sites_node(domain)
			),
			{
				'www-root': 'httpdocs'
			}
		)

	def _sites_node(self, domain):
		addon_domains = domain.get('sites', [])
		subdomains = domain.get('subdomains', [])
		if len(addon_domains) + len(subdomains) == 0:
			return None
		else:
			return elem(
				'sites',
				(
					seq_iter(self._addon_domain_node(addon_domain) for addon_domain in addon_domains) +
					seq_iter(self._subdomain_node(domain, subdomain) for subdomain in subdomains)
				)
			)

	def _addon_domain_node(self, site):
		return elem(
			'site',
			[
				elem('preferences'),
				self._site_phosting_node()
			],
			{
				'name': site['name']
			}
		)

	def _subdomain_node(self, domain, subdomain):
		parent_name = self._get_parent(domain, subdomain)
		return elem(
			'site',
			[
				elem('preferences'),
				self._site_phosting_node()
			],
			{
				'name': subdomain['name'],
				'parent-domain-name': parent_name
			}
		)

	def _get_parent(self, domain, subdomain):
		all_domains = [domain['name']] + [addon['name'] for addon in domain.get('sites', [])]
		for domain_name in all_domains:
			if subdomain['name'].endswith('.%s' % domain_name):
				return domain_name

	def _site_phosting_node(self):
		return elem(
			'phosting',
			seq(elem('preferences')),
			{'www-root': 'httpdocs'}
		)

	@staticmethod
	def _short_mailbox_name(mailbox_name):
		if '@' in mailbox_name:
			return mailbox_name[:mailbox_name.find('@')]
		else:
			return mailbox_name

	def _databases_node(self, domain):
		databases = domain.get('databases', [])
		if len(databases) == 0:
			return None
		else:
			return elem('databases', seq_iter(
				self._database_node(database) for database in databases
			))

	def _database_node(self, database):
		return elem(
			'database',
			seq(
				elem(
					'db-server',
					seq(
						text_elem('host', database.get('server', 'localhost')),
						text_elem('port', '0'),
					),
					{
						'type': database.get('type', 'mysql')
					}
				),
				if_not_none(database.get('user'), self._database_user)
			),
			{
				'name': database['name'],
				'type': database.get('type', 'mysql')
			}
		)

	def _database_user(self, user):
		return elem(
			'dbuser',
			seq(
				self._password_node('123qwe')
			),
			{
				'name': user['login']
			}
		)

	@staticmethod
	def _save(backup_tree, target_backup_filename):
		if os.path.exists(target_backup_filename):
			logger.debug(u"Removing the existing backup file.")
			os.remove(target_backup_filename)

		backup_file_content = xml_to_string_pretty(backup_tree)
		save_backup_tar(backup_file_content, target_backup_filename)


class InputFormat(object):
	@property
	def name(self):
		raise NotImplementedError()

	def read(self, filename):
		raise NotImplementedError()


class InputFormatJSON(InputFormat):
	@property
	def name(self):
		return 'json'

	def read(self, filename):
		with open(filename) as fp:
			return json.loads(fp.read())


class InputFormatYAML(InputFormat):
	@property
	def name(self):
		return 'yaml'

	def read(self, filename):
		return read_yaml(filename)


class InputFormatXML(InputFormat):
	@property
	def name(self):
		return 'xml'

	def read(self, filename):
		root = ElementTree.parse(filename).getroot()

		def parse(node):
			list_nodes = {
				'clients': 'client',
				'webspaces': 'webspace',
				'sites': 'site',
				'subdomains': 'subdomain',
				'databases': 'database',
				'mailboxes': 'mailbox'
			}

			if node.tag in list_nodes:
				result = []

				for child in node:
					if child.tag != list_nodes[node.tag]:
						raise MigrationError("Invalid tag: %s. Expected: %s." % (child.tag, list_nodes[node.tag]))
					result.append(parse(child))

				return result
			else:
				if len(node) > 0:
					result = {}
					for child in node:
						result[child.tag] = parse(child)
					return result
				else:
					return node.text

		return parse(root)

INPUT_FORMATS = [InputFormatYAML(), InputFormatJSON(), InputFormatXML()]