from parallels.plesk import messages

import os.path
import logging
import xml.etree.ElementTree as ElementTree

from parallels.core.utils import plesk_utils
from parallels.core.checking import Problem
from parallels.core.actions.base.common_action import CommonAction

logger = logging.getLogger(__name__)


class CheckCapabilityAction(CommonAction):
	def get_description(self):
		return messages.ACTION_CHECK_CAPABILITY_DESCRIPTION

	def get_failure_message(self, global_context):
		"""
		:type global_context: parallels.core.global_context.GlobalMigrationContext
		"""
		return messages.ACTION_CHECK_CAPABILITY_FAILURE

	def is_critical(self):
		"""If action is critical or not

		If action is critical and it failed, migration tool completely stops.
		Otherwise it proceeds to the next steps of migrations.

		:rtype: bool
		"""
		return False

	def filter_action(self, global_context):
		"""Check whether we should run this action or not.

		:type global_context: parallels.core.global_context.GlobalMigrationContext
		:rtype: bool
		"""
		return not(
			hasattr(global_context.options, 'skip_capability_checks') and
			global_context.options.skip_capability_checks
		)

	def run(self, global_context):
		"""
		:type global_context: parallels.core.global_context.GlobalMigrationContext
		"""
		common_issue_messages = set()
		for source_id in global_context.conn.get_source_plesks():
			capability_dump_path = global_context.session_files.get_path_to_capability_dump(source_id)
			capability_check_report_path = global_context.session_files.get_path_to_capability_check_report(source_id)
			if not os.path.exists(capability_dump_path):
				raise Exception(messages.UNABLE_TO_FIND_CAPABILITY_DUMP % capability_dump_path)

			capability_dump_remote_path = global_context.conn.target.main_node_session_file_path(
				'capability_dump.%s.xml' % source_id
			)
			capability_check_report_remote_path = global_context.conn.target.main_node_session_file_path(
				'capability_check_report.%s.xml' % source_id
			)
			with global_context.conn.target.main_node_runner() as runner:
				runner.upload_file(capability_dump_path, capability_dump_remote_path)

			plesk_utils.check_capability(
				global_context.conn.target.plesk_server,
				capability_dump_remote_path,
				capability_check_report_remote_path
			)
			with global_context.conn.target.main_node_runner() as runner:
				if not runner.file_exists(capability_check_report_remote_path):
					raise Exception(
						messages.UNABLE_TO_FIND_CAPABILITY_CHECK_REPORT % capability_check_report_remote_path
					)
				runner.get_file(capability_check_report_remote_path, capability_check_report_path)

			report_tree = ElementTree.parse(capability_check_report_path)
			for message_node in report_tree.findall('.//message'):

				severity_node = message_node.find('severity')
				severity_string = severity_node.text if severity_node is not None else ''
				if severity_string == 'warning':
					severity = Problem.WARNING
				elif severity_string == 'error':
					severity = Problem.ERROR
				else:
					severity = Problem.INFO

				text_node = message_node.find('text')
				description = text_node.text if text_node is not None else None
				if description is None:
					continue

				if message_node.findtext('resolutionDescription/componentName') in {'perl', 'python'}:
					# Perl/Python not installed on target Windows server.
					#
					# To avoid multiple reporting, such common for Plesk >= 12.5 issues are processed at:
					# - parallels.plesk.actions.hosting_settings.convert.
					# 	remove_missing_components.RemoveMissingComponents
					# - parallels.plesk.actions.hosting_settings.check.
					# 	check_missing_components.CheckMissingComponents
					continue

				resolution_text_node = message_node.find('resolutionDescription/text')
				solution = resolution_text_node.text if resolution_text_node is not None else None

				object_nodes = message_node.findall('objects-list/object')
				if len(object_nodes) == 0 and description not in common_issue_messages:
					# report common issue, not related to specific object
					global_context.pre_check_report.add_issue(
						Problem('capability_%s_%s' % (severity, source_id), severity, description),
						solution
					)
					common_issue_messages.add(description)
				else:
					for object_node in object_nodes:
						type_node = object_node.find('type')
						if type_node is None or type_node.text != 'domain':
							# skip all isses, not related to domains
							continue
						name_node = object_node.find('name')
						if name_node is None:
							continue
						subscription_name = name_node.text
						if not global_context.has_subscription(subscription_name):
							continue
						global_context.get_subscription(subscription_name).add_report_issue(
							global_context.pre_check_report,
							Problem('capability_%s_%s' % (severity, source_id), severity, description),
							solution
						)
