import logging
from xml.etree import ElementTree

from parallels.plesks_migrator.migrator import Migrator as PlesksMigrator

from parallels.utils import cached, obj, group_by_id

from .converter import Converter
from migration_list import MigrationList
from parallels.common.checking import PlainReport
from parallels.common.existing_objects_model import ExistingObjectsModel
from parallels.common.utils.yaml_utils import write_yaml
from parallels.pbas_migrator.workflow import FromPBASWorkflow

logger = logging.getLogger(__name__)


class Migrator(PlesksMigrator):
	def _load_configuration(self):
		super(Migrator, self)._load_configuration()
		self.pbas_data_filename = self.config.get('GLOBAL', 'pbas-data-file')

	def _create_workflow(self):
		return FromPBASWorkflow()

	@cached
	def _get_pbas_data(self):
		"""Read PBAS data file
		Return list of Plesk accounts with information where to transfer their subscriptions.
		"""
		with open(self.pbas_data_filename) as source:
			root = ElementTree.ElementTree(file=source)

		accounts = {}
		# { plesk_login: ppa_login, ppa_st_id, ppa_group_name }
		for account_node in root.findall('Node/Vendor/Account'):
			ppa_login = account_node.attrib['PaLogin']
			for subscription_node in account_node.findall('Subscr'):
				plesk_login = subscription_node.attrib['PleskLogin']
				ppa_st_id = int(subscription_node.attrib['SrvTmplId'])
				ppa_group_name = subscription_node.attrib['PleskName']
				accounts[plesk_login] = obj(
					ppa_login=ppa_login, ppa_st_id=ppa_st_id, ppa_group_name=ppa_group_name, source=None
				)

		return accounts

	def _get_migration_list_source_data(self):
		"""Return source data for migration list creation/validation"""
		return obj(plesk_backup_iter=lambda: self.iter_shallow_plesk_backups(), accounts=self._get_pbas_data())

	@classmethod
	def _get_migration_list_class(cls):
		return MigrationList

	def _read_migration_list(self, options, migration_list_optional=False):
		mapping = super(Migrator, self)._read_migration_list(
			options, 
			# in PBAS migrator migration list is always optional, and in default scenario is not specified by customer
			migration_list_optional=True
		)
		if mapping is None:
			mapping, _ = self._get_migration_list_class().generate_migration_list(
				self._get_migration_list_source_data(),
				ppa_webspace_names=[], ppa_service_templates={},
				target_panel=self.target_panel
			)
		return mapping

	def import_resellers(self):
		logger.warning(
			u"PBAS migrator does not support import-resellers command. "
			u"Please create and configure resellers on PPA. "
			u"Then create migration list file with 'generate-migration-list' "
			u"command and run 'transfer-accounts' command to transfer subscriptions from PBAS to PPA."
		)

	def _convert_resellers(self, existing_resellers, resellers_migration_list, report):
		return Converter.convert_resellers(
			self._get_plesk_infos(), existing_resellers,
			resellers_migration_list, report, self.global_context.password_holder
		)

	# we want _check() complain if plan or reseller does not exist in PPA
	# according to Plesk's migrator implementation, it will not complain, if finds plan or reseller on source Plesks
	# but since we do not convert source Plesks' plans and resellers, it will complain
	# so we don't have to redefine it here.

	def _convert_accounts(
		self, converted_resellers, existing_objects, subscriptions_mapping, customers_mapping, report, options
	):
		"""Accounts conversion for PBAS
		does not add new PPA customers,
		and does not check emails uniqueness.

		converted_resellers - ignored,
		existing_objects - ignored,
		subscriptions_mapping - subscription_name: None (so it is "selected_subscriptions" by its sense),
		customers_mapping - ignored
		"""
		converter = Converter(existing_objects, self._get_pbas_data(), options)
		plain_report = PlainReport(report, *self._extract_source_objects_info())
		converter.convert_plesks(
			self._get_plesk_infos(), plain_report, subscriptions_mapping, self.global_context.password_holder
		)
		return converter.get_ppa_model()

	@staticmethod
	def is_expand_mode():
		return True

	@staticmethod
	def _get_converter_class():
		return Converter

	def _fetch_ppa_existing_objects(self, reseller_logins, client_logins, plans, subscription_names, domain_names):
		"""In addition to fetching from PPA the objects according to migration list,
		fetch PBAS-specific data, such as service templates
		referred by identifier (which cannot be specified in migration list).
		"""
		ppa_existing_objects_plesk = super(Migrator, self)._fetch_ppa_existing_objects(
			reseller_logins,
			client_logins + [account_info.ppa_login for account_info in self._get_pbas_data().itervalues()],
			plans,
			subscription_names,
			domain_names
		)

		service_templates_plesk = group_by_id(ppa_existing_objects_plesk.service_templates, lambda st: st.st_id)

		service_templates_pbas = self._get_target_panel_api().get_service_template_info_list([
			account_info.ppa_st_id for account_info in self._get_pbas_data().itervalues()
			if account_info.ppa_st_id not in service_templates_plesk
		])

		ppa_existing_objects = ExistingObjectsModel(
			resellers=ppa_existing_objects_plesk.resellers,
			customers=ppa_existing_objects_plesk.customers,
			# the difference is below
			service_templates=ppa_existing_objects_plesk.service_templates + service_templates_pbas,
			addon_service_templates=ppa_existing_objects_plesk.addon_service_templates,
			raw_ppa_subscriptions=ppa_existing_objects_plesk.raw_ppa_subscriptions,
			webspaces=ppa_existing_objects_plesk.webspaces,
			auxiliary_user_roles=ppa_existing_objects_plesk.auxiliary_user_roles,
			auxiliary_users=ppa_existing_objects_plesk.auxiliary_users,
			plesk_subscriptions=ppa_existing_objects_plesk.plesk_subscriptions,
			plesk_sites=ppa_existing_objects_plesk.plesk_sites,
			plesk_site_aliases=ppa_existing_objects_plesk.plesk_site_aliases,
			plesk_subdomains=ppa_existing_objects_plesk.plesk_subdomains,
			ip_addresses=ppa_existing_objects_plesk.ip_addresses,
		)

		# XXX existing objects should not be stored into a file
		target_filename = self._get_session_file_path('ppa_existing_objects.yaml')
		write_yaml(target_filename, ppa_existing_objects)
		return ppa_existing_objects
