import re
import codecs
from collections import namedtuple
from StringIO import StringIO

from parallels.common.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.plesk_backup_iter)

		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(u"Line %s: subscription '%s' is already defined" % (line_number, subscription))
					return False
				elif subscription not in source_plesk_subscriptions:
					self.errors.append(u"Line %s: client subscription '%s' is not present on source Plesks" % (line_number, subscription))
					return False
				elif source_plesk_subscriptions[subscription] not in source_data.accounts:
					self.errors.append(u"Line %s: subscription '%s' does not belong to any Plesk user listed in PBAS data file" % (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):
			pass # skip empty lines

		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, target_panel, 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 = u"""# List subscriptions to migrate in this file.
# If you don't want to migrate a subscription either remove it from this file, or comment out with "#"."""
	
	@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.plesk_backup_iter, 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(
						u"# %s # already exists in the target panel "
						% 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, plesk_backup_iter, subscription_filter=None):
		subscriptions = {}  # { subscription_name: plesk_login}
		for _, backup in plesk_backup_iter():
			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, plesk_backup_iter, ppa_webspace_names, ppa_service_templates,
		target_panel
	):
		initial_mapping_text = cls._generate_initial_content(
			plesk_backup_iter, ppa_webspace_names, ppa_service_templates,
			target_panel
		)
		result, errors = cls.read(StringIO(initial_mapping_text), plesk_backup_iter)

		# Put all clients and resellers into mapping so they are always fetched in Plesk backup dumps
		for _, backup in plesk_backup_iter.plesk_backup_iter():
			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
))
