from parallels.core import messages
import logging
import ntpath
import re
from parallels.core import run_and_check_local_command
from parallels.core.utils import unix_utils
from parallels.core.utils.common import safe_string_repr

logger = logging.getLogger(__name__)


class WindowsNationalSymbolsCommandError(Exception):
	def __init__(self, command):
		# We should never try to run such commands. There are always problems with Windows + national symbols.
		self.command = command
		message = (
			messages.INTERNAL_ERROR_TRYING_EXECUTE_COMMAND_THAT % (command,)
		)
		Exception.__init__(self, message)


def check_windows_national_symbols_command(command):
	try:
		command.decode('ascii')
	except UnicodeError:
		logger.debug(messages.LOG_EXCEPTION, exc_info=True)
		raise WindowsNationalSymbolsCommandError(command)


def quote_arg(arg):
	return '"%s"' % arg.replace('"', '"""')


def format_command(template, *args, **kw):
	qargs = map(quote_arg, args)
	qkw = dict((k, quote_arg(unicode(v))) for k, v in kw.iteritems())
	return unicode(template).format(*qargs, **qkw)


def format_command_list(cmd, args):
	return " ".join([
		quote_arg(arg) for arg in [cmd] + ([] if args is None else args)
	])


def convert_path_to_cygwin(path):
	drive, rel_path = ntpath.splitdrive(path)
	if drive != '':
		drive = drive[0].lower()
		rel_path = rel_path[1:].replace("\\", "/")
		return u"/cygdrive/%s/%s" % (drive, rel_path)
	else:
		return path.replace("\\", "/")


def to_cmd(pathname):
	return pathname.replace('/', '\\')


def _split_samba_pathname(pathname):
	if ':' in pathname:
		disk, pathname_part = pathname.split(':')
		share = u'%s$' % disk
		pathname_cmd = to_cmd(pathname_part)
	else:
		share = u'C$'
		pathname_cmd = to_cmd(pathname)
	# guarantee leading backslash
	pathname_cmd = u"\\%s" % pathname_cmd.lstrip('\\')

	return share, pathname_cmd


def run_smbclient_cmd(cmd, share, settings):
	logger.debug(messages.DEBUG_EXECUTE_SMB_COMMAND, cmd)
	return run_and_check_local_command(
		[
			'/bin/sh',
			'-c',
			unix_utils.format_command(
				u"smbclient //{ip}/{share} -U {login} {password} -c {command}",
				ip=settings.ip, share=share, login=settings.windows_auth.username,
				password=settings.windows_auth.password,
				command=cmd
			)
		]
	)


def upload_file(local_pathname, remote_pathname, settings):
	share, pathname_cmd = _split_samba_pathname(remote_pathname)

	# smbclient removes duplicate slashes and backslashes from paths it reports
	# we compare the paths it reports with paths we specified, to make sure the transfer went successfully
	# so we need to remove duplicate slaesh and backslashes too
	pathname_cmd = pathname_cmd.replace('//', '/').replace('\\\\', '\\')

	stdout, stderr = run_smbclient_cmd(
		u'put {local} "{remote}"'.format(local=local_pathname, remote=pathname_cmd),
		share,
		settings
	)

	if r'putting file {local} as {remote} ('.format(local=local_pathname, remote=pathname_cmd) not in stderr:
		raise Exception(
			messages.FAILED_TO_UPLOAD_FILE_SMB.format(
				local=local_pathname, remote=pathname_cmd, ip=settings.ip, share=share,
				stdout=safe_string_repr(stdout), stderr=safe_string_repr(stderr)
			)
		)


def download_file(remote_pathname, local_pathname, settings):
	logger.debug(messages.DEBUG_DOWNLOADING_FILE, remote_pathname, local_pathname)

	# remove duplicate slashes and backslashes, to successfully match smbclient's output with what we expect
	remote_pathname = remote_pathname.replace('//', '/').replace('\\\\', '\\')

	share, pathname_cmd = _split_samba_pathname(remote_pathname)

	stdout, stderr = run_smbclient_cmd(
		u'get "{remote}" {local}'.format(local=local_pathname, remote=pathname_cmd),
		share,
		settings
	)

	match = re.search(ur'getting file {remote} of size [0-9]+ as {local} \('.format(
		local=re.escape(local_pathname), remote=re.escape(pathname_cmd)), stderr, re.MULTILINE
	)
	if match is None:
		raise Exception(
			messages.FAILED_TO_DOWNLOAD_FILE_SMB.format(
				local=local_pathname, remote=pathname_cmd, ip=settings.ip, share=share,
				stdout=safe_string_repr(stdout), stderr=safe_string_repr(stderr)
			)
		)


def download_directory_contents(remote_pathname, local_pathname, settings):
	logger.debug(messages.LOG_DOWNLOAD_DIRECTORY, remote_pathname, local_pathname)

	share, pathname_cmd = _split_samba_pathname(remote_pathname)

	run_smbclient_cmd(
		u'prompt OFF;recurse ON;lcd {local};cd "{remote}";mget *'.format(
			local=local_pathname, remote=pathname_cmd
		), share, settings
	)


