from parallels.plesk import messages
from collections import defaultdict
import logging

from parallels.core.checking import Problem
from parallels.core.converter.business_objects.common import SOURCE_TARGET
from parallels.core.utils.plesk_limits_permissions import RESELLER_PLAN_LIMIT_NAMES, RESELLER_PLAN_PERMISSION_NAMES
from parallels.plesk.models.target_data_model import AdminResellerPlan, \
	AdminResellerPlanApsBundleFilterItem, AdminResellerPlanApsBundleFilter
from parallels.core.utils.common import exists

logger = logging.getLogger(__name__)


class ResellerPlanConverter(object):
	"""Convert and merge admin's reseller plans from source and target panels"""

	def convert_plans(self, source_panels, target_plans, plan_names, report):
		"""Convert admin's reseller plans, merge plans within sources and target.

		Return list of plans expected to have on target system.

		:type source_panels: list[parallels.core.global_context.SourceInfo]
		:type target_plans: list[parallels.plesk.utils.xml_rpc.plesk.operator.reseller_plan.ResellerPlanInfo]
		:type plan_names: list[basestring]
		:type report: parallels.core.checking.Report
		:rtype: list[parallels.plesk.models.target_data_model.AdminResellerPlan]
		"""
		logger.debug(messages.LOG_CONVERT_ADMIN_RESELLER_PLANS)
		all_converted_plans = defaultdict(list)
		logger.debug(messages.LOG_CONVERT_TARGET_ADMIN_RESELLER_PLANS)
		self._create_target_panel_plans(all_converted_plans, target_plans)
		logger.debug(messages.LOG_CONVERT_SOURCE_PANEL_ADMIN_RESELLER_PLANS)
		self._create_source_panel_plans(all_converted_plans, plan_names, source_panels)
		logger.debug(messages.LOG_MERGE_ADMIN_RESELLER_PLANS)
		merged_plans = self._merge_plans(all_converted_plans, plan_names, report)

		return merged_plans

	@staticmethod
	def _create_target_panel_plans(all_converted_plans, target_plans):
		"""Create converted plans from target panel plans, add them to all converted plans dictionary

		:type all_converted_plans: defaultdict
		:type target_plans: list[parallels.plesk.utils.xml_rpc.plesk.operator.reseller_plan.ResellerPlanInfo]
		:rtype: None
		"""
		for target_plan in target_plans:
			all_converted_plans[target_plan.name].append(
				AdminResellerPlan(
					source=SOURCE_TARGET, name=target_plan.name,
					oversell=None, overuse=None,
					limits=None, permissions=None, aps_bundle_filter=None,
				)
			)

	def _create_source_panel_plans(self, all_converted_plans, plan_names, source_panels):
		"""Create converted plans from source panel plans, add them to all converted plans dictionary

		:type all_converted_plans: defaultdict
		:type plan_names: list[basestring]
		:type source_panels: list[parallels.core.global_context.SourceInfo]
		:rtype: None
		"""
		for source in source_panels:
			backup = source.load_raw_dump()
			for backup_plan in backup.iter_admin_reseller_plans():
				if backup_plan.name in plan_names:
					converted_source_plan = AdminResellerPlan(
						source=source.id,
						name=backup_plan.name,
						oversell=backup_plan.properties.get('oversell'),
						overuse=backup_plan.properties.get('overuse'),
						limits={
							limit_name: backup_plan.properties[limit_name]
							for limit_name in RESELLER_PLAN_LIMIT_NAMES
							if limit_name in backup_plan.properties
						},
						permissions={
							permission_name: backup_plan.properties[permission_name]
							for permission_name in RESELLER_PLAN_PERMISSION_NAMES
							if permission_name in backup_plan.properties
						},
						aps_bundle_filter=self._convert_aps_bundle_filter(backup_plan),
					)
					all_converted_plans[backup_plan.name].append(converted_source_plan)

	@staticmethod
	def _convert_aps_bundle_filter(backup_plan):
		"""
		:type backup_plan: parallels.core.dump.data_model.ResellerPlan
		:rtype: parallels.plesk.models.target_data_model.AdminResellerPlanApsBundleFilter | None
		"""
		if backup_plan.aps_filter is None:
			return None

		return AdminResellerPlanApsBundleFilter(
			type=backup_plan.aps_filter.type,
			items=[
				AdminResellerPlanApsBundleFilterItem(name=item.name, value=item.value)
				for item in backup_plan.aps_filter.items
			]
		)

	@staticmethod
	def _merge_plans(all_converted_plans, plan_names, report):
		"""Merge converted plans to get single converted plan for each plan name

		:type all_converted_plans: defaultdict
		:type plan_names: list[basestring]
		:type report: parallels.core.checking.Report
		:rtype: list[parallels.plesk.models.target_data_model.AdminResellerPlan]
		"""
		merged_plans = []
		for plan_name in plan_names:
			converted_plans = all_converted_plans.get(plan_name, [])
			if len(converted_plans) == 0:
				report.add_issue(
					Problem(
						'no_such_reseller_plan', Problem.ERROR,
						messages.NO_SUCH_ADMIN_RESELLER_PLAN_ISSUE % plan_name
					),
					messages.NO_SUCH_ADMIN_RESELLER_PLAN_SOLUTION
				)
			else:
				first_converted_plan = converted_plans[0]
				source_panels = [p.source for p in converted_plans if p.source != SOURCE_TARGET]
				exists_on_target = exists(converted_plans, lambda pl: pl.source == SOURCE_TARGET)
				if len(source_panels) > 1 and not exists_on_target:
					report.add_issue(
						Problem(
							'reseller_plan_on_multiple_source', Problem.WARNING,
							messages.ADMIN_RESELLER_PLAN_ON_MULTIPLE_SOURCES_ISSUE % (
								plan_name, source_panels[0]
							)
						),
						""
					)
				if exists_on_target:
					logger.debug(
						messages.LOG_RESELLER_ADMIN_PLAN_ALREADY_EXISTS,
						plan_name
					)

				merged_plans.append(first_converted_plan)

		return merged_plans
