from parallels.ppa.source.pbas import messages
import logging
from collections import defaultdict
from contextlib import closing

from parallels.core import target_data_model as ppa
from parallels.core.utils.common import group_by_id, if_not_none
from parallels.core.checking import Problem
from parallels.ppa.converter.converter import PPAConverter
from parallels.core.converter.business_objects.common import check_domain_conflicts, index_plesk_backup_domain_objects, \
	SOURCE_TARGET


class Converter(PPAConverter):
	"""
	Conversion rules:
	* ignore Plesk plans, convert only PPA service templates; do not check Plesk plans conflicts and ownership
	* ignore Plesk resellers, convert only PPA resellers; do not check Plesk resellers conflicts
	* ignore Plesk clients, convert only PPA customers; do not check Plesk clients conflicts
	* convert only Plesk customer subscriptions (but customer may be under admin as well as under reseller);
	must not check that client and plan have same owner, in PBAS it is possible for reseller to subscribe
	own client to admin's template
	* ignore Plesk admin's hosting subscriptions
	"""
	logger = logging.getLogger('%s.Converter' % __name__)
	ppa_admin_login = None	# not going to migrate admin's subscriptions

	def __init__(self, target_existing_objects, pbas_data, options):
		PPAConverter.__init__(self, None, target_existing_objects, options)
		self.pbas_data = dict(pbas_data)	# { plesk_login: ppa_login, ppa_st_id, ppa_group_name }
		# all aux users, to check if such aux user already exists on other Plesk
		self.raw_aux_users = defaultdict(list)	# { client_login: [aux_user1,...] }

	def convert_dumps(
		self, plesks, plain_report, subscriptions_mapping, password_holder,
		mail_servers=dict()
	):
		for plesk_info in plesks:
			self.logger.info(messages.LOG_CONVERT_DUMP)
			with closing(plesk_info.load_raw_dump()) as backup:
				self._convert_dump(
					plesk_info.id, backup, plesk_info.ip, plain_report,
					plesk_info.is_windows, plesk_info.is_windows,
					subscriptions_mapping, password_holder)

		# check if all Plesk accounts specified in migration_data.xml are found in Plesk backups
		for plesk_login, data in self.pbas_data.iteritems():
			if data.source is None:
				plain_report.add_customer_issue(
					data.source, plesk_login,
					Problem(
						'nonexistent_plesk_account', Problem.ERROR,
						messages.ACCOUNT_DOES_NOT_EXIST % plesk_login
					),
					messages.ADD_PLESK_SERVER_WITH_ACCOUNT
				)

		# group PPA account subscriptions and check limits
		for _, ppa_client in self.ppa_clients.iteritems():
			self._group_subscriptions(ppa_client)
			self._check_group_limit(ppa_client.subscriptions, plain_report)
			self._check_service_template_limit(ppa_client.subscriptions, None, plain_report)

	def _convert_dump(
		self, plesk_id, backup, ip, plain_report, is_windows,
		mail_is_windows, subscriptions_mapping, password_holder
	):
		def add_all_subscriptions(plesk_account, ppa_client, ppa_group_name, ppa_st_id, report):
			subscriptions = plesk_account.subscriptions
			if hasattr(plesk_account, 'clients'):
				for client in plesk_account.clients:
					subscriptions += client.subscriptions

			for subscription in subscriptions:
				if subscription.name not in subscriptions_mapping:
					self.logger.debug(messages.SKIP_SUBSCRIPTION_ACCORDING_TO_MIGRATION_LIST, subscription.name)
				else:
					self.logger.debug(messages.DEBUG_CONVERT_SUBSCRIPTION, subscription.name)
					sub_report = plain_report.get_subscription_report(plesk_id, subscription.name)
					self._add_subscription(
						plesk_id, ppa_client, subscription, backup, sub_report,
						is_windows, mail_is_windows, ppa_st_id, ppa_group_name
					)
					if ppa_client.login not in self.existing_objects.customers:
						sub_report.add_issue(
							Problem(
								'nonexistent_ppa_account', Problem.ERROR,
								messages.UNABLE_TO_TRANSFER_SUBSCRIPTION_CUSTOMER_DOES_NOT_EXIST_IN_PPA % (
									subscription.name, ppa_client.login
								)
							),
							messages.CREATE_PPA_CUSTOMER_ACCOUNT
						)

		def add_all_aux_users(plesk_account, ppa_client, report):
			aux_users = [
				aux_user for aux_user in plesk_account.auxiliary_users
				if not aux_user.is_built_in  # skip login that is auto-generated by PBAS
			]
			# { login: subscription } *subscription* is the name of a subscription to which login must be bound
			aux_users_bindings = {}
			if hasattr(plesk_account, 'clients'):
				for client in plesk_account.clients:
					if len(client.subscriptions) == 0:
						# if we transfer panel access for client without subscriptions,
						# he will have access to all webspaces (not own) - therefore we do not transfer it.
						continue
					aux_users += client.auxiliary_users
					subscription_name = client.subscriptions[0].name
					aux_users_bindings.update({user.name: subscription_name for user in client.auxiliary_users})

			for aux_user in aux_users:
				issues, converted_user = self._make_auxiliary_user(ppa_client.login, aux_user, password_holder)
				if converted_user is not None:
					subscription_name = aux_users_bindings.get(aux_user.name)
					if subscription_name is not None:
						converted_user.subscription_name = subscription_name
					self.raw_aux_users[ppa_client.login].append(converted_user)
					ppa_client.auxiliary_users.append(converted_user)

					if ppa_client.login not in self.existing_objects.customers:
						issues.append((
							Problem(
								'nonexistent_ppa_account', Problem.ERROR,
								messages.UNABLE_TO_TRANSFER_AUXILIARY_USER % (converted_user.login, ppa_client.login)
							),
							messages.CREATE_PPA_CUSTOMER_ACCOUNT))

				for issue in issues:
					report.add_issue(*issue)

		def add_all_aux_roles(plesk_account, ppa_client, report):
			self._add_auxiliary_user_roles(ppa_client, plesk_account.auxiliary_user_roles, report)
			if hasattr(plesk_account, 'clients'):
				for client in plesk_account.clients:
					self._add_auxiliary_user_roles(ppa_client, client.auxiliary_user_roles, report)

		def make_ppa_client(customer_login):
			if customer_login not in self.ppa_clients:
				ppa_client = ppa.Client(
					login=customer_login,
					source=SOURCE_TARGET,
					target_client=None,
					personal_info=ppa.PersonalInfo(
						email=None, first_name=None, last_name=None,
						preferred_email_format=None, address_line_1=None,
						address_line_2=None, city=None, county=None,
						state=None, postal_code=None, language_code=None,
						locale=None, country_code=None, primary_phone=None,
						additional_phone=None, fax=None, mobile_phone=None,
					),
					password=None, subscriptions=[], company=None,
					auxiliary_user_roles=[], auxiliary_users=[], is_enabled=None,
				)
				self.ppa_clients[customer_login] = ppa_client
			else:
				ppa_client = self.ppa_clients[customer_login]
			return ppa_client

		resellers_by_login = group_by_id(backup.iter_resellers(), lambda r: r.login)
		clients_by_login = group_by_id(backup.iter_all_clients(), lambda c: c.login)
		users_by_login = dict(resellers_by_login.items() + clients_by_login.items())

		for plesk_login, data in self.pbas_data.iteritems():
			if plesk_login not in users_by_login:
				continue

			if data.source is None:
				data.source = plesk_id

			ppa_client = make_ppa_client(data.ppa_login)
			if plesk_login in resellers_by_login:
				account_report = plain_report.get_reseller_report(plesk_id, plesk_login)
			else:
				account_report = plain_report.get_customer_report(plesk_id, plesk_login)
			add_all_subscriptions(
				plesk_account=users_by_login[plesk_login], ppa_client=ppa_client,
				ppa_group_name=data.ppa_group_name, ppa_st_id=data.ppa_st_id, report=account_report
			)

			add_all_aux_roles(plesk_account=users_by_login[plesk_login], ppa_client=ppa_client, report=account_report)
			add_all_aux_users(plesk_account=users_by_login[plesk_login], ppa_client=ppa_client, report=account_report)

	@classmethod
	def convert_resellers(cls, source_panel_data, existing_ppa_resellers, resellers_migration_list, report, password_holder):
		cls.logger.info(messages.LOG_CONVERT_RESELLERS)

		converted_resellers = []
		for reseller in existing_ppa_resellers.itervalues():
			if resellers_migration_list is None or reseller.contact.username in resellers_migration_list:
				converted_resellers.append(cls._convert_ppa_reseller(reseller))
		return converted_resellers

	# TODO make it even more flat
	# 1) need to check if subscription conflicts with: existing in ppa, existing in sources, existing sites, existing subdomains
	# 2) need to form up subscription with proper source
	def _add_subscription(
		self, plesk_id, ppa_client, new_subscription, backup, report, is_windows, mail_is_windows, plan_id, group_name
	):
		required_hostings = PPAConverter._get_required_hostings(new_subscription, is_windows)
		if self.options.allocate_only_required_resources:
			desired_hostings = required_hostings
		else:
			if is_windows:
				desired_hostings = required_hostings + ['iis', 'mysql', 'mssql']
			else:
				desired_hostings = required_hostings + ['apache', 'mysql', 'postgresql']

		webspaces_by_name = group_by_id(self.existing_objects.webspaces, lambda ws: ws.name)
		if new_subscription.name in webspaces_by_name:
			webspace = webspaces_by_name[new_subscription.name]
			ppa_subscriptions_by_id = group_by_id(self.existing_objects.raw_target_subscriptions, lambda s: s.subscription_id)
			ppa_subscription = ppa_subscriptions_by_id[webspace.subscription_id]
			if webspace.owner_id != if_not_none(self.existing_objects.customers.get(ppa_client.login, None), lambda c: c.id):
				existing_customer_username_by_id = dict(
					(customer.id, customer.contact.username) for customer in self.existing_objects.customers.values()
				)
				problem = Problem(
					'subscription_owned_by_another_customer', Problem.WARNING,
					messages.SUBSCRIPTION_ALREADY_EXISTS_IN_PPA_FOR_OTHER_CUSTOMER % (
						new_subscription.name, 
						existing_customer_username_by_id[webspace.owner_id],
					)
				)
			else:
				problem = Problem(
					'duplicate_subscription_name', Problem.WARNING,
					messages.SUBSCRIPTION_ALREADY_EXISTS_IN_PPA % new_subscription.name
				)

			solution_msg = messages.SUBSCRIPTION_WILL_BE_OVERWRITTEN
			report.add_issue(problem, solution_msg)

			sub = ppa.Subscription(
				new_subscription.name, plan_name=None, plan_id=plan_id,
				addon_plan_ids=[],
				web_ip=None,
				web_ip_type=new_subscription.ip_type,
				web_ipv6=None,
				web_ipv6_type=new_subscription.ipv6_type,
				is_enabled=True,
				# XXX: source=plesk_id is logically wrong
				source=plesk_id, is_windows=is_windows,
				mail_is_windows=is_windows,
				sub_id=webspace.webspace_id, group_name=ppa_subscription.name, group_id=webspace.subscription_id,
				required_resources=[], additional_resources=[],
				sysuser_login=None
			)
			ppa_client.subscriptions.append(sub)
			self.ppa_subscriptions[sub.name] = sub

		elif new_subscription.name in self.ppa_subscriptions:
			sub = self.ppa_subscriptions[new_subscription.name]
			report.add_issue(
				Problem(
					'duplicate_subscription_name', Problem.ERROR,
					messages.SUBSCRIPTION_ALREADY_EXISTS_ON_OTHER_SERVER % (new_subscription.name, sub.source)
				),
				messages.RENAME_ONE_SUBSCRIPTION_OR_EXCLUDE)

		elif new_subscription.name in self.existing_site_names:
			sub_name, obj_type = self.existing_site_names[new_subscription.name]
			report.add_issue(
				Problem(
					'duplicate_site_name', Problem.ERROR,
					messages.ALREADY_EXISTS_IN_PPA % (obj_type, new_subscription.name, sub_name)
				),
				messages.RENAME_SUBSCRIPTION_OR_EXCLUDE % (obj_type, new_subscription.name)
			)

		elif new_subscription.name in self.ppa_site_names:
			sub_name, obj_type = self.ppa_site_names[new_subscription.name]
			report.add_issue(
				Problem(
					'duplicate_site_name', Problem.ERROR,
					messages.ALREADY_EXISTS_OTHER_SERVER % (obj_type, new_subscription.name, sub_name)
				),
				messages.RENAME_SUBSCRIPTION_OR_EXCLUDE % (obj_type, new_subscription.name)
			)

		else:
			# TODO refine checks, as it looks like above checks are partially the same as the following
			problems = check_domain_conflicts(
				backup, new_subscription.name,
				target_webspaces=self.existing_objects.webspaces,
				target_sites=self.existing_site_names,
				source_webspaces=self.raw_target_subscriptions,
				source_sites=self.ppa_site_names,
			)
			if not problems:
				ppa_subscription = ppa.Subscription(
					new_subscription.name, plan_name=None, plan_id=plan_id,
					addon_plan_ids=[],
					web_ip=None,
					web_ip_type=new_subscription.ip_type,
					web_ipv6=None,
					web_ipv6_type=new_subscription.ipv6_type,
					is_enabled=new_subscription.is_enabled,
					source=plesk_id, is_windows=is_windows,
					mail_is_windows=is_windows,
					sub_id=None, group_name=group_name,
					group_id=None, required_resources=required_hostings, additional_resources=desired_hostings,
					sysuser_login=None
				)
				ppa_client.subscriptions.append(ppa_subscription)
				self.ppa_subscriptions[ppa_subscription.name] = ppa_subscription
				self._check_subscription_dns_zones(backup, new_subscription.name, report)
				domain_objects = index_plesk_backup_domain_objects(backup, new_subscription.name) 
				for name, kind in domain_objects:
					self.ppa_site_names[name] = (new_subscription.name, kind)
			else:
				for problem in problems:
					report.add_issue(problem, messages.CHANGE_CONFLICTING_NAMES)
