import os
from contextlib import contextmanager

import parallels.common.migrator_config as connections_config
from parallels import plesk_api
from parallels.common.registry import Registry
from parallels.common.connections.checker import ConnectionChecker
from parallels.common import run_command
from parallels.common.utils.config_utils import ConfigSection
from parallels.common.connections.target_connections import TargetConnections
from parallels.common.utils.plesk_cli_runner import PleskCLIRunnerLocalCLIGate, PleskCLIRunnerCLI
from parallels.common.utils.plesk_cli_runner import CLIGateAuthPassword
from parallels.common.utils.session_dir import UnixSessionDir
from parallels.common.utils.session_dir import WindowsSessionDir
from parallels.common.connections.ssh.connection_pool import SSHConnectionPool
from parallels.plesk_api import get_max_supported_api_version
from parallels.plesk_api.operator import ServerOperator
from parallels.target_panel_plesk.connections.target_server import PleskTargetServer
from parallels.utils 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, 'target Plesk server'
				)

		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
	@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:
			return run_command.WindowsAgentRunner(self._plesk_settings)
		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 'target Plesk server (%s)' % (self.main_node_ip,)

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

	def plesk_api(self):
		client = plesk_api.Client(
			self._plesk_settings.plesk_api.url,
			self._plesk_settings.plesk_api.auth.username,
			self._plesk_settings.plesk_api.auth.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_target_plesk_api(self.plesk_api(), self.main_node_description())
		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):
		if self.is_local and self.is_windows:
			plesk_cli_runner = PleskCLIRunnerLocalCLIGate(
				CLIGateAuthPassword(self.panel_admin_password), True
			)
		else:
			plesk_cli_runner = PleskCLIRunnerCLI(self.plesk_server)
		return plesk_cli_runner