from parallels.core.run_command import LocalUnixRunner
from parallels.ppa import messages
import logging
from contextlib import contextmanager
from collections import namedtuple

from parallels.core.connections.ssh.connection_pool import SSHConnectionPool
from parallels.core.utils.common import cached
from parallels.ppa.run_command.unix_hcl_runner import UnixHclRunner
from parallels.ppa.run_command.windows_hcl_runner import WindowsHclRunner
from parallels.ppa.utils.xml_rpc.poa import poa_api
from parallels.plesk.utils.xml_rpc import plesk as plesk_api
from parallels.ppa.utils.xml_rpc.ppab import import_api as billing_import_api
from parallels.ppa import config
from parallels.core import MigrationError
from parallels.core.connections.checker import ConnectionChecker
from parallels.plesk.connections.target_server import PleskTargetServer
from parallels.core.connections.target_connections import TargetConnections
from parallels.core.utils.session_dir import UnixSessionDir

logger = logging.getLogger(__name__)


class PPATargetConnections(TargetConnections):
	def __init__(self, config_data, webmail_ipv4_required=False, default_apache_restart_interval='none'):
		self._ppa_settings = config.read_ppa_settings(
			config_data, 'ppa', webmail_ipv4_required, default_apache_restart_interval
		)
		if not self._ppa_settings.is_local:
			self._ssh = SSHConnectionPool.get_instance().get(
				self._ppa_settings, messages.PPA_MN_DESCRIPTION
			)
		self._session_dir = UnixSessionDir(
			self.main_node_runner, self._ppa_settings.unix_session_dir
		)

		logger.info(messages.LOG_PPA_MN, self._ppa_settings.ip)
		logger.info(messages.LOG_PPA_PLESK_URL, self._ppa_settings.plesk_api.url)

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

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

	@property
	def panel_port(self):
		return self._ppa_settings.plesk_api.port

	@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 LocalUnixRunner()
		else:
			with self._ssh.runner() as runner:
				yield runner

	def main_node_description(self):
		return messages.PPA_MN_DESCRIPTION

	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 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 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) 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:
			# it should not be possible, but if it is so due to some bug, use this just not to fail all checks
			ip_address = messages.UNKNOWN_IP

		return messages.PPA_SN_DESCRIPTION % (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(messages.CHECK_TARGET_POA_API_CONNECTION)
		try:
			# just a sample request
			self.poa_api().get_ppa_license_status()
		except Exception as err:
			logger.debug(messages.LOG_EXCEPTION, exc_info=True)
			raise MigrationError(
				messages.FAILED_TO_CHECK_POA_API % (self._ppa_settings.poa_api.url, err)
			)

		connection_checker = ConnectionChecker()
		connection_checker.check_plesk_api(self.plesk_server)
		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'))
