import logging
from collections import defaultdict
from contextlib import closing
import itertools

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


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

	def __init__(self, admin_user_password, ppa_existing_objects, expand_objects, options, multiple_webspaces=False):
		PPAConverter.__init__(self, admin_user_password, ppa_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 c in model.assigned_plesk_clients:
			if c.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[c.client_id].login
			if login == 'admin':
				login = Converter.ppa_admin_login
			clients_assignments[(
				plesk_clients_by_id[c.client_id].plesk_id,
				login
			)].append(obj(
				reseller_login=expand_resellers_by_id[c.reseller_id].login,
				assignee_id=c.assignee_id,
				assignee_type=c.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(u"Extract auxiliary users from centralized mail '%s'", cmail_server.id)
			server_report = root_report.subtarget(u"Centralized mail server", cmail_server.id)
			with closing(cmail_server.load_raw_backup()) 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(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:
					client_report.add_issue(
						Problem('cmail_misconfiguration', Problem.WARNING, u"Domain '%s' belongs to client '%s' in centralized mail server '%s', but no such client found in Plesk servers" % (aux_user.name, client_login, cmail_server_id)),
						u"Domain administrator '%s' will not be transferred to PPA. You can create corresponding additional user in PPA hosting panel after transfer." % aux_user.name
					)
				else:	# mail user
					client_report.add_issue(
						Problem('cmail_misconfiguration', Problem.WARNING, u"Mail user '%s' belongs to client '%s' in centralized mail server '%s', but no such client found in Plesk servers" % (aux_user.name, client_login, cmail_server_id)),
						u"Panel access for the mail user '%s' will not be transferred to PPA. You can create corresponding additional user in PPA hosting panel after transfer." % 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(u"Extract auxiliary users for client '%s'", 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)
		expand_client = self.expand_clients[self.clients_plesk_to_expand[(plesk_id, plesk_client.login)]] if (plesk_id, plesk_client.login) in self.clients_plesk_to_expand else None
		client = expand_client or plesk_client

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

			already_added = client.login in self.raw_ppa_clients and self.raw_ppa_clients[client.login].source == 'ppa' # already added PPA client from previously processed backups
			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_ppa_clients[client.login] = ppa_client
			else:
				ppa_client = self.raw_ppa_clients[client.login]
		elif client.login in self.raw_ppa_clients:
			existing_client = self.raw_ppa_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, u"A client with the same username already exists on another Plesk server but is associated with another Expand client"),
					u"Either change username of this client or associate both Plesk clients with the same Expand client."
				)

			# now both (new and existing) Plesk clients are either linked with Expand client or not
			# if not linked:
			elif expand_client is None:
				new_client = self._create_ppa_client(client, None, Report('', ''), password_holder) # just for easy comparison
				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_ppa_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):
		expand_client = self.expand_clients[self.clients_plesk_to_expand[(plesk_id, plesk_client.login)]] if (plesk_id, plesk_client.login) in self.clients_plesk_to_expand else None
		if expand_client is not None:
			super(Converter, self)._add_client_if_needed(expand_client, 'expand', customers_mapping, plain_report, password_holder)
		else:
			super(Converter, 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 == 'ppa':
			return u'PPA'
		elif source == 'expand':
			return u'Expand'
		else:
			return u"Plesk '%s'" % (source,)

class ExpandResellersConverter(ResellersConverter):
	@classmethod
	def _convert_source_panel_resellers(cls, source_panel_data, resellers_migration_list, report, password_holder):
		cls.logger.info(u"Convert Expand resellers")
		expand_resellers = source_panel_data
		converted_resellers = []
		for reseller in expand_resellers:
			with log_context(reseller.login):
				if resellers_migration_list is None or reseller.login in resellers_migration_list:
					reseller_report = report.subtarget(u"Reseller", reseller.login)
					converted_resellers.append(cls._create_target_model_reseller(None, reseller, False, reseller_report, password_holder, None))
		return converted_resellers

	@classmethod
	def _create_target_model_reseller(cls, plesk_id, reseller, is_windows, report, password_holder, reseller_source_panel_plan):
		password = cls._get_reseller_password(reseller, report, password_holder)
		return ppa.Reseller(
			login=reseller.login, 
			password=password,
			plan_name=None,
			clients=[],
			company='',
			personal_info=ppa.PersonalInfo(
				first_name=reseller.name,
				last_name='',
				email=reseller.email,
				preferred_email_format='plainText',
				address_line_1='No address',
				address_line_2='',
				city='No city',
				county='',
				state='Washington', 
				postal_code='11111', 
				language_code=(reseller.locale)[0:2],
				locale=reseller.locale,
				country_code='US', 
				primary_phone='1111111', 
				additional_phone='', 
				fax='', 
				mobile_phone=''
			),
			auxiliary_user_roles=[],
			auxiliary_users=[],
			is_enabled=reseller.is_active,
			plans={},
			source='expand',
			settings=None,
		)

