from parallels.common import MigrationError, MigrationNoContextError
from parallels.common.connections.target_servers import TargetServers
from parallels.common.migrator_config import SSHAuthPassword
from parallels.common.target_panel_base import TargetPanelBase
from parallels.common.utils.config_utils import Auth
from parallels.common.utils.plesk_db_credentials_fetcher import PleskDbCredentialsFetcher
from parallels.target_panel_plesk.connections.database_target_server import PleskDatabaseTargetServer
from parallels.target_panel_ppa.connections.target_connections import PPATargetConnections
from parallels.target_panel_ppa.import_api.import_api import WebspacesRequestsHolder
from parallels.target_panel_pvps.connections.target_server import PleskInVPSTargetServer, PleskInVPSWindowsSettings
from parallels.target_panel_pvps.connections.target_server import PleskInVPSUnixSettings
from parallels.target_panel_pvps.import_api.import_api import PVPSImportAPI
from parallels.target_panel_pvps.utils import poa_pvps_utils
from parallels.target_panel_pvps.utils.poa_pvps_utils import get_panel_aps_status
from parallels.utils import cached, generate_random_password, find_only
from parallels.target_panel_pvps.workflow import extend_workflow_with_pvps_actions


class PVPSTargetPanel(TargetPanelBase):
	def name(self):
		return 'Plesk in VPS'

	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 False

	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 False

	def get_subscription_nodes(self, global_context, subscription_target_services, subscription_name):
		subscription_plesk_server = self._get_plesk_node(global_context, subscription_name)

		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(
					subscription_plesk_server, db_type, db_params
				)

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

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

	def get_subscription_plesk_node(self, global_context, subscription_name):
		return self._get_plesk_node(global_context, subscription_name)

	@cached
	def _get_plesk_node(self, global_context, subscription_name):
		"""
		:type global_context: parallels.common.global_context.GlobalMigrationContext
		:type subscription_name: basestring
		"""
		model_subscription = find_only(
			global_context.target_model.iter_all_subscriptions(),
			lambda s: s.name == subscription_name,
			"Failed to find subscription in model"
		)
		if model_subscription.target_subscription is None:
			return None
		subscription_id = model_subscription.target_subscription.id
		if self.is_target_server_installed(global_context.conn.target.poa_api(), subscription_id):
			return PleskInVPSTargetServer(self._get_target_server_settings(global_context, subscription_id))

		raise MigrationError(
			u'Unable to retrieve information about a Plesk installation for the subscription "{name}". '
			u'Please check whether the subscription was created: '
			u'Go to Provider\'s Panel > Operations > Tasks, and see if there are failed tasks '
			u'for the subscription "{name}" (id:{id}). Resolve issues and re-run the failed tasks. '
			u'After the tasks were completed, start the migration again.'.format(
				name=subscription_name, id=subscription_id
			)
		)

	def get_service_nodes(self, conn):
		# TODO list service nodes for service checks
		return []

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

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

	def get_hosting_check_messages_panel_id(self):
		return 'plesk'

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

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

	@staticmethod
	def is_target_server_installed(poa_api, subscription_id):
		"""
		:type poa_api: parallels.poa_api.Client
		"""
		return get_panel_aps_status(poa_api, subscription_id) == u'aps:ready'

	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, 6):
			raise MigrationNoContextError(
				"Migration from shared hosting to dedicated is not supported for PPA %s. "
				"Install PPA 11.6 or later to perform migration to Plesk in VPS." % (
					plesk_version_str
				)
			)

	@staticmethod
	def _change_vps_password(global_context, subscription_id):
		"""
		:type global_context: parallels.common.global_context.GlobalMigrationContext
		:rtype string
		:return new generated password of VPS
		"""
		password = generate_random_password()
		poa_api = global_context.conn.target.poa_api()
		poa_pvps_utils.set_pvps_password(poa_api, subscription_id, password)
		return password

	@staticmethod
	def _get_vps_platform_and_ip(global_context, subscription_id):
		"""
		:type global_context: parallels.common.global_context.GlobalMigrationContext
		"""
		poa_api = global_context.conn.target.poa_api()
		app_instance_id = poa_api.get_subscription_app_instances(subscription_id)
		app_settings = poa_api.get_app_instance_settings(app_instance_id)
		plesk_ip = app_settings['pleskPublicIp'].pop()
		platform = app_settings['configurationDetails.platform'].pop()
		return platform, plesk_ip

	def _get_target_server_settings(self, global_context, subscription_id):
		"""
		:type global_context: parallels.common.global_context.GlobalMigrationContext
		"""
		vps_platform, vps_ip = self._get_vps_platform_and_ip(global_context, subscription_id)
		vps_password = self._change_vps_password(global_context, subscription_id)
		if vps_platform == 'linux':
			return PleskInVPSUnixSettings(
				ip=vps_ip,
				ssh_auth=SSHAuthPassword(
					username='root',
					password=vps_password
				),
				panel_auth=Auth(username='root', password=vps_password),
				session_dir=global_context.conn.target.unix_session_dir
			)
		else:
			auth = Auth(username='administrator', password=vps_password)
			return PleskInVPSWindowsSettings(
				ip=vps_ip,
				windows_auth=auth,
				panel_auth=auth,
				session_dir=global_context.conn.target.windows_session_dir
			)