import logging
from contextlib import contextmanager
import ntpath
import posixpath
from parallels.core import messages
from parallels.core.connections.unix_server import UnixServer
from parallels.core.utils.windows_utils import get_from_registry

from parallels.core.utils.common import cached
from parallels.core.utils import plesk_utils
from parallels.core.utils import migrator_utils
from parallels.core.utils import windows_utils
from parallels.api.plesk import operator as plesk_ops
from parallels.core.utils import plesk_api_utils
from parallels.core.utils.plesk_utils import get_plesk_ips_with_cli, get_plesk_ips_with_api

logger = logging.getLogger(__name__)


class PleskServer(UnixServer):
	@contextmanager
	def runner(self):
		raise NotImplementedError()

	def description(self):
		raise NotImplementedError()

	def is_windows(self):
		raise NotImplementedError()

	def plesk_api(self):
		raise NotImplementedError()

	@property
	@cached
	def vhosts_dir(self):
		if self.is_windows():
			get_vhosts_dir_function = plesk_utils.get_windows_vhosts_dir
		else:
			get_vhosts_dir_function = plesk_utils.get_unix_vhosts_dir

		with self.runner() as runner:
			return get_vhosts_dir_function(runner)

	def get_vhost_dir(self, domain):
		"""Get path to virtual host's directory of domain"""
		vhost_name = domain.encode('idna')
		if self.is_windows():
			return windows_utils.path_join(self.vhosts_dir, vhost_name)
		else:
			return posixpath.join(self.vhosts_dir, vhost_name)

	@property
	@cached
	def plesk_dir(self):
		if self.is_windows():
			with self.runner() as runner:
				return plesk_utils.get_windows_plesk_dir(runner)
		else:
			with self.runner() as runner:
				return plesk_utils.get_unix_product_root_dir(runner)

	@property
	@cached
	def websrvmng_bin(self):
		"""Get full path to websrvmng Plesk utility

		:rtype: unicode
		"""
		if not self.is_windows():
			raise NotImplementedError()

		if self.get_plesk_version() >= (12, 0) and self.is_64_bit_os:
			return ur'{plesk_dir}\admin\bin64\websrvmng'.format(plesk_dir=self.plesk_dir)
		else:
			return ur'{plesk_dir}\admin\bin\websrvmng'.format(plesk_dir=self.plesk_dir)

	@property
	@cached
	def is_64_bit_os(self):
		"""Check if this is 64-bit OS or 32-bit one

		:rtype: bool
		"""
		if not self.is_windows():
			raise NotImplementedError()

		try:
			with self.runner() as runner:
				# Get architecture from registry.
				# Don't try to get it from environment variable directly, as migrator is running 32-bit version
				# of Python, and Windows sets environment variable accordingly, and you always get 'x86'.
				# Also, avoid using 'wmic os get osarchitecture', as it does not work on Windows 2003.
				return '64' in get_from_registry(
					runner, [r'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'],
					'PROCESSOR_ARCHITECTURE'
				)
		except Exception as e:
			logger.debug(messages.LOG_EXCEPTION, exc_info=True)
			logger.error(messages.FAILED_TO_DETECT_ARCH.format(server=self.description(), reason=unicode(e)))
			return True

	@property
	@cached
	def data_dir(self):
		if self.is_windows():
			with self.runner() as runner:
				return plesk_utils.get_windows_data_dir(runner)
		else:
			with self.runner() as runner:
				#  for Plesk for Unix data dir is the same as root dir
				return plesk_utils.get_unix_product_root_dir(runner)

	@property
	@cached
	def mail_dir(self):
		if self.is_windows():
			raise NotImplementedError()
		else:
			with self.runner() as runner:
				return plesk_utils.get_unix_mailnames_dir(runner)

	@property
	@cached
	def dump_dir(self):
		if self.is_windows():
			with self.runner() as runner:
				dump_dir = plesk_utils.get_windows_dump_dir(runner)
				if not dump_dir:
					# Fallback to %plesk_dir%\Backup
					dump_dir = ntpath.join(self.plesk_dir, u'Backup')
				return dump_dir
		else:
			with self.runner() as runner:
				return plesk_utils.get_unix_dump_dir(runner)

	@cached
	def get_plesk_version(self):
		"""Return a tuple with Plesk server version.
		
		In PPA, Plesk version the same across all service nodes; we get Plesk
		version from management node.
		"""
		if self.is_windows():
			version = plesk_api_utils.get_plesk_version(self.plesk_api())
			return tuple(map(int, (version.split("."))))
		else:
			with self.runner() as runner:
				return plesk_utils.get_plesk_version(runner)

	@property
	@cached
	def rsync_bin(self):
		with self.runner() as runner:
			return migrator_utils.detect_rsync_path(
				runner, self.description()
			)

	@property
	@cached
	def is_mailserver_qmail(self):
		if self.is_windows():
			return False
		else:
			with self.runner() as runner:
				return 'qmail' in runner.sh(
					'plesk sbin mailmng --features | grep SMTP_Server'
				).lower()

	def get_bin_util_path(self, name):
		if self.is_windows():
			return u'%s\\bin\\%s' % (self.plesk_dir, name)
		else:
			return u'%s/bin/%s' % (self.plesk_dir, name)

	@cached
	def get_db_servers(self):
		return {
			result.data.id: result.data
			for result in self.plesk_api().send(plesk_ops.DbServerOperator.Get(plesk_ops.DbServerOperator.FilterAll()))
		}

	def has_database_server(self, server_host, server_type):
		for database_server in self.get_db_servers().itervalues():
			if database_server.host == server_host and database_server.dbtype == server_type:
				return True
		return False

	def get_database_server(self, server_host, server_type):
		for database_server in self.get_db_servers().itervalues():
			if database_server.host == server_host and database_server.dbtype == server_type:
				return database_server
		return None

	def get_default_database_server(self, server_type):
		for database_server in self.get_db_servers().itervalues():
			if database_server.dbtype == server_type and database_server.default:
				return database_server
		return None

	@cached
	def get_all_ips(self, global_context):
		"""Get list of all IP addresses of Plesk server

		Information includes IP address, its type and corresponding public IP address.
		"""
		if self.get_plesk_version() >= (12, 0):
			return get_plesk_ips_with_cli(self)
		else:
			# on old Plesk/PPA 'ipmanage' utility returns incomplete set of IP addresses,
			# fallback to using Plesk API
			return get_plesk_ips_with_api(self)
