from parallels.plesk.source.expand import messages
import logging
from collections import defaultdict
from contextlib import closing
import itertools

from parallels.core import target_data_model as ppa
from parallels.core.utils.common import group_by_id, obj
from parallels.core.checking import Problem, Report
from parallels.ppa.converter.converter import PPAConverter
from parallels.core.dump.data_model import Client as PleskClient, Password
from parallels.core.logging_context import log_context
from parallels.core.converter.business_objects.common import check_client_contacts_matching_source_panels, \
	check_client_contacts_matching_source_target, SOURCE_TARGET


class ExpandToPPAClientsAndSubscriptionsConverter(PPAConverter):
	logger = logging.getLogger('%s.Converter' % __name__)
	ppa_admin_login = 'ppa-admin'

	def __init__(self, admin_user_password, target_existing_objects, expand_objects, options, multiple_webspaces=False):
		PPAConverter.__init__(self, admin_user_password, target_existing_objects, options, multiple_webspaces)
		self.expand_clients, self.clients_plesk_to_expand = self._extract_expand_plesk_clients(expand_objects)
		self.expand_resellers = expand_objects.expand_resellers
		self.clients_assignments = self._extract_plesk_clients_assignments(expand_objects)

	@staticmethod
	def _extract_expand_plesk_clients(model):
		# build expand_clients dictionary
		expand_clients = dict()
		for expand_client in model.expand_clients:
			plesk_personal_info = dict()
			for key, value in expand_client.personal_info.iteritems():
				properties_mapping = {'cname': 'company', 'pcode': 'zip'}
				if key in properties_mapping:
					new_key = properties_mapping[key]
				else:
					new_key = key
				plesk_personal_info[new_key] = value

			plesk_client = PleskClient(
				expand_client.info['login'], Password('plain', expand_client.info['passwd']),
				expand_client.personal_info['pname'],
				subscriptions=[], personal_info=plesk_personal_info,
				auxiliary_users=[], auxiliary_user_roles=[],
				is_enabled=expand_client.info['status'] == '0'
			)
			expand_clients[expand_client.info['login']] = plesk_client

		# build clients_plesk_to_expand dictionary
		clients_plesk_to_expand = dict()
		expand_clients_by_id = group_by_id(model.expand_clients, lambda c: c.id)
		for plesk_client in model.plesk_clients:
			if plesk_client.expand_client_id in expand_clients_by_id:
				clients_plesk_to_expand[(
					plesk_client.plesk_id,
					plesk_client.login
				)] = expand_clients_by_id[plesk_client.expand_client_id].info['login']

		return expand_clients, clients_plesk_to_expand

	@staticmethod
	def _extract_plesk_clients_assignments(model):
		plesk_clients_by_id = group_by_id(model.plesk_clients, lambda c: c.id)
		expand_resellers_by_id = group_by_id(model.expand_resellers, lambda r: r.id)

		clients_assignments = defaultdict(list)
		for client_assignment in model.assigned_plesk_clients:
			if client_assignment.client_id not in plesk_clients_by_id:
				# if assigned clients are from a Plesk server not being migrated - skip them
				continue
			
			login = plesk_clients_by_id[client_assignment.client_id].login
			if login == 'admin':
				login = ExpandToPPAClientsAndSubscriptionsConverter.ppa_admin_login
			clients_assignments[(
				plesk_clients_by_id[client_assignment.client_id].plesk_id,
				login
			)].append(obj(
				reseller_login=expand_resellers_by_id[client_assignment.reseller_id].login,
				assignee_id=client_assignment.assignee_id,
				assignee_type=client_assignment.assignee_type
			))

		return clients_assignments

	def extract_aux_users(self, cmail_servers, root_report, password_holder):
		"""Called directly by Expand migrator"""
		for cmail_server in cmail_servers:
			self.logger.info(messages.EXTRACT_AUX_USERS_FROM_CENTRALIZED_MAIL, cmail_server.id)
			server_report = root_report.subtarget(u"Centralized mail server", cmail_server.id)
			with closing(cmail_server.load_raw_dump()) as backup:
				self._extract_aux_users_single_cmail(backup, cmail_server.id, server_report, password_holder)

	def _extract_aux_users_single_cmail(self, backup, cmail_server_id, report, password_holder):
		def add_no_client_found_issues(cl_report, client_login, aux_users):
			for aux_user in aux_users:
				# in centralized mail, there are domain administrators and mail users
				if aux_user.is_domain_admin:
					cl_report.add_issue(
						Problem(
							'cmail_misconfiguration',
							Problem.WARNING,
							messages.DOMAIN_UNABLE_TO_FILE_PLESK_SERVER_FOR_CMAIL_SERVER % (
								aux_user.name, client_login, cmail_server_id
							)
						),
						messages.DOMAIN_ADMIN_WILL_NOT_BE_TRANSFERRED % (
							aux_user.name
						)
					)
				else:
					# mail user
					cl_report.add_issue(
						Problem(
							'cmail_misconfiguration',
							Problem.WARNING,
							messages.MAIL_USER_UNABLE_TO_FILE_PLESK_SERVER_FOR_CMAIL_SERVER % (
								aux_user.name, client_login, cmail_server_id
							)
						),
						messages.PANEL_ACCESS_WILL_NOT_BE_TRANSFERRED % (
							aux_user.name
						)
					)

		# Plesk's clients and resellers both can have mail in centralized mail.
		# How: they have a client account in centralized mail, with same login as reseller or client has in Plesk,
		# and that client account holds mail users for all domains of this client.
		# In addition, that client holds domain administrators for all domains - we won't transfer them 
		# considering that main server's domain administrators should be transferred only.

		# Plesk admin could have mail in centralized mail (as a client with same login - 'admin'),
		# but centralized mail cannot add such client account.

		# So, extracting mail users only for client accounts:

		for client in backup.iter_clients():
			with log_context(client.login):
				self.logger.debug(messages.EXTRACT_AUX_USERS, client.login)

				client_report = report.subtarget(u"Client", client.login)

				ppa_client = self._get_ppa_client(client.login)
				if ppa_client is not None:
					self._add_auxiliary_user_roles(
						ppa_client, [r for r in client.auxiliary_user_roles if not r.is_domain_admin], client_report
					)
					self._add_auxiliary_users(
						ppa_client.login, [u for u in client.auxiliary_users if not u.is_domain_admin],
						client_report, password_holder
					)
				else:
					add_no_client_found_issues(client_report, client.login, client.auxiliary_users)

	def _get_ppa_client(self, login):
		all_clients_flat = dict(self.ppa_clients)
		all_clients_flat.update({rc.login: rc for r in self.ppa_resellers.itervalues() for rc in r.clients})
		return all_clients_flat.get(login)

	def _add_client(self, plesk_client, plesk_id, plain_report, password_holder):
		client_report = plain_report.get_customer_report(plesk_id, plesk_client.login)
		if (plesk_id, plesk_client.login) in self.clients_plesk_to_expand:
			expand_client = self.expand_clients[self.clients_plesk_to_expand[(plesk_id, plesk_client.login)]]
		else:
			expand_client = None
		client = expand_client or plesk_client

		if client.login in self.existing_objects.customers:
			existing_client = self.existing_objects.customers[client.login]
			# create client object for easy comparison
			new_client = self._create_ppa_client(
				client, None, Report('', ''), password_holder
			)
			check_client_contacts_matching_source_target(new_client, existing_client, client_report)

			# already added PPA client from previously processed backups
			already_added = client.login in self.raw_target_clients and self.raw_target_clients[client.login].source == 'ppa'
			if not already_added:
				ppa_client = ppa.Client(
					login=client.login, password=None, subscriptions=[],
					company=None, personal_info=None, auxiliary_user_roles=[], auxiliary_users=[],
					is_enabled=True,
					source=SOURCE_TARGET,
					target_client=None,
				)
				self.raw_target_clients[client.login] = ppa_client
			else:
				ppa_client = self.raw_target_clients[client.login]
		elif client.login in self.raw_target_clients:
			existing_client = self.raw_target_clients[client.login]

			# if new client is not linked with Expand client but existing client is linked,
			# or new client is linked with Expand client but existing client is not linked:
			if (
				self.clients_plesk_to_expand.get(plesk_id, client.login) !=
				self.clients_plesk_to_expand.get(existing_client.source, existing_client.login)
			):
				client_report.add_issue(
					Problem(
						'duplicate_customer_name',
						Problem.ERROR,
						messages.CLIENT_EXISTS_ON_ANOTHER_PLESK),
					messages.SOLUTION_CHANGE_USERNAME_OF_CLIENT_OR_REASSIGN)

			# now both (new and existing) Plesk clients are either linked with Expand client or not
			# if not linked:
			elif expand_client is None:
				# create new client object for easy comparison
				new_client = self._create_ppa_client(client, None, Report('', ''), password_holder)
				check_client_contacts_matching_source_panels(new_client, existing_client, client_report)

			ppa_client = existing_client
		else:
			ppa_client = self._create_ppa_client(
				client, 'expand' if expand_client is not None else plesk_id, client_report, password_holder
			)
			self.raw_target_clients[client.login] = ppa_client

		return ppa_client

	def _make_special_account_if_needed(self, acc_login, reseller_login, clients, resellers, first_plesk_id, plain_report):
		"""Overrides function of Plesks' converter

		Differences:
			1. Search in Plesk clients and Plesk resellers alike
			2. Do not count Plesk logins when Plesk client is assigned to an Expand client.
			In this case, use Expand client's login.

		NOTE: If the Plesk login is different from corresponding Expand login, and acc_login equals to Plesk login,
		create a special account, regardless of that we could get its information
		from Plesk and thus create a regular account.
		Why: when Plesk account is assigned to an Expand client, only the information from Expand is authoritative.
		If the user wants account information transferred in this case, he must specify Expand account's login.
		"""
		regular_logins = []
		for login, (_, plesk_id) in itertools.chain(clients.iteritems(), resellers.iteritems()):
			if (plesk_id, login) in self.clients_plesk_to_expand:
				regular_logins.append(self.clients_plesk_to_expand[(plesk_id, login)])
			else:
				regular_logins.append(login)
		
		if acc_login not in regular_logins:
			self._add_special_ppa_client(acc_login, reseller_login, first_plesk_id, plain_report)

	def _add_client_if_needed(self, plesk_client, plesk_id, customers_mapping, plain_report, password_holder):
		if (plesk_id, plesk_client.login) in self.clients_plesk_to_expand:
			expand_client = self.expand_clients[self.clients_plesk_to_expand[(plesk_id, plesk_client.login)]]
		else:
			expand_client = None
		if expand_client is not None:
			super(ExpandToPPAClientsAndSubscriptionsConverter, self)._add_client_if_needed(
				expand_client, 'expand', customers_mapping, plain_report, password_holder
			)
		else:
			super(ExpandToPPAClientsAndSubscriptionsConverter, self)._add_client_if_needed(
				plesk_client, plesk_id, customers_mapping, plain_report, password_holder
			)

	def _make_resellers_client_and_subscriptions(
		self, plesk_id, is_windows, mail_is_windows, plain_report, backup, password_holder
	):
		"""Overrides function in Plesks' converted
		Difference: additionally, add Plesk resellers as usual PPA clients
		"""
		for reseller in backup.iter_resellers():
			self._add_client(reseller, plesk_id, plain_report, password_holder)
			for subscription in reseller.subscriptions:
				self._add_subscription(subscription, plesk_id, is_windows, mail_is_windows, plain_report, backup)

	@staticmethod
	def _format_source(source):
		if source == SOURCE_TARGET:
			return messages.TITLE_SOURCE_TARGET
		elif source == 'expand':
			return messages.TITLE_EXPAND
		else:
			return messages.TITLE_PLESK % (source,)
