from parallels.ppa.source.pbas import messages
import re
import codecs
from collections import namedtuple
from StringIO import StringIO

from parallels.core.migration_list import BaseMigrationListLineHandler


class MigrationList(object):
	allowed_labels = ['plan', 'customer', 'reseller']

	@classmethod
	def read(
		cls, fileobj, source_data,
		has_custom_subscriptions_feature=False,
		has_admin_subscriptions_feature=False,
		has_reseller_subscriptions_feature=False
	):
		source_plesk_subscriptions = cls._list_source_subscriptions(source_data.dump_iterator)

		class Handler(BaseMigrationListLineHandler):
			def __init__(self, allowed_labels):
				super(Handler, self).__init__(allowed_labels)
				self.data = MigrationListData(
					subscriptions_mapping={}, customers_mapping={}, resellers=set(), plans={None: set()}
				)

			def handle_subscription(self, subscription, line_number):
				if self._check_subscription(line_number, subscription):
					self.data.subscriptions_mapping[subscription] = None
	
			def _check_subscription(self, line_number, subscription):
				if subscription in self.data.subscriptions_mapping:
					self.errors.append(messages.SUBSCRIPTION_ALREADY_DEFINED % (line_number, subscription))
					return False
				elif subscription not in source_plesk_subscriptions:
					self.errors.append(
						messages.CLIENT_SUBSCRIPTION_IS_NOT_PRESENT_ON_SOURCE % (line_number, subscription)
					)
					return False
				elif source_plesk_subscriptions[subscription] not in source_data.accounts:
					self.errors.append(
						messages.SUBSCRIPTION_DOES_NOT_BELONG_TO_PLESK_USER % (line_number, subscription)
					)
					return False
				else:
					return True
	
		handler = Handler(cls.allowed_labels)
		cls._read(fileobj, handler)
		return handler.data, handler.errors

	@staticmethod
	def _read(fileobj, handler):
		def empty_line_handler(match, line_number):
			# skip empty lines
			pass

		line_handlers = [
			(u"^(#.*)?$", empty_line_handler),
			(u"^(\S*)\s*(#.*)?$", handler.handle_subscription),
		]

		for line_number, line in enumerate(fileobj, 1):
			line = line.strip(' \r\n')
	
			line_matched = False
			for regex, line_handler in line_handlers:
				match = re.match(regex, line)
				if match is not None:
					line_handler(match.group(1), line_number)
					line_matched = True
					break
	
			if not line_matched:
				handler.handle_syntax_error(line, line_number)

	@classmethod
	def read_resellers(cls, fileobj, source_data):
		"""This method should list the resellers participating in migration, without reading the source backups.
		In the migration_data.xml, there are Plesk logins, without indication whether it's a reseller login,
		or customer login.
		Considering the only use case of this method - to cut from backup all resellers not participating in migration,
		return all Plesk logins listed in migration_data.xml, as resellers. Similarly,
		return all Plesk logins listed in migration_data.xml, as customers.
		"""
		return (source_data.accounts.keys(), [])

	@classmethod
	def read_plans(cls, fileobj, source_data):
		raise NotImplementedError()
	
	@classmethod
	def write_initial(
		cls, filename, source_data, ppa_webspace_names, ppa_service_templates,
		include_addon_plans=False, target_addon_plans={}, include_source_plans=True
	):
		initial_mapping_text = cls._generate_initial_content(source_data, ppa_webspace_names, ppa_service_templates)
		with codecs.open(filename, "w", "utf-8") as f:
			f.write(initial_mapping_text)
	
	@classmethod
	def write_selected_subscriptions(
			cls, filename, source_data, ppa_webspace_names,
			ppa_service_templates, subscriptions_mapping,
			subscription_filter, pre_subscription_comments):
		initial_mapping_text = cls._generate_initial_content(
			source_data, ppa_webspace_names, ppa_service_templates, subscription_filter, pre_subscription_comments
		)
		with codecs.open(filename, "w", "utf-8") as f:
			f.write(initial_mapping_text)
	
	header = messages.MIGRATION_LIST_HEADER
	
	@classmethod
	def _generate_initial_content(
		cls, source_data, ppa_webspace_names, ppa_service_templates, 
		subscription_filter=None, pre_subscription_comments=None,
		include_source_plans=True
	):
		subscriptions = cls._list_source_subscriptions(source_data.dump_iterator, subscription_filter)
		pbas_subscriptions = set(
			subscription for subscription, account in subscriptions.iteritems() if account in source_data.accounts
		)
	
		blocks = []
	
		def get_subscriptions_lines(subscriptions):
			lines = []
			for subscription_name in set(subscriptions.keys()) & set(pbas_subscriptions):
				if pre_subscription_comments is not None and subscription_name in pre_subscription_comments:
					for comment in pre_subscription_comments[subscription_name]:
						lines.append(u'# %s' % (comment))

				if subscription_name in ppa_webspace_names:
					lines.append(messages.SUBSCRIPTION_ALREADY_EXISTS_IN_TARGET % subscription_name)
				else:
					lines.append(u"%s" % subscription_name)
			return lines
	
		blocks.append([cls.header])
		blocks.append(get_subscriptions_lines(subscriptions))

		return "\n\n".join("\n".join(lines) for lines in blocks) + "\n"
	
	@classmethod
	def _list_source_subscriptions(cls, dump_iterator, subscription_filter=None):
		subscriptions = {}  # { subscription_name: plesk_login}
		for _, backup in dump_iterator():
			for client in backup.iter_clients():
				subscriptions.update({subscription.name: client.login for subscription in client.subscriptions})
			for reseller in backup.iter_resellers():
				subscriptions.update({subscription.name: reseller.login for subscription in reseller.subscriptions})
				subscriptions.update({
					subscription.name: reseller.login
					for client in reseller.clients for subscription in client.subscriptions
				})
		return subscriptions

	@classmethod
	def generate_migration_list(
		cls, dump_iterator, ppa_webspace_names, ppa_service_templates,
	):
		initial_mapping_text = cls._generate_initial_content(
			dump_iterator, ppa_webspace_names, ppa_service_templates,
		)
		result, errors = cls.read(StringIO(initial_mapping_text), dump_iterator)

		# Put all clients and resellers into mapping so they are always fetched in Plesk backup dumps
		for _, backup in dump_iterator.dump_iterator():
			result.customers_mapping.update({
				customer.login: None for customer in backup.iter_all_clients()
			})
			result.resellers.update({
				reseller.login for reseller in backup.iter_resellers()
			})

		return result, errors

# TODO get rid of unneeded fields in MigrationListData
MigrationListData = namedtuple('MigrationListData', (
	'subscriptions_mapping',  # map of selected subscriptions' names to plan identifier
	'customers_mapping',  # customers mapping is formally required by Plesks migrator, but empty for PBAS case
	'resellers',  # set consisting of names of resellers that should be migrated
	'plans'  # dict with keys - reseller names (None for admin), values - sets consisting of names of plans that should be migrated
))
