import logging

from parallels.core.subscription_target_info import TargetServices, SubscriptionServiceIPs, TargetServers
from parallels.core.utils import plesk_api_utils
from parallels.core.utils import migrator_utils
from parallels.ppa import messages
from parallels.core import MigrationNoContextError, MigrationError
from parallels.core.target_panel_base import TargetPanelBase
from parallels.ppa.client_subscription_converter import TargetPPAClientSubscriptionConverterAdapter
from parallels.ppa.connections.target_connections import PPATargetConnections
from parallels.ppa.import_api.import_api import PPAImportAPI, WebspacesRequestsHolder
from parallels.core.utils.common import cached
from parallels.ppa.utils.poa_utils import get_host_id_by_ip, HostNotFoundException
from parallels.ppa.connections import target_server
from parallels.ppa.connections.database_target_server import \
	PPADatabaseTargetServer
from parallels.core.hosting_check.entity_source.service import \
	TargetServiceInfo
from parallels.plesk.utils.xml_rpc.plesk import operator as plesk_ops

logger = logging.getLogger(__name__)


class Panel(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_reseller_plan_feature(self):
		"""Whethere admin reseller plans are supported for that target panel migration

		:rtype: bool
		"""
		return False

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

	def get_default_admin_name(self):
		"""
		Return name of user to assign subscription, not assigned to particular user,
		if panel unable to assign subscription to administrator directly, None otherwise
		:rtype: str
		"""
		return u'ppa-admin'

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

	def get_default_reseller_name(self, reseller):
		"""
		Return name of user to assign reseller subscription, not assigned to particular user,
		if panel unable to assign subscription to reseller directly, None otherwise
		:type reseller: parallels.core.dump.entity.reseller.Reseller
		:rtype: str
		"""
		return u'ppa-%s' % reseller.login

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

	def is_subdomain_record_transferred_as_separate_zone(self):
		"""Whether subdomains's DNS record in parent domain's zone is transferred as new separate DNS zone

		For PPA that is true, each subdomain is forced to have separate DNS zone.
		"""
		return True

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

		For target PPA we always remove Plesk limits and permissions from Plesk backup file before restoration.

		There are 2 different cases:
		1. We don't need to transfer limits, but rather use limits from POA service template.
		That is a default option for PPA customers.
		2. We need to transfer limits from H-Sphere to PPA for correct billing migration,
		we change POA resource limits instead of Plesk limits
		in function _transfer_resource_limits of H-Sphere migrator.

		But result is the same - we don't transfer resource limits from backup dump.
		"""
		return False

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

	@cached
	def get_subscription_target_services(self, global_context, subscription_name):
		"""Get location of subscriptions's hosting services on target panel

		:type global_context: parallels.core.global_context.GlobalMigrationContext
		:type subscription_name: basestring
		:rtype: parallels.core.subscription_target_info.TargetServices
		"""
		plesk_server = self.get_subscription_plesk_node(global_context, subscription_name)
		plesk_api = plesk_server.plesk_api()

		request = plesk_ops.SubscriptionOperator.Get(
			plesk_ops.SubscriptionOperator.FilterByName([subscription_name]),
			[
				plesk_ops.SubscriptionOperator.Dataset.GEN_INFO,
				plesk_ops.SubscriptionOperator.Dataset.HOSTING_BASIC,
				plesk_ops.SubscriptionOperator.Dataset.MAIL,
			]
		)

		subscription_info_response = plesk_api_utils.request_single_optional_item(plesk_api, request)

		if subscription_info_response is None:
			raise MigrationError(
				messages.SUBSCRIPTION_DOES_NOT_EXIST_ON_TARGET_PPA
			)

		subscription_info = subscription_info_response[1]
		hosting = subscription_info.hosting
		mail_info = subscription_info.mail
		dns_ips = self.get_import_api(global_context).get_domain_dns_server_ips(plesk_server, subscription_name)

		with global_context.conn.target.main_node_runner() as runner:
			# this is necessary to get destination database server password
			with migrator_utils.plain_passwords_enabled(runner):
				db_servers_info = plesk_api_utils.get_subscription_db_servers(plesk_api, subscription_name)

				db_servers = {
					server.server_type: plesk_server.get_db_servers().get(server.server_id)
					for server in db_servers_info[1]
				}

		if isinstance(hosting, (
			plesk_ops.SubscriptionHostingVirtual,
			plesk_ops.SubscriptionHostingStandardForwarding,
			plesk_ops.SubscriptionHostingFrameForwarding
		)):
			web_ips = SubscriptionServiceIPs(v4=hosting.ip_addresses.v4, v6=hosting.ip_addresses.v6)
		else:
			web_ips = SubscriptionServiceIPs()

		if mail_info is not None:
			mail_ips = SubscriptionServiceIPs(v4=mail_info.ip_addresses.v4, v6=mail_info.ip_addresses.v6)
		else:
			mail_ips = SubscriptionServiceIPs()

		return TargetServices(
			web_ips=web_ips,
			mail_ips=mail_ips,
			db_servers=db_servers,
			dns_ips=[SubscriptionServiceIPs(v4=dns_ip, v6=None) for dns_ip in dns_ips]
		)

	@cached
	def get_subscription_nodes(self, global_context, subscription_name):
		"""Get servers of subscription on target panel

		:type global_context: parallels.core.global_context.GlobalMigrationContext
		:type subscription_name: basestring
		:rtype: parallels.core.subscription_target_info.TargetServers
		"""
		conn = global_context.conn.target
		subscription_target_services = self.get_subscription_target_services(global_context, subscription_name)

		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(
					messages.DNS_SERVER_IS_NOT_REGISTERED_IN_PPA, 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
		)

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

	def get_connections(self, global_context):
		"""Get target panel connections"""
		return PPATargetConnections(
			global_context.config,
			webmail_ipv4_required=global_context.migrator.is_dns_migration_enabled()
		)

	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_adapter(self):
		"""
		:rtype: parallels.core.client_subscription_converter.ClientSubscriptionConverterAdapter
		"""
		return TargetPPAClientSubscriptionConverterAdapter()

	def get_hosting_check_messages_panel_id(self):
		return 'ppa'

	@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.core.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(
				messages.MIGRATION_TO_PPA_OF_THIS_VERSION_IS_NOT_SUPPORTED % (
					plesk_version_str
				)
			)

	@property
	def sysuser_name_disallowed_chars(self):
		# dot is not allowed in system user name on PPA
		return {'.', ' '}

	def get_restore_hosting_additional_env_vars(self, global_context, subscription=None):
		"""Get additional target panel-specific environment variables to pass to hosting restoration utilities

		:type global_context: parallels.core.global_context.GlobalMigrationContext
		:type subscription: parallels.core.migrated_subscription.MigratedSubscription | None
		:rtype: dict[str, str]
		"""
		env = dict()

		# Note that PLESK_DISABLE_APSMAIL_PROVISIONING must always stay defined, as it indicates to
		# PMM that it should work despite heterogeneous mode
		if subscription is not None and subscription.is_mail_assimilate:
			env['PLESK_DISABLE_APSMAIL_PROVISIONING'] = u'true'
		else:
			env['PLESK_DISABLE_APSMAIL_PROVISIONING'] = u'false'

		# PLESK_DISABLE_PROVISIONING=false must stay defined for the same reasons
		# when migrating to PPA 11.5 which does not have latest micro updates
		# installed, and does not know about PLESK_DISABLE_APSMAIL_PROVISIONING (this
		# could be cleaned up on the next major update when migrator requires new
		# version of PPA)
		env['PLESK_DISABLE_PROVISIONING'] = u'false'

		env['PPA_IGNORE_FILE_MATCHING'] = u'true'

		return env

	def get_import_dump_additional_env_vars(self, global_context):
		"""Get additional target panel-specific environment variables to pass to pmmcli when importing dump

		:type global_context: parallels.core.global_context.GlobalMigrationContext
		:rtype: dict[str, str]
		"""
		return {'PPA_IGNORE_FILE_MATCHING': 'true'}