from parallels.target.plesk import messages
from parallels.core import MigrationNoContextError
from parallels.core.utils.common import find_first
from parallels.core.migrator import SubscriptionTargetServices
from parallels.core.connections.target_servers import TargetServers
from parallels.core.target_panel_base import TargetPanelBase
from parallels.core.utils.plesk_db_credentials_fetcher import PleskDbCredentialsFetcher
from parallels.target.plesk.connections.database_target_server import \
	PleskDatabaseTargetServer
from parallels.core.hosting_check.entity_source.service import \
	TargetServiceInfo
from parallels.api.plesk import operator as plesk_ops
from parallels.api.plesk.operator.subscription import Ips
from parallels.target.plesk.connections.target_connections import PleskTargetConnections
from parallels.target.plesk.converter.converter import PleskConverter
from parallels.target.plesk.import_api.import_api_unix import PleskUnixImportAPI
from parallels.target.plesk.import_api.import_api_windows import PleskWindowsImportAPI
from parallels.target.plesk.workflow import extend_workflow_with_plesk_actions


class PleskTargetPanel(TargetPanelBase):
	def __init__(self):
		self.db_credentials_cache = dict()
		self._plesk_target_server = None

	@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_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

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

		database_servers = {}
		if subscription_target_services is not None:
			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]
		)

	def get_subscription_target_services(self, global_context, subscription_name):
		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_backup.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 = Ips(subscription.model.web_ip, subscription.model.web_ipv6)

		return SubscriptionTargetServices(
			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, config):
		"""Get target panel connections"""
		return PleskTargetConnections(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_class(self):
		return PleskConverter

	def get_hosting_check_messages_panel_id(self):
		return 'plesk'

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

	@staticmethod
	def extend_workflow(workflow):
		"""Extend shared hosting workflow with plesk-specific actions
		:type workflow parallels.core.workflow.base_workflow.BaseWorkflow
		:rtype None
		"""
		extend_workflow_with_plesk_actions(workflow)

	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
				)
			)
