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

from parallels.common import MigrationError
from parallels.common import target_data_model

logger = logging.getLogger(__name__)


class PlansConverter(object):
	"""Generate list of plans ready to import to target panel.
	
	Plans converter takes the following information:
	- Plesk backup or other source of information 
	about resellers and plans on source servers
	- Import API which provides information about 
	resellers and plans that already exist on target panel
	- Migration list, which contains list of plans that 
	will be used during migration

	Plans converter:
	- Converts each plan we need to migrate to 
	format ready to import to target panel.
	- Performs conflict resolution, if plan exists on multiple
	source panel, or already exists on target panel.

	Result is a list of plans ready to import to target panel.
	"""
	def convert_plans(self, source_panels, import_api, plan_migration_list, backup_plan_converter):
		"""Convert plans from source and target panel information to target model

		plan_migration_list is a dictionary with key - reseller login, value - list of service templates to create
			for example: {None: ['Plan 1', 'Plan 2'], 'res1': ['Plan 3']} means that we need to have
			'Plan 1' and 'Plan 2' plans for administrator and 'Plan 3' plan for reseller 'res1'

		:type source_panels: list[parallels.common.global_context.SourceServerInfo]
		:type import_api: parallels.common.import_api.import_api.ImportAPI
		:type plan_migration_list: parallels.common.migration_list.MigrationListPlans
		:type backup_plan_converter: BackupToTargetModelPlanConverter

		:rtype: dict[(basestring|None, basestring, bool), parallels.common.target_data_model.Plan]
		"""
		logger.info(u"Convert service plans")

		all_plans = defaultdict(list)
		self._convert_target_admin_plans(all_plans, import_api)
		self._convert_target_admin_addon_plans(all_plans, import_api)
		self._convert_target_reseller_all_plans(all_plans, import_api, plan_migration_list)
		self._convert_source_panel_plans(all_plans, backup_plan_converter, source_panels)

		merged_plans = dict()
		self._merge_plans(all_plans, merged_plans, plan_migration_list)
		self._merge_addon_plans(all_plans, merged_plans, plan_migration_list)

		return merged_plans

	@staticmethod
	def _convert_target_admin_plans(all_plans, import_api):
		"""
		:type all_plans: dict[(basestring | None, basestring, bool), list[parallels.common.target_data_model.Plan]]
		:type import_api: parallels.common.import_api.import_api.ImportAPI
		"""
		target_admin_plans = import_api.get_service_template_list(None)
		for plan in target_admin_plans:
			all_plans[(None, plan.name, False)].append(
				target_data_model.Plan(name=plan.name, source='target', is_addon=False, settings=None)
			)

	@staticmethod
	def _convert_target_admin_addon_plans(all_plans, import_api):
		"""
		:type all_plans: dict[(basestring | None, basestring, bool), list[parallels.common.target_data_model.Plan]]
		:type import_api: parallels.common.import_api.import_api.ImportAPI
		"""
		target_admin_addon_plans = import_api.get_addon_service_template_list(None)
		for plan in target_admin_addon_plans:
			all_plans[(None, plan.name, True)].append(
				target_data_model.Plan(name=plan.name, source='target', is_addon=True, settings=None)
			)

	@staticmethod
	def _convert_target_reseller_all_plans(all_plans, import_api, plan_migration_list):
		"""
		:type all_plans: dict[(basestring | None, basestring, bool), list[parallels.common.target_data_model.Plan]]
		:type import_api: parallels.common.import_api.import_api.ImportAPI
		:type plan_migration_list: parallels.common.migration_list.MigrationListPlans
		"""
		reseller_logins = [
			reseller
			for reseller in itertools.chain(
				plan_migration_list.plans.iterkeys(),
				plan_migration_list.addon_plans.iterkeys()
			)
			if reseller is not None
		]
		target_resellers = import_api.list_resellers(reseller_logins)
		for reseller in target_resellers:
			target_reseller_plans = import_api.get_service_template_list(reseller.id)
			for plan in target_reseller_plans:
				all_plans[(reseller.contact.username, plan.name, False)].append(
					target_data_model.Plan(name=plan.name, source='target', is_addon=False, settings=None)
				)
			target_reseller_addon_plans = import_api.get_addon_service_template_list(reseller.id)
			for plan in target_reseller_addon_plans:
				all_plans[(reseller.contact.username, plan.name, True)].append(
					target_data_model.Plan(name=plan.name, source='target', is_addon=True, settings=None)
				)

	@staticmethod
	def _convert_source_panel_plans(all_plans, backup_plan_converter, source_panels):
		"""
		:type all_plans: dict[(basestring | None, basestring, bool), list[parallels.common.target_data_model.Plan]]
		:type backup_plan_converter: BackupToTargetModelPlanConverter
		:type source_panels: list[parallels.common.global_context.SourceServerInfo]
		"""
		for source_panel in source_panels:
			with closing(source_panel.load_raw_backup()) as backup:
				backup_all_admin_plans = chain(
					backup.get_plans(),
					backup.get_addon_plans()
				)
				for plan in backup_all_admin_plans:
					all_plans[(None, plan.name, plan.is_addon)].append(
						target_data_model.Plan(
							name=plan.name,
							source=source_panel.id,
							is_addon=plan.is_addon,
							settings=backup_plan_converter.convert(plan)
						)
					)
				for reseller in backup.iter_resellers():
					backup_all_reseller_plans = chain(
						backup.iter_reseller_plans(reseller.login),
						backup.iter_reseller_addon_plans(reseller.login)
					)
					for plan in backup_all_reseller_plans:
						all_plans[(reseller.login, plan.name, plan.is_addon)].append(
							target_data_model.Plan(
								name=plan.name,
								source=source_panel.id,
								is_addon=plan.is_addon,
								settings=backup_plan_converter.convert(plan)
							)
						)

	@staticmethod
	def _merge_addon_plans(all_plans, merged_plans, plan_migration_list):
		"""
		:type all_plans: dict[(basestring | None, basestring, bool), list[parallels.common.target_data_model.Plan]]
		:type merged_plans: dict[(basestring | None, basestring, bool), parallels.common.target_data_model.Plan]
		:type plan_migration_list: parallels.common.migration_list.MigrationListPlans
		"""
		for reseller, plans in plan_migration_list.addon_plans.iteritems():
			for plan in plans:
				converted_plans = all_plans.get((reseller, plan, True), [])
				if len(converted_plans) == 0:
					raise MigrationError("Addon plan '%s' specified in migration list file does not exist" % (plan,))
				else:
					first_converted_plan = converted_plans[0]
					if len(converted_plans) > 1:
						logger.debug(
							"Addon plan '%s' exists on multiple panels. Plan from '%s' will be used",
							plan, first_converted_plan.source
						)
					merged_plans[(reseller, plan, True)] = first_converted_plan

	@staticmethod
	def _merge_plans(all_plans, merged_plans, plan_migration_list):
		"""
		:type all_plans: dict[(basestring | None, basestring, bool), list[parallels.common.target_data_model.Plan]]
		:type merged_plans: dict[(basestring | None, basestring, bool), parallels.common.target_data_model.Plan]
		:type plan_migration_list: parallels.common.migration_list.MigrationListPlans
		"""
		for reseller, plans in plan_migration_list.plans.iteritems():
			for plan in plans:
				converted_plans = all_plans.get((reseller, plan, False), [])
				if len(converted_plans) == 0:
					raise MigrationError("Plan '%s' specified in migration list file does not exist" % (plan,))
				else:
					first_converted_plan = converted_plans[0]
					if len(converted_plans) > 1:
						logger.debug(
							"Plan '%s' exists on multiple panels. Plan from '%s' will be used",
							plan, first_converted_plan.source
						)
					merged_plans[(reseller, plan, False)] = first_converted_plan


class BackupToTargetModelPlanConverter(object):
	def convert(self, backup_plan):
		"""Convert plan settings from Plesk backup format to format ready to import with target panel API

		:type backup_plan: parallels.common.plesk_backup.data_model.HostingPlan
		"""
		raise NotImplementedError()