from parallels.core.subscription_target_info import TargetServices, SubscriptionServiceIPs, TargetServers
from parallels.core.utils import plesk_api_utils
from parallels.core.utils.plesk_api_utils import request_single_optional_item
from parallels.plesk import messages
from parallels.core import MigrationNoContextError
from parallels.core.utils.common import find_first, cached
from parallels.core.target_panel_base import TargetPanelBase
from parallels.core.utils.plesk_db_credentials_fetcher import PleskDbCredentialsFetcher
from parallels.plesk.client_subscription_converter import TargetPleskClientSubscriptionConverterAdapter
from parallels.plesk.connections.database_target_server import \
	PleskDatabaseTargetServer
from parallels.core.hosting_check.entity_source.service import \
	TargetServiceInfo
from parallels.plesk.utils.xml_rpc.plesk import operator as plesk_ops
from parallels.plesk.utils.xml_rpc.plesk.operator.subscription import SubscriptionHostingNone
from parallels.plesk.connections.target_connections import PleskTargetConnections
from parallels.plesk.import_api.import_api_unix import PleskUnixImportAPI
from parallels.plesk.import_api.import_api_windows import PleskWindowsImportAPI


class Panel(TargetPanelBase):
	@property
	def name(self):
		return messages.TARGET_PANEL_PLESK_TITLE

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

		:rtype: bool
		"""
		return True

	def has_admin_reseller_plan_feature(self):
		"""Whethere admin reseller plans are supported for that target panel migration

		:rtype: bool
		"""
		return True

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

		:rtype: bool
		"""
		return True

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

		:rtype: bool
		"""
		return True

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

		:rtype: bool
		"""
		return True

	def is_test_dns_forwarding(self):
		"""Whether to test DNS forwarding feature on post-migration checks

		:rtype: bool
		"""
		# Do not test DNS forwarding, as it is experimental not documented feature
		# and should not used by customers by default, so we should not force testing it.
		# Better for user, but more complex solution is to store domains for which we set DNS forwarding
		# and check only that domains
		return False

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

		:rtype: bool
		"""
		return True

	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_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
		"""
		plesk_server = self.get_subscription_plesk_node(global_context, subscription_name)
		subscription_target_services = self.get_subscription_target_services(global_context, subscription_name)

		database_servers = {}

		for db_type, db_params in subscription_target_services.db_servers.iteritems():
			if db_params is None:
				continue

			login, password = PleskDbCredentialsFetcher.get_instance().get(
				plesk_server, db_type, db_params
			)

			database_servers[db_type] = PleskDatabaseTargetServer(
				db_type, db_params.host, db_params.port,
				login, password, plesk_server
			)

		return TargetServers(
			web=plesk_server,
			mail=plesk_server,
			database=database_servers,
			dns=[plesk_server]
		)

	@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,
				plesk_ops.SubscriptionOperator.Dataset.MAIL,
			]
		)

		subscription_info_response = request_single_optional_item(plesk_api, request)

		if subscription_info_response is None:
			# Subscription was not created on target Plesk yet. Predict services location by service template.
			return self.predict_subscription_target_services(global_context, subscription_name)

		subscription_info = subscription_info_response[1]
		hosting = subscription_info.hosting

		raw_dump = global_context.get_subscription(subscription_name).raw_dump
		if isinstance(hosting, SubscriptionHostingNone) and raw_dump.hosting_type != 'none':
			# Subscription has no virtual hosting and has no information about services location.
			# Wa may enable hosting during migration, so here we predict target services location by service template.
			return self.predict_subscription_target_services(global_context, subscription_name)

		mail_info = subscription_info.mail

		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=plesk_server.ip(), v6=None)]
		)

	def predict_subscription_target_services(self, global_context, subscription_name):
		"""
		:rtype: parallels.core.subscription_target_info.TargetServices
		"""
		subscription_database_nodes = {}

		subscription = global_context.get_subscription(subscription_name)

		api = self.get_import_api(global_context)
		owner_username = None if subscription.model_reseller is None else subscription.model_reseller.login
		service_template_details = api.get_service_template_details_by_name(subscription.model.plan_name, owner_username)

		plesk_server = self._get_plesk_target_server(global_context.conn.target)

		database_server_types = {database.dbtype for database in subscription.raw_dump.all_databases}

		for database_server_type in database_server_types:
			target_database_server = None
			# try to detect target database server based on database server details,
			# specified as default for target service template
			if service_template_details is not None:
				default_database_server = find_first(
					service_template_details.database_servers,
					lambda x: x.type == database_server_type
				)
				if default_database_server is not None and plesk_server.has_database_server(
					default_database_server.host,
					default_database_server.type
				):
					target_database_server = plesk_server.get_database_server(
						default_database_server.host,
						default_database_server.type
					)
			# try to detect target database server based on target panel database servers settings
			if target_database_server is None:
				target_database_server = plesk_server.get_default_database_server(database_server_type)
			# raise exception, if unable to detect target database server
			if target_database_server is None:
				raise Exception(messages.UNABLE_TO_FIND_DB_SERVER_ON_TARGET_ISSUE % database_server_type)

			subscription_database_nodes[target_database_server.dbtype] = target_database_server

		web_ips = SubscriptionServiceIPs(v4=subscription.model.web_ip, v6=subscription.model.web_ipv6)

		return TargetServices(
			web_ips=web_ips,
			mail_ips=web_ips,
			db_servers=subscription_database_nodes,
			dns_ips=plesk_server.ip(),
		)

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

	def get_service_nodes(self, conn):
		service_nodes = [
			TargetServiceInfo(
				service_type='web',
				node=self._get_plesk_target_server(conn)
			),
			TargetServiceInfo(
				service_type='mail',
				node=self._get_plesk_target_server(conn)
			),
			TargetServiceInfo(
				service_type='dns',
				node=self._get_plesk_target_server(conn)
			)
		]
		for result in conn.plesk_api().send(
			plesk_ops.DbServerOperator.Get(
				filter=plesk_ops.DbServerOperator.FilterAll()
			)
		):
			# We can not use the database server until credentials is not
			# set. Example: for postgresql credentials don't set after
			# install.
			if result.data.status == 'CREDENTIALS_NOT_SET':
				continue
			# Check only local database server.
			if result.data.local:
				service_nodes.append(
					TargetServiceInfo(
						service_type=result.data.dbtype,
						node=self._get_plesk_target_server(conn)
					)
				)
		return service_nodes

	def get_connections(self, global_context):
		"""Get target panel connections"""
		return PleskTargetConnections(global_context.config)

	def get_import_api(self, global_context):
		if global_context.conn.target.is_windows:
			return PleskWindowsImportAPI(global_context.conn.target)
		else:
			return PleskUnixImportAPI(global_context.conn.target)

	def get_converter_adapter(self):
		"""
		:rtype: parallels.core.client_subscription_converter.ClientSubscriptionConverterAdapter
		"""
		return TargetPleskClientSubscriptionConverterAdapter()

	def get_hosting_check_messages_panel_id(self):
		return 'plesk'

	@staticmethod
	def _get_plesk_target_server(conn):
		return conn.plesk_server

	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 < (12, 0):
			raise MigrationNoContextError(
				messages.TARGET_PLESK_VERSION_NOT_SUPPORTED % (
					plesk_version_str
				)
			)

	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]
		"""
		# Tell pmmcli to convert imported dump into latest format with hierarchical directory structure
		return {'PPA_IGNORE_FILE_MATCHING': 'true'}
