import logging

from parallels.common import MigrationNoContextError
from parallels.common.target_panel_base import TargetPanelBase
from parallels.target_panel_ppa.connections.target_connections import PPATargetConnections
from parallels.target_panel_ppa.converter.converter import PPAConverter
from parallels.target_panel_ppa.import_api.import_api import PPAImportAPI, WebspacesRequestsHolder
from parallels.utils import cached
from parallels.common.utils.poa_utils import get_host_id_by_ip, HostNotFoundException

from parallels.common.connections.target_servers import TargetServers
from parallels.target_panel_ppa.connections import target_server
from parallels.target_panel_ppa.connections.database_target_server import \
	PPADatabaseTargetServer
from parallels.common.hosting_check.entity_source.service import \
	TargetServiceInfo
from parallels.target_panel_ppa.workflow import \
	extend_workflow_with_ppa_actions


logger = logging.getLogger(__name__)


class PPATargetPanel(TargetPanelBase):
	@property
	def name(self):
		return 'PPA'

	def has_custom_subscriptions_feature(self):
		"""Whether subscriptions not assigned to plan are allowed"""
		return False

	def has_admin_subscriptions_feature(self):
		"""Whether subscriptions assigned directly to admin are allowed"""
		return False

	def has_reseller_subscriptions_feature(self):
		"""Whether subscriptions assigned directly to reseller are allowed"""
		return False

	def has_dns_forwarding(self):
		"""Whether panel should support DNS forwarding migration feature"""
		return True

	def is_transfer_resource_limits_by_default(self):
		"""Whether resource limits should be transferred by default"""
		return False

	def use_source_plans(self):
		"""Return True if we should use plans (service templates) of source panel, False otherwise"""
		return True

	def get_subscription_nodes(self, global_context, subscription_target_services, subscription_name):
		conn = global_context.conn.target

		if subscription_target_services is not None:
			if subscription_target_services.web_ips is None or subscription_target_services.web_ips.v4 is None:
				web_node = None
			else:
				web_node_id = self._get_host_id_by_ip(subscription_target_services.web_ips.v4, conn)
				web_node = target_server.PPATargetServer(
					conn,
					web_node_id,
					subscription_target_services.web_ips.v4
				)

			if subscription_target_services.mail_ips is None or subscription_target_services.mail_ips.v4 is None:
				mail_node = None
			else:
				mail_node_id = self._get_host_id_by_ip(subscription_target_services.mail_ips.v4, conn)
				mail_node = target_server.PPATargetServer(
					conn,
					mail_node_id,
					subscription_target_services.mail_ips.v4
				)

			dns_nodes = []
			for dns_ip in subscription_target_services.dns_ips:
				try:
					dns_node_id = self._get_host_id_by_ip(dns_ip.v4, conn)
				except HostNotFoundException:
					logger.debug(
						"DNS server %s is not registered in PPA, consider it as external DNS server", dns_ip.v4
					)
				else:
					dns_nodes.append(
						target_server.PPATargetServer(
							conn,
							dns_node_id,
							dns_ip.v4
						)
					)

			return TargetServers(
				web=web_node,
				mail=mail_node,
				database={
					db_server_type: PPADatabaseTargetServer(
						conn,
						db_server_type,
						db_server_params.host,
						db_server_params.port,
						db_server_params.admin,
						db_server_params.password,
					)
					for db_server_type, db_server_params in subscription_target_services.db_servers.iteritems()
				},
				dns=dns_nodes
			)
		else:
			return TargetServers(web=None, mail=None, database={}, dns=[])

	def get_subscription_plesk_node(self, global_context, subscription_name):
		return global_context.conn.target.plesk_server

	def get_connections(self, config):
		"""Get target panel connections"""
		return PPATargetConnections(config)

	def get_import_api(self, global_context):
		return PPAImportAPI(
			global_context.conn.target,
			WebspacesRequestsHolder(
				global_context.migrator_server.get_session_file_path('webspaces_requests.yaml')
			)
		)

	def get_converter_class(self):
		return PPAConverter

	def get_hosting_check_messages_panel_id(self):
		return 'ppa'

	@staticmethod
	def extend_workflow(workflow):
		"""Extend shared hosting workflow with ppa-specific actions

		:type workflow parallels.common.workflow.base_workflow.BaseWorkflow
		:rtype None
		"""
		extend_workflow_with_ppa_actions(workflow)

	@cached
	def _get_host_id_by_ip(self, ip, conn):
		"""Determine POA host ID by IP address.
		As it is unlikely that this information changes during migration tool execution, 
		(no new nodes are registered, assimilated or removed by migration tool) 
		we can safely cache it."""
		return get_host_id_by_ip(conn.poa_api(), ip)

	def get_service_nodes(self, conn):
		service_nodes = []
		service_by_name = {
			'bind9': 'dns',
			'ppa_mysql': 'mysql',
			'ppa_pgsql': 'postgresql',
			'ppa_mssql2012': 'mssql',
			'ppa_apache': 'web',
			'ppa_iis': 'web',
			'ppa_postfix': 'mail',
			'ppa_webmail': 'mail',
			'ppa_smartermail': 'mail'
		}

		for service_info in conn.poa_api().listServices():
			if service_info.service_name in service_by_name.keys():
				service_nodes.append(
					TargetServiceInfo(
						service_type=service_by_name[service_info.service_name],
						node=target_server.PPATargetServer(
							conn,
							service_info.host_id,
							conn.poa_api().getHost(service_info.host_id).ip_addresses[0].address
						)
					)
				)
		return service_nodes

	def check_version(self, global_context):
		"""Check that target panel version is ok for migration purposes. Raise MigrationError othewise.

		Raised exception should contain information about supported versions for migration.

		:type global_context: parallels.common.global_context.GlobalMigrationContext
		:rtype: None
		"""
		plesk_version = global_context.conn.target.plesk_server.get_plesk_version()
		plesk_version_str = '.'.join([str(i) for i in plesk_version])

		if plesk_version < (11, 5):
			raise MigrationNoContextError(
				"Migration to PPA %s is not supported. We strongly recommend that you install the "
				"latest version of PPA for migration. If you still want to migrate to PPA 11.1.x, "
				"please use the previous version of migration tools: "
				"http://autoinstall.plesk.com/PPA_11.1.0/migration-tools/ (not recommended)." % (
					plesk_version_str
				)
			)