import base64
from parallels.core.checking import Issue, Problem

from parallels.core.converter.business_objects.plans import BackupToTargetModelPlanConverter
from parallels.core.utils.plesk_limits_permissions import \
	HOSTING_PLAN_LIMIT_NAMES, HOSTING_PLAN_PERMISSION_NAMES
from parallels.core.utils.plesk_utils import PHPHandler
from parallels.plesk import messages
from parallels.plesk.models.target_data_model import HostingPlanSettings, HostingPlanLogRotation, \
	HostingPlanLogRotationSettings, HostingPlanLogRotationConditionByTime, HostingPlanLogRotationConditionBySize, \
	HostingPlanMail, HostingPlanMailNonexistentUserBounce, HostingPlanMailNonexistentUserForward, \
	HostingPlanMailNonexistentUserReject, HostingPlanPreferences, HostingPlanHostingSetting, HostingPlanPerformance, \
	HostingPlanWebServerSettings, HostingPlanApsBundleFilter, HostingPlanApsBundleFilterItem, HostingPlanCustomItem, \
	HostingPlanDefaultDatabaseServer
from parallels.core.utils.common import if_not_none, compare_by


class BackupToTargetPleskModelPlanConverter(BackupToTargetModelPlanConverter):
	def __init__(
		self, allowed_limits, allowed_permissions, allowed_hosting_settings, allowed_php_handlers, is_windows=False
	):
		"""
		:type allowed_limits: set[basestring]
		:type allowed_permissions: set[basestring]
		:type allowed_hosting_settings: set[basestring]
		:type allowed_php_handlers: list[parallels.core.utils.plesk_utils.PHPHandler] | None
		:type is_windows: bool
		"""
		self._allowed_limits = allowed_limits
		self._allowed_permissions = allowed_permissions
		self._allowed_hosting_settings = allowed_hosting_settings
		self._allowed_php_handlers = allowed_php_handlers
		self._is_windows = is_windows

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

		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:rtype: (parallels.plesk.models.target_data_model.HostingPlanSettings, list[parallels.core.checking.Issue])
		"""
		issues = []
		hosting = self._convert_hosting(
			backup_plan, self._allowed_hosting_settings, self._allowed_php_handlers, issues
		)
		return (
			HostingPlanSettings(
				overuse=self._convert_overuse(backup_plan),
				limits=self._convert_limits(backup_plan, self._allowed_limits),
				permissions=self._convert_permissions(backup_plan, self._allowed_permissions),
				php_settings_node=backup_plan.php_settings_node,
				log_rotation=self._convert_log_rotation(backup_plan),
				mail=self._convert_mail(backup_plan),
				preferences=self._convert_preferences(backup_plan),
				hosting=hosting,
				virtual_hosting_enabled=backup_plan.properties.get('vh_type', 'physical') != 'none',
				performance=self._convert_performance(backup_plan),
				web_server_settings=self._convert_web_server_settings(backup_plan),
				aps_bundle_filter=self._convert_aps_bundle_filter(backup_plan),
				custom_plan_items=self._convert_custom_plan_items(backup_plan),
				default_db_servers=self._convert_default_database_servers(backup_plan)
			),
			issues
		)

	@staticmethod
	def _convert_overuse(backup_plan):
		"""
		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:rtype: basestring
		"""
		return backup_plan.properties.get('overuse')

	@staticmethod
	def _convert_limits(backup_plan, allowed_limits):
		"""
		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:type allowed_limits: set[basestring]
		:rtype: dict[string, string]
		"""
		return {
			limit_name: backup_plan.properties[limit_name]
			for limit_name in allowed_limits & set(HOSTING_PLAN_LIMIT_NAMES)
			if limit_name in backup_plan.properties
		}

	@staticmethod
	def _convert_permissions(backup_plan, allowed_permissions):
		"""
		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:type allowed_permissions: set[basestring]
		:rtype: dict[string, string]
		"""
		return {
			permission_name: backup_plan.properties[permission_name]
			for permission_name in allowed_permissions & set(HOSTING_PLAN_PERMISSION_NAMES)
			if permission_name in backup_plan.properties
		}

	@staticmethod
	def _convert_log_rotation(backup_plan):
		"""
		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:rtype: parallels.plesk.models.target_data_model.HostingPlanLogRotationSettings
		"""
		if backup_plan.log_rotation is not None:
			if backup_plan.log_rotation.period is not None:
				log_condition = HostingPlanLogRotationConditionByTime(backup_plan.log_rotation.period.capitalize())
			else:
				log_condition = HostingPlanLogRotationConditionBySize(backup_plan.log_rotation.max_size)

			log_rotation = HostingPlanLogRotation(
				status=backup_plan.log_rotation.enabled,
				settings=HostingPlanLogRotationSettings(
					log_condition=log_condition,
					log_max_num_files=backup_plan.log_rotation.max_logfiles_number,
					log_compress=backup_plan.log_rotation.compress,
					log_email=backup_plan.log_rotation.email,
				)
			)
		else:
			log_rotation = None
		return log_rotation

	def _convert_mail(self, backup_plan):
		"""
		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:rtype: parallels.plesk.models.target_data_model.HostingPlanMail
		"""
		webmail = backup_plan.properties.get('webmail')
		if webmail == 'true':
			webmail = 'horde'
		return HostingPlanMail(
			nonexistent_user=self._convert_mail_nonexistent_user(backup_plan),
			webmail=webmail,
		)

	@staticmethod
	def _convert_mail_nonexistent_user(backup_plan):
		"""
		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:rtype: None |
			parallels.plesk.models.target_data_model.HostingPlanMailNonexistentUserBounce |
			parallels.plesk.models.target_data_model.HostingPlanMailNonexistentUserForward |
			parallels.plesk.models.target_data_model.HostingPlanMailNonexistentUserReject
		"""
		nonexistent_prop = backup_plan.properties.get('nonexist_mail')
		if nonexistent_prop == 'bounce':
			nonexistent_user = HostingPlanMailNonexistentUserBounce(message=backup_plan.properties.get('bounce_mess'))
		elif nonexistent_prop == 'catch':
			nonexistent_user = HostingPlanMailNonexistentUserForward(address=backup_plan.properties.get('catch_addr'))
		elif nonexistent_prop == 'reject':
			nonexistent_user = HostingPlanMailNonexistentUserReject()
		else:
			nonexistent_user = None
		return nonexistent_user

	@staticmethod
	def _convert_preferences(backup_plan):
		"""
		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:rtype: parallels.plesk.models.target_data_model.HostingPlanPreferences
		"""
		return HostingPlanPreferences(
			stat=backup_plan.properties.get('stat_ttl', '3'),
			maillists=backup_plan.properties.get('maillists', 'false') == 'true',
			mailservice=backup_plan.properties.get('mailservice', 'true') == 'true',
			dns_zone_type=backup_plan.properties.get('dns_type', 'master')
		)

	@classmethod
	def _convert_hosting(cls, backup_plan, allowed_hosting_settings, allowed_php_handlers, issues):
		"""
		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:type allowed_hosting_settings: set[basestring]
		:type allowed_php_handlers: list[parallels.core.utils.plesk_utils.PHPHandler] | None
		:type issues: list
		:rtype: list[parallels.plesk.models.target_data_model.HostingPlanHostingSetting]
		"""
		available_hostings_list = set(allowed_hosting_settings)
		if backup_plan.properties.get('php') != 'true':
			available_hostings_list -= {'php_handler_type', 'php_handler_id'}
		hostings = []

		if 'vh_type' in backup_plan.properties and backup_plan.properties['vh_type'] == 'none':
			return hostings

		for name, value in backup_plan.properties.iteritems():
			if value is None:
				continue
				
			if name == 'quota':
				name = 'ftp_quota'

			if name == 'managed_runtime_version' and backup_plan.properties.get('asp_dot_net') == 'false':
				# we should not set ASP.NET version if ASP.NET is disabled, otherwise Plesk fails to create plan
				continue

			if name == 'php_handler_id' and allowed_php_handlers is not None:
				requested_php_handler = PHPHandler(
					handler_id=value,
					version=backup_plan.properties.get('php_version'),
					handler_type=backup_plan.properties.get('php_handler_type'),
					full_version=None
				)

				# We do not check web server that handles PHP here (Apache or nginx), just check version, type and ID
				# Web server compatibility issue should not be common case here, but it is still possible,
				# so probably we need to implement such check in the future.

				criteria = ['version', 'handler_type', 'handler_id']
				selected_php_handler = cls._select_php_handler(
					requested_php_handler, allowed_php_handlers, criteria
				)
				if not compare_by(selected_php_handler, requested_php_handler, criteria):
					value = selected_php_handler.handler_id
					issues.append(
						Issue(
							Problem(
								'service-template-php-handler', Problem.WARNING,
								messages.SERVICE_PLAN_PHP_HANDLER_NOT_AVAILABLE_ISSUE.format(
									plan_name=backup_plan.name,
									source_php_handler=requested_php_handler.pretty_str(),
									target_php_handler=selected_php_handler.pretty_str()
								)
							),
							messages.SERVICE_PLAN_PHP_HANDLER_NOT_AVAILABLE_SOLUTION
						)
					)

			if name in available_hostings_list:
				hostings.append(
					HostingPlanHostingSetting(name=name, value=value)
				)

		return hostings

	@staticmethod
	def _select_php_handler(required_handler, allowed_php_handlers, criteria):
		for i in range(len(criteria), 0, -1):
			for handler in allowed_php_handlers:
				if all(getattr(handler, attrib) == getattr(required_handler, attrib) for attrib in criteria[:i]):
					return handler

		# If no suitable PHP handler exists - simply select the first one
		return allowed_php_handlers[0]

	def _convert_performance(self, backup_plan):
		"""
		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:rtype: parallels.plesk.models.target_data_model.HostingPlanPerformance
		"""
		bandwidth = int(backup_plan.properties.get('bandwidth', '-1'))
		if bandwidth > 0 and not self._is_windows:
			bandwidth /= 1024

		return HostingPlanPerformance(
			bandwidth=str(bandwidth),
			max_connections=backup_plan.properties.get('max_connections', '-1')
		)

	@staticmethod
	def _convert_web_server_settings(backup_plan):
		"""
		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:rtype: parallels.plesk.models.target_data_model.HostingPlanWebServerSettings
		"""
		return HostingPlanWebServerSettings(
			additional=if_not_none(backup_plan.properties.get('additionalSettings'), base64.b64decode),
			additional_ssl=if_not_none(backup_plan.properties.get('additionalSslSettings'), base64.b64decode),
			additional_nginx=if_not_none(backup_plan.properties.get('additionalNginxSettings'), base64.b64decode),
		)

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

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

	@staticmethod
	def _convert_custom_plan_items(backup_plan):
		"""
		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:rtype: list[parallels.plesk.models.target_data_model.HostingPlanCustomItem]
		"""
		return [
			HostingPlanCustomItem(
				name=item.name, guid=item.guid, visible=item.visible,
				hint=item.hint, description=item.description,
				url=item.url, options=item.options
			)
			for item in backup_plan.custom_plan_items
		]

	@staticmethod
	def _convert_default_database_servers(backup_plan):
		"""
		:type backup_plan: parallels.core.dump.data_model.HostingPlan
		:rtype: list[parallels.plesk.models.target_data_model.HostingPlanDefaultDatabaseServer]
		"""
		return [
			HostingPlanDefaultDatabaseServer(
				type=server.type, host=server.host, port=server.port
			)
			for server in backup_plan.default_db_servers
		]