from parallels.common import messages
import errno
import logging

from parallels.common import MigrationError, local_command_exec, run_and_check_local_command
from parallels.utils import cached
from parallels.utils.threading_utils import synchronized

logger = logging.getLogger(__name__)


@synchronized
@cached
def install_if_not_installed():
	if not is_installed():
		install()


def is_installed():
	"""Check if imapsync package is installed"""
	return _is_tool_available('imapsync')


def install():
	"""Install imapsync package on CentOS/RHEL 5/6"""
	try:
		install_unsafe()
	except UnsupportedOSForImapsync:
		raise
	except Exception as e:
		logger.debug('Exception: ', exc_info=True)
		raise FailedToInstallImapsync(e)


def install_unsafe():
	os, version, arch = OSVersion.detect()
	logger.debug("Detected os=%s, version=%s, arch=%s", os, version, arch)

	if os not in (OSVersion.OS_CENTOS, OSVersion.OS_RHEL):
		raise UnsupportedOSForImapsync()

	if (version, arch) == (5, OSVersion.ARCH_I386):
		epel_path = '5/i386/epel-release-5-4.noarch.rpm'
	elif (version, arch) == (5, OSVersion.ARCH_X86_64):
		epel_path = '5/x86_64/epel-release-5-4.noarch.rpm'
	elif (version, arch) == (6, OSVersion.ARCH_I386):
		epel_path = '6/i386/epel-release-6-8.noarch.rpm'
	elif (version, arch) == (6, OSVersion.ARCH_X86_64):
		epel_path = '6/x86_64/epel-release-6-8.noarch.rpm'
	else:
		raise UnsupportedOSForImapsync()

	logger.info(messages.INSTALL_IMAPSYNC_TOOL_COPY_MAIL_CONTENT)

	if (os, version) == (OSVersion.OS_RHEL, 6):
		perl_packages = {
			'perl-File-Copy-Recursive': (
				'http://mirror.centos.org/centos/6/os/{arch}/Packages/perl-File-Copy-Recursive-0.38-4.el6.noarch.rpm'
			),
			'perl-TermReadKey': (
				'http://mirror.centos.org/centos/6/os/{arch}/Packages/perl-TermReadKey-2.30-13.el6.{ext_arch}.rpm'
			),
			'perl-Unicode-String': (
				'http://mirror.centos.org/centos/6/os/{arch}/Packages/perl-Unicode-String-2.09-12.el6.{ext_arch}.rpm'
			)
		}
		for name, url in perl_packages.iteritems():
			if not _is_package_installed(name):
				ext_arch = {OSVersion.ARCH_I386: 'i686', OSVersion.ARCH_X86_64: 'x86_64'}[arch]
				run_and_check_local_command(
					['rpm', '-ihv', url.format(arch=arch, ext_arch=ext_arch)]
				)

	# update certificates bundle, otherwise installation from EPEL repo may fail with the following error
	# Cannot retrieve repository metadata (repomd.xml) for repository: epel
	run_and_check_local_command(['yum', '-y', 'update', 'ca-certificates'])

	if not _is_package_installed('epel-release'):
		run_and_check_local_command(['rpm', '-Uhv', 'http://dl.fedoraproject.org/pub/epel/%s' % epel_path])
	if not _is_package_installed('perl-File-Copy-Recursive') and os == OSVersion.OS_CENTOS:
		run_and_check_local_command(['yum', '-y', 'install', 'perl-File-Copy-Recursive'])

	run_and_check_local_command(['yum', '-y', 'install', 'imapsync'])
	logger.info(messages.TOOL_IMAPSYNC_WAS_INSTALLED_SUCCESSFULLY)


def _is_tool_available(tool_name):
	exit_code, _, _ = local_command_exec(["which", tool_name])
	return exit_code == 0


def _is_package_installed(pkg_name):
	stdout, _ = run_and_check_local_command(["rpm", "-qa", "--queryformat=%{NAME}\n"])
	return pkg_name in stdout.split()


class OSVersion(object):
	OS_CENTOS = 'centos'
	OS_RHEL = 'rhel'
	ARCH_X86_64 = 'x86_64'
	ARCH_I386 = 'i386'

	@classmethod
	@cached
	def detect(cls):
		try:
			with open("/etc/redhat-release") as fp:
				release_info = fp.read()
				if "CentOS" in release_info:
					return cls.OS_CENTOS, cls._first_digit(release_info), cls._detect_arch()
				elif 'Red Hat Enterprise Linux Server' in release_info:
					return cls.OS_RHEL, cls._first_digit(release_info), cls._detect_arch()
		except IOError as e:
			if e.errno == errno.ENOENT:
				return None, None, cls._detect_arch()
			else:
				raise

	@staticmethod
	def _first_digit(s):
		digits = [c for c in s if c.isdigit()]
		if len(digits) == 0:
			return None
		else:
			return int(digits[0])

	@classmethod
	def _detect_arch(cls):
		stdout, _ = run_and_check_local_command(['uname', '-m'])
		if stdout.strip() == 'x86_64':
			return cls.ARCH_X86_64
		else:
			return cls.ARCH_I386


class UnsupportedOSForImapsync(MigrationError):
	def __init__(self):
		super(UnsupportedOSForImapsync, self).__init__(
			messages.MAIL_CONTENT_NOT_COPIED_IMAPSYNC_TOOL)


class FailedToInstallImapsync(MigrationError):
	def __init__(self, original_exception):
		super(FailedToInstallImapsync, self).__init__(
			messages.MAIL_CONTENT_NOT_COPIED_FAILED_INSTALL % unicode(original_exception)
		)
		self.original_exception = original_exception