from parallels.plesk import messages
import os
from contextlib import contextmanager

import parallels.plesk.config as connections_config
from parallels.plesk.utils.xml_rpc import plesk as plesk_api
from parallels.core.registry import Registry
from parallels.core.connections.checker import ConnectionChecker
from parallels.core import run_command
from parallels.core.utils.config_utils import ConfigSection
from parallels.core.connections.target_connections import TargetConnections
from parallels.core.utils.plesk_cli_runner import PleskCLIRunnerLocalCLIGate, PleskCLIRunnerCLI
from parallels.core.utils.plesk_cli_runner import CLIGateAuthPassword
from parallels.core.utils.session_dir import UnixSessionDir
from parallels.core.utils.session_dir import WindowsSessionDir
from parallels.core.connections.ssh.connection_pool import SSHConnectionPool
from parallels.plesk.utils.xml_rpc.plesk import get_max_supported_api_version
from parallels.plesk.utils.xml_rpc.plesk.operator import ServerOperator
from parallels.plesk.connections.target_server import PleskTargetServer
from parallels.core.utils.common import cached


class PleskTargetConnections(TargetConnections):
	def __init__(self, config):
		self.config = config
		self._plesk_settings = connections_config.read_target_plesk_settings(config, 'plesk')
		if not self._plesk_settings.is_windows:
			if not self._plesk_settings.is_local:
				self._ssh = SSHConnectionPool.get_instance().get(
					self._plesk_settings, messages.TARGET_PLESK_SERVER_TITLE
				)

		if self._plesk_settings.is_windows:
			self._session_dir = WindowsSessionDir(
				self.main_node_runner, self._plesk_settings.windows_session_dir
			)
		else:
			self._session_dir = UnixSessionDir(
				self.main_node_runner, self._plesk_settings.unix_session_dir
			)

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

	@property
	@cached
	def plesk_server(self):
		return PleskTargetServer(self)

	@property
	def main_node_ip(self):
		return self._plesk_settings.ip

	@contextmanager
	def main_node_runner(self):
		if not self._plesk_settings.is_windows:
			if self._plesk_settings.is_local:
				yield run_command.LocalUnixRunner()
			else:
				with self._ssh.runner() as runner:
					yield runner
		else:
			if self._plesk_settings.is_local:
				yield run_command.LocalWindowsRunner.get_instance()
			else:
				yield self._get_windows_main_node_runner()

	@cached
	def _get_windows_main_node_runner(self):
		if self._plesk_settings.agent_settings.enabled:
			migrator_server = Registry().get_instance().get_context().migrator_server
			return run_command.WindowsAgentRunner(self._plesk_settings, migrator_server)
		else:
			# XXX we should not read config file except for target
			# plesk options there
			global_section = ConfigSection(self.config, 'GLOBAL')
			local_temp_dir = os.path.join(
				Registry.get_instance().get_base_dir(),
				'sessions',
				global_section.get('session-dir', 'migration-session')
			)
			return run_command.WinexeRunner(self._plesk_settings, local_temp_dir=local_temp_dir)

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

	def main_node_description(self):
		return messages.TARGET_PLESK_SERVER_TITLE_WITH_IP % (self.main_node_ip,)

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

	@property
	def panel_admin_password(self):
		config_password = self._plesk_settings.plesk_api.auth.password
		if config_password:
			# If specified in config explicitly - use it
			return config_password
		else:
			# If not specified in config - try to detect
			return self.plesk_server.panel_admin_password

	@cached
	def plesk_api(self):
		client = plesk_api.Client(
			self._plesk_settings.plesk_api.url,
			self._plesk_settings.plesk_api.auth.username,
			self.panel_admin_password,
			plesk_api.api_versions['8.4'],
			pretty_print=True
		)

		# Get Plesk version.
		result = client.send(
			ServerOperator.Get([
				ServerOperator.Dataset.STAT,
			])
		)
		server_info = result.data

		client.api_version = get_max_supported_api_version(
			server_info.statistics.versions.plesk_version
		)

		return client

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

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

	@property
	def session_dir_obj(self):
		return self._session_dir
	
	def check_connections(self):
		connection_checker = ConnectionChecker()
		connection_checker.check_plesk_api(self.plesk_server)
		if (
			not self._plesk_settings.is_windows and
			not self._plesk_settings.is_local
		):
			connection_checker.check_ssh(self.main_node_description(), self._plesk_settings)
		else:
			with self.main_node_runner() as runner:
				runner.check(self.main_node_description())

	@property
	def is_windows(self):
		return self._plesk_settings.is_windows

	@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._plesk_settings.is_local

	@property
	@cached
	def plesk_cli_runner(self):
		"""
		:rtype: parallels.core.utils.plesk_cli_runner.PleskCLIRunnerBase
		"""
		if self.is_local and self.is_windows:
			plesk_cli_runner = PleskCLIRunnerLocalCLIGate(
				CLIGateAuthPassword(self.panel_admin_password), True,
				self._plesk_settings.cligate_ip,
				self._plesk_settings.plesk_api.port
			)
		else:
			plesk_cli_runner = PleskCLIRunnerCLI(self.plesk_server)
		return plesk_cli_runner