def file_exists(runner, filepath):
	cmd = u"cmd.exe /C if exist {filepath} ( echo true ) else ( echo false )"
	args = dict(filepath=filepath)
	result = runner.sh(cmd, args).strip()
	if result == 'true':
		return True
	elif result == 'false':
		return False
	else:
		raise Exception(
			messages.FILE_EXISTS_INVALID_COMMAND_OUTPUT.format(
				command=format_command(cmd, **args),
				output=result
			)
		)


def path_join(base_path, *paths):
	"""Join the paths using backslash as a separator, and return the result.
	Take care of the excess separators between the joined path chunks.

	This function behaves similar to ntpath.join
	path_join('\\\\sambashare\\dir1\\', 'file1') returns the same '\\\\sambashare\\dir1\\file1'
	as ntpath.join('\\\\sambashare\\dir1\\', 'file1')
	but does not allow a subsequent path to override all the paths before:
	path_join('\\dir1', '\\file1') returns '\\dir1\\file1',
	whereas ntpath.join('\\dir1', '\\file1') returns '\\file1'.
	It also takes more care of the excess separators than ntpath.join does:
	path_join('\\dir1\\\\dir2\\\\', '\\file1') returns '\\dir1\\\\dir2\\file1',
	whereas ntpath.join('\\dir1\\\\dir2\\\\', '\\file1') returns '\\dir1\\\\dir2\\\\file1'.
	"""
	if len(paths) == 0:
		return base_path

	sep_to_strip = r'\/'
	sep_to_add = '\\'

	joined_path = base_path.rstrip(sep_to_strip)

	for path in paths[:-1]:
		joined_path += sep_to_add + path.strip(sep_to_strip)
	joined_path += sep_to_add + paths[-1].lstrip(sep_to_strip)

	return joined_path


def get_dbbackup_mssql_host(host, ip):
	"""Return canonical MSSQL instance host that could be passed
	to dbbackup.exe Plesk utility
	
	Possible host values:
	1) 'localhost'. That means default MSSQL instance on
	local server, and this function returns IP address instead.
	2) Something like ".\MSSQLSERVER2008" (string starts with '.\'), 
	that means "MSSQLSERVER2008" named instance on local server,
	so if local server's IP is 192.168.1.1, this function returns
	"192.168.1.1\MSSQLSERVER2008"
	3) Full path to MSSQL instance, for example 
	'192.168.1.1\MSSQLSERVER' (named instance) or 
	'192.168.1.1' (default instance)
	That is returned as is.
	"""
	if host == 'localhost':
		return ip
	elif host.startswith('.\\'):
		return r"%s\%s" % (ip, host[2:])
	else:
		return host


def cmd_command(command):
	"""Format command to be executed under cmd.exe"""
	return 'cmd /c "%s"' % (command,)


def fix_path(path):
	"""Fix incorrect and double slashes in Windows path"""
	return path.replace('//', '/').replace('/', '\\')


def convert_path(subscription, source_path, source_vhost_path):
	"""Convert path from source to target
	Paths are normalized before comparison and replacement.
	Return normalized result path.
	"""
	def normalize_path(p):
		return p.replace('/', '\\').rstrip('\\')

	source_path = normalize_path(source_path)
	source_vhost_path = normalize_path(source_vhost_path)
	new_vhost_path = normalize_path(path_join(subscription.web_target_server.vhosts_dir, subscription.name_idn))

	if source_path.lower().startswith(source_vhost_path.lower() + '\\'):
		new_vdir_path = path_join(new_vhost_path, source_path[len(source_vhost_path) + 1:])
	elif source_path.lower() == source_vhost_path.lower():
		new_vdir_path = new_vhost_path
	else:
		new_vdir_path = source_path

	# if we don't strip double backslash, error documents of File type will not work after transfer.
	return new_vdir_path.replace('\\\\', '\\')


def get_from_registry(runner, keys, value):
	"""Get value from Windows registry

	:type keys: list[str]
	:type value: str
	:rtype: str
	"""
	for key in keys:
		(code, stdout, _) = runner.sh_unchecked('reg query "%s" /v "%s"' % (key, value))
		if code is 0:
			match = re.search('^\s*%s\s+REG_(EXPAND_)?SZ\s+(.*?)\s*$' % re.escape(value), stdout, re.MULTILINE)
			if match is not None:
				return match.group(2).strip()
	return None


def find_in_registry(runner, keys, pattern):
	"""
	Return true if key with given pattern exist below one of given keys in registry, false otherwise
	:type runner: parallels.core.run_command.BaseRunner
	:type keys: list[str]
	:type pattern: str
	:rtype: bool
	"""
	for key in keys:
		(code, stdout, _) = runner.sh_unchecked('reg query "%s"' % key)
		if code is 0:
			match = re.search('^%s%s$' % (re.escape(key + '\\'), pattern), stdout, re.MULTILINE)
			if match is not None:
				return True
	return False

