from collections import defaultdict
import logging

from parallels.common.checking import Problem
from parallels.common.converter.business_objects.common import SOURCE_TARGET
from parallels.common.utils.plesk_limits_permissions import RESELLER_PLAN_LIMIT_NAMES, RESELLER_PLAN_PERMISSION_NAMES
from parallels.target_panel_plesk.models.target_data_model import AdminResellerPlan, \
	AdminResellerPlanApsBundleFilterItem, AdminResellerPlanApsBundleFilter
from parallels.utils 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.common.global_context.SourceServerInfo]
		:type target_plans: list[parallels.plesk_api.operator.reseller_plan.ResellerPlanInfo]
		:type plan_names: list[basestring]
		:type report: parallels.common.checking.Report
		:rtype: list[parallels.target_panel_plesk.models.target_data_model.AdminResellerPlan]
		"""
		logger.debug(u"Convert admin's reseller plans")
		all_converted_plans = defaultdict(list)
		logger.debug(u"Convert target panel admin's reseller plans")
		self._create_target_panel_plans(all_converted_plans, target_plans)
		logger.debug(u"Convert source panel's admin's reseller plans")
		self._create_source_panel_plans(all_converted_plans, plan_names, source_panels)
		logger.debug(u"Merge admin's 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_api.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.common.global_context.SourceServerInfo]
		:rtype: None
		"""
		for source in source_panels:
			backup = source.load_raw_backup()
			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.common.plesk_backup.data_model.ResellerPlan
		:rtype: parallels.target_panel_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.common.checking.Report
		:rtype: list[parallels.target_panel_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,
						"Reseller plan '%s' specified in migration list file does not exist" % plan_name
					),
					"Fix migration list so existing plan is used, or create reseller plan with such name"
				)
			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,
							"Reseller plan '%s' specified in migration list file exists on multiple source panels."
							"Plan from '%s' will be used during migration" % (
								plan_name, source_panels[0]
							)
						),
						""
					)
				if exists_on_target:
					logger.debug(
						"Reseller plan '%s' already exists on target panel, it will be used during migration",
						plan_name
					)

				merged_plans.append(first_converted_plan)

		return merged_plans