import logging
import ntpath
import re
from parallels.common import run_and_check_local_command
from parallels.common.utils import unix_utils
from parallels.utils 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 = "Internal error: trying to execute command that contains national symbols on Windows server. Command's Python repr is: %r." % (command,)
		Exception.__init__(self, message)

def check_windows_national_symbols_command(command):
	try:
		command.decode('ascii')
	except UnicodeError as e:
		logger.debug(u"Exception:", exc_info=e)
		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] + 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 detect_plesk_dir(run_command_function):
	command = 'reg query "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v plesk_dir'
	stdout = run_command_function(command)
	match = re.search(u"^\s*plesk_dir\s+REG_(EXPAND_)?SZ\s+(.*?)\s*$", stdout, re.MULTILINE)
	if match is None:
		raise Exception(u"Failed to detect Plesk directory, reg query output is incorrect: %s", stdout)
	return match.group(2)

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)
	pathname_cmd = u"\\%s" % pathname_cmd.lstrip('\\') # guarantee leading backslash

	return share, pathname_cmd

def run_smbclient_cmd(cmd, share, settings):
	logger.debug(u"Execute smbclient command: %s", cmd)
	return run_and_check_local_command(
		[
			'/bin/sh',
			'-c',
			unix_utils.format_command(u"smbclient //{ip}/{share} -U {login} {password}",
				ip=settings.ip, share=share, login=settings.windows_auth.username, password=settings.windows_auth.password
			)
		],
		stdin_content=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(
		r"""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(ur"Failed to upload file {local} to \\{ip}\{share}\{remote}, stdout is {stdout}, stderr is {stderr}".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(u"Downloading file '%s' to '%s'", 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(
		ur"""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(ur"Failed to download file \\{ip}\{share}\{remote} to {local}, stdout is {stdout}, stderr is {stderr}".format(
			local=local_pathname, remote=pathname_cmd, ip=settings.ip, share=share, stdout=safe_string_repr(stdout), stderr=safe_string_repr(stderr)
		))

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(
			u"Invalid output for command {command} (expected 'true' or 'false'):\n{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('\\\\', '\\')
