import logging
from contextlib import contextmanager
from collections import namedtuple

from parallels.common.connections.ssh.connection_pool import SSHConnectionPool
from parallels.utils import cached
from parallels.common import run_command
from parallels import poa_api
from parallels import plesk_api
from parallels.ppab_api import import_api as billing_import_api
import parallels.common.migrator_config as connections_config
from parallels.common import MigrationError
from parallels.common.connections.checker import ConnectionChecker
from parallels.target_panel_plesk.connections.target_server import PleskTargetServer
from parallels.common.connections.target_connections import TargetConnections
from parallels.common.utils.session_dir import UnixSessionDir


logger = logging.getLogger(__name__)

class PPATargetConnections(TargetConnections):
	def __init__(self, config):
		self._ppa_settings = connections_config.read_ppa_settings(config, 'ppa')
		if not self._ppa_settings.is_local:
			self._ssh = SSHConnectionPool.get_instance().get(
				self._ppa_settings, 'PPA management node'
			)
		self._session_dir = UnixSessionDir(
			self.main_node_runner, self._ppa_settings.unix_session_dir
		)

		logger.info(u"PPA management node: %s", self._ppa_settings.ip)
		logger.info(u"PPA Plesk URL: %s", self._ppa_settings.plesk_api.url)

	@property
	@cached
	def plesk_server(self):
		return PleskTargetServer(self)
		
	@property
	def main_node_ip(self):
		return self._ppa_settings.ip

	@property
	def panel_admin_password(self):
		return self._ppa_settings.plesk_api.auth.password

	@contextmanager
	def main_node_runner(self):
		if self._ppa_settings.is_local:
			yield run_command.LocalUnixRunner()
		else:
			with self._ssh.runner() as runner:
				yield runner

	def main_node_description(self):
		return 'PPA management node'

	def main_node_session_file_path(self, filename):
		return self._session_dir.get_file_path(filename)

	@property
	def session_dir_obj(self):
		return self._session_dir

	@contextmanager
	def ppa_unix_node_runner(self, node_id):
		with self.main_node_runner() as ppa_runner:
			yield run_command.UnixHclRunner(
				ppa_runner, node_id, self.get_ppa_node_description(node_id)
			)

	@contextmanager
	def ppa_windows_node_runner(self, node_id):
		with self.main_node_runner() as ppa_runner:
			yield run_command.WindowsHclRunner(
				ppa_runner, node_id, self.get_ppa_node_description(node_id)
			)

	@cached
	def poa_api(self):
		return poa_api.Client(self._ppa_settings.poa_api.url)

	def plesk_api(self):
		return plesk_api.Client(
			self._ppa_settings.plesk_api.url,
			self._ppa_settings.plesk_api.auth.username,
			self._ppa_settings.plesk_api.auth.password,
			plesk_api.api_versions['11.5'],
			pretty_print=True
		)

	def billing_api(self):
		connection_info = self._get_billing_connection_info()
		return billing_import_api.PPABImportAPI(connection_info.url, connection_info.username, connection_info.password)
	
	def _get_billing_connection_info(self):
		xml_rpc_info = self.poa_api().get_bm_xml_rpc_address()
		protocol = 'https' if xml_rpc_info.get('ssl', True) == True else 'http'
		return BillingConnectionInfo(
			url='%s://%s:%s%s' % (protocol, xml_rpc_info['host'], xml_rpc_info.get('portnum', '5224'), xml_rpc_info.get('uri', '/RPC2')),
			username=self._ppa_settings.plesk_api.auth.username,
			password=self._ppa_settings.plesk_api.auth.password,
		)

	@cached
	def get_ppa_node_description(self, node_id):
		"""Get description of service node that should be used in log messages.
		Function is cached, so use it only in stable environment, when you do not expect new nodes to be registered/assimilated"""

		node_info = self.poa_api().getHost(node_id)

		if len(node_info.ip_addresses) > 0:
			ip_address = node_info.ip_addresses[0].address
		else:
			ip_address = 'unknown IP' # it should not be possible, but if it is so due to some bug, use this just not to fail all checks

		return "PPA service node '%s' (#%s, %s)" % (node_info.hostname, node_id, ip_address)

	def check_connections(self):
		"""Check connections to POA API, Plesk API and SSH.
		   Raise MigrationError if required connection(s) are not available.
		"""	
		logger.debug(u"Check target POA API connection")
		try:
			self.poa_api().get_ppa_license_status() # just a sample request
		except Exception as err:
			logger.debug(u"Exception:", exc_info=err)
			raise MigrationError(
				u'''Failed to check POA API at %s: %s. Make sure that PPA Public API is configured and there are no firewall rules that could block connections to it.
Also the issue might be caused by incorrect PPA Public API configuration. Please verify settings at: "System" - "Settings" - "Public API":
1. "SSL" should be enabled.
2. "HTTP Authentication" should be enabled.
3. "Accept connections" should have one of the values:
   1) "Only from allowed networks". In that case you should add host where you run migration/moving tools to allowed hosts at "Allowed Networks" tab.
   2) "From everywhere". No additional configuration is required in that case, still this option is not recommended due to security considerations.''' % (self._ppa_settings.poa_api.url, err)
		   )

		connection_checker = ConnectionChecker()
		connection_checker.check_target_plesk_api(self.plesk_api(), 'PPA')
		if not self._ppa_settings.is_local:
			connection_checker.check_ssh('PPA', self._ppa_settings)

	@property
	def unix_session_dir(self):
		return self._ppa_settings.unix_session_dir

	@property
	def windows_session_dir(self):
		return self._ppa_settings.windows_session_dir

	@property
	def is_windows(self):
		return False

	@property
	def is_local(self):
		"""If main node of target server is local or remote

		Local server means that migrator works on the target node, remote means
		that migrator's node and target node are different servers
		"""
		return self._ppa_settings.is_local

BillingConnectionInfo = namedtuple('BillingConnectionInfo', ('url', 'username', 'password'))

