import logging

from parallels.common import MigrationError
from parallels.common.actions.base.common_action import CommonAction
from parallels.common.checking import Report, PlainReport, Problem
from parallels.common.converter.business_objects.clients import ClientsConverter
from parallels.common.converter.business_objects.common import SOURCE_TARGET
from parallels.common.converter.business_objects.resellers import ResellersConverter
from parallels.common.target_data_model import Model
from parallels.target_panel_pvps.converter.pvps_subscription import PVPSSubscriptionConverter
from parallels.utils import group_by_id, find_first, format_list

logger = logging.getLogger(__name__)


class Convert(CommonAction):
	def get_description(self):
		return "Convert clients and subscriptions"

	def get_failure_message(self, global_context):
		"""
		:type global_context: parallels.common.global_context.GlobalMigrationContext
		"""
		return "Failed to convert clients and subscriptions"

	def run(self, global_context):
		"""
		:type global_context: parallels.common.global_context.GlobalMigrationContext
		"""
		target_model = Model(plans={}, resellers={}, clients={})
		report = Report('Conversion status', None)

		self._convert_resellers(global_context, report, target_model)
		self._convert_clients(global_context, report, target_model)
		self._convert_vps_subscriptions(global_context, report, target_model)
		self._check_plan_compatibility(global_context, report, target_model)
		self._check_report_errors(global_context, report)
		global_context.migrator._write_ppa_model(target_model)

	@staticmethod
	def _convert_resellers(global_context, report, target_model):
		"""Convert resellers and put them into target model

		:type global_context: parallels.common.global_context.GlobalMigrationContext
		:type report parallels.common.checking.Report
		:type target_model parallels.common.target_data_model.Model
		"""
		converted_resellers = ResellersConverter().convert_resellers(
			global_context.get_source_plesks_info(),
			global_context.target_existing_objects.resellers,
			global_context.migration_list_data.resellers,
			report, global_context.password_holder
		)
		converted_resellers_filtered = [
			reseller for reseller in converted_resellers if reseller.source == SOURCE_TARGET
		]
		target_model.resellers.update(group_by_id(converted_resellers_filtered, lambda r: r.login))

	@staticmethod
	def _convert_clients(global_context, report, target_model):
		"""Convert clients and put them into target model

		:type global_context: parallels.common.global_context.GlobalMigrationContext
		:type report parallels.common.checking.Report
		:type target_model parallels.common.target_data_model.Model
		"""
		converted_clients = ClientsConverter().convert_clients(
			global_context.get_source_plesks_info(),
			global_context.target_existing_objects.customers,
			global_context.migration_list_data.customers_mapping,
			report, global_context.password_holder
		)
		ClientsConverter().assign_clients_to_resellers(
			target_model.clients, target_model.resellers.values(), converted_clients,
			global_context.migration_list_data.customers_mapping, report
		)

	@staticmethod
	def _convert_vps_subscriptions(global_context, report, target_model):
		"""Convert VPS subscriptions and put them into target model

		:type global_context: parallels.common.global_context.GlobalMigrationContext
		:type report parallels.common.checking.Report
		:type target_model parallels.common.target_data_model.Model
		"""
		PVPSSubscriptionConverter().convert_subscriptions(
			global_context.get_source_plesks_info(),
			global_context.target_existing_objects,
			global_context.migration_list_data.subscriptions_mapping,
			target_model,
			PlainReport(report, *global_context.migrator._extract_source_objects_info())
		)

	@staticmethod
	def _check_report_errors(global_context, report):
		"""Check report and exit in case of errors

		:type global_context: parallels.common.global_context.GlobalMigrationContext
		:type report parallels.common.checking.Report
		"""
		if report.has_errors():
			global_context.migrator.print_report(
				report, "convert_report_tree", show_no_issue_branches=False
			)
			raise MigrationError(
				u"Unable to continue migration until there are no issues "
				u"at 'error' level in pre-migration checks. "
				u"Please review pre-migration tree above and fix the errors. "
			)

	def _check_plan_compatibility(self, global_context, report, target_model):
		"""
		:type global_context: parallels.common.global_context.GlobalMigrationContext
		:type report parallels.common.checking.Report
		:type target_model parallels.common.target_data_model.Model
		"""
		for subscription in target_model.iter_all_subscriptions():
			service_template = find_first(global_context.target_existing_objects.vps_service_templates,
										lambda x: x.id == subscription.plan_id)
			service_template_report = report.subtarget(u'Subscription', subscription.name) \
				.subtarget(u'Service Template', service_template.name)

			pvps_configs = []
			for resource in service_template.resource_types:
				self._check_resource_limit(resource, service_template_report)
				if self._is_pvps_config_resource(resource):
					pvps_configs.append(resource)

			self._check_pvps_configs(pvps_configs, service_template_report)

	resource_requirements = [
		({'name': 'PVPS_Cloud', 'resclass_name': 'rc.saas.service.link'}, 1),
		({'name': 'PVPS_Panel', 'resclass_name': 'rc.saas.application'}, 1),
		({'name': 'PVPS_License', 'resclass_name': 'rc.saas.service'}, 1),
		({'resclass_name': 'rc.saas.service', 'service_id': 'ip_addresses'}, 1),
	]

	def _check_resource_limit(self, resource, report):
		"""
		:type resource: parallels.target_panel_pvps.models.existing_objects_model.PVPSResource
		:type report: parallels.common.checking.Report
		"""
		resource_to_check = {
			'name': resource.name,
			'resclass_name': resource.resclass_name,
			'service_id': resource.act_params.get('service_id')
		}

		def is_valid_resource_limit():
			def is_expected_resource(expected_resource):
				for name, value in expected_resource.iteritems():
					if resource_to_check.get(name) != value:
						return False
				return True

			for expected_resource, limit in self.resource_requirements:
				if is_expected_resource(expected_resource):
					return (resource.limit == -1 or resource.limit >= limit), limit
			return True, None

		is_valid, expected_limit = is_valid_resource_limit()
		if not is_valid:
			report.add_issue(
				Problem(
					'wrong_limit_on_service_template_resource',
					Problem.ERROR,
					u"The resource type '{r_name}' (id:{r_id}) has wrong limit value.\n "
					u"Expected limit: '{e_limit}'. Actual limit: '{a_limit}'".format(
						r_name=resource.name,
						r_id=resource.rt_id,
						e_limit=expected_limit,
						a_limit=resource.limit
					)
				),
				u"Please go to the Products>Service Templates>Failed Service Template>Resources "
				u"and change limit to expected."
			)

	@staticmethod
	def _check_pvps_configs(pvps_configs, report):
		"""
		:type pvps_configs: list[parallels.target_panel_pvps.models.existing_objects_model.PVPSResource]
		:type report: parallels.common.checking.Report
		"""
		resources = [(resource.name, resource.rt_id, resource.limit) for resource in pvps_configs]  # todo drop
		resources_with_unzero_limit = filter(lambda x: x[2] != 0, resources)
		if len(resources_with_unzero_limit) > 1:
			report.add_issue(
				Problem(
					'wrong_limit_on_service_template_resource_pvps_config',
					Problem.ERROR,
					u"There are more than one PVPS Config resources in the service template. Please choose one.\n"
					u"PVPS Configs: %s" % format_list([r[0] for r in resources_with_unzero_limit])
				),
				u"Please go to the Products>Service Templates>Failed Service Template>Resources "
				u"and set the excess PVPS Config resource limit to zero."
			)
		elif len(resources_with_unzero_limit) == 0:
			report.add_issue(
				Problem(
					'wrong_limit_on_service_template_resource_pvps_config',
					Problem.ERROR,
					u"The resource type '{r_name}' (id:{r_id}) has wrong limit value.\n "
					u"Expected limit: '{e_limit}'. Actual limit: '{a_limit}'".format(
						r_name=resources[0][0],
						r_id=resources[0][1],
						e_limit=1,
						a_limit=resources[0][2]
					)
				),
				u"Please go to the Products>Service Templates>Failed Service Template>Resources "
				u"and change limit to expected."
			)

	def _is_pvps_config_resource(self, resource):
		"""
		:type resource: parallels.target_panel_pvps.models.existing_objects_model.PVPSResource
		"""
		return resource.name.startswith('PVPS_Config_') and \
				resource.resclass_name == 'rc.saas.service.link' and \
				resource.act_params.get('service_id') == 'configurations'
