import logging

from parallels.core.run_command import WindowsRunner
from parallels.ppa.utils import poa_utils
from parallels.core.utils import windows_utils
from parallels.core.utils.common import strip_multiline_spaces, safe_string_repr, default
from parallels.ppa import messages
from parallels.core.utils.steps_profiler import get_default_steps_profiler
from parallels.core.utils.windows_utils import check_windows_national_symbols_command
from parallels.ppa.run_command.hcl_common import HclRunnerException, hcl_execute_multiple_attempts

logger = logging.getLogger(__name__)
profiler = get_default_steps_profiler()


class WindowsHclRunner(WindowsRunner):
	def __init__(self, ppa_runner, poa_host_id, host_description):
		self.poa_host_id = poa_host_id
		self.host_description = host_description
		self._hcl = poa_utils.Hcl(ppa_runner)

	def run(self, cmd, args=None, stdin_content=None, output_codepage=None, error_policy='strict', env=None):
		"""The same as run_unchecked(), but checks exit code and returns only stdout"""
		log_cmd_str = self._get_cmd_str_listargs(cmd, args)

		def run_func():
			exit_code, stdout, stderr = self._run_unchecked(
				cmd, args, stdin_content, output_codepage, error_policy, env
			)
			if exit_code != 0:
				raise HclRunnerException(
					strip_multiline_spaces(messages.COMMAND_COMMAND_EXECUTED_AT_HOST_RETURNED).format(
						stderr=stderr,
						stdout=stdout,
						command=log_cmd_str,
						host=self._get_host_description(),
						host_id=self.poa_host_id,
						hcl_script=self._hcl.get_windows_hcl_script(log_cmd_str, env)
					),
					stdout,
					stderr
				)
			return stdout

		return hcl_execute_multiple_attempts(run_func, self._get_host_description(), log_cmd_str)

	def run_unchecked(self, cmd, args=None, stdin_content=None, output_codepage=None, error_policy='strict', env=None):
		def run_func():
			return self._run_unchecked(cmd, args, stdin_content, output_codepage, error_policy, env)

		log_cmd_str = " ".join([windows_utils.quote_arg(arg) for arg in [cmd] + ([] if args is None else args)])
		return hcl_execute_multiple_attempts(run_func, self._get_host_description(), log_cmd_str)

	def _run_unchecked(self, cmd, args=None, stdin_content=None, output_codepage=None, error_policy='strict', env=None):
		if stdin_content is not None:
			raise NotImplementedError(messages.RUNNER_DOES_NOT_SUPPORT_STDIN_CONTENT)

		cmd_str = " ".join([windows_utils.quote_arg(arg) for arg in [cmd] + ([] if args is None else args)])
		return self._run_command_and_decode_output(cmd_str, output_codepage, error_policy, env)

	def sh(self, cmd_str, args=None, stdin_content=None, output_codepage=None, error_policy='strict', env=None):
		"""The same as sh_unchecked(), but checks exit code and returns only stdout"""
		log_cmd_str = self._get_cmd_str_formatargs(cmd_str, args)

		def run_func():
			exit_code, stdout, stderr = self.sh_unchecked(
				cmd_str, args, stdin_content, output_codepage, error_policy, env
			)
			if exit_code != 0:
				logger.debug(
					messages.LOG_COMMAND_FAILED,
					log_cmd_str, exit_code, safe_string_repr(stdout), safe_string_repr(stderr)
				)
				raise HclRunnerException(
					strip_multiline_spaces(messages.COMMAND_COMMAND_EXECUTED_AT_HOST_RETURNED_1).format(
						stderr=stderr,
						stdout=stdout,
						command=log_cmd_str,
						host=self._get_host_description(),
						host_id=self.poa_host_id,
						hcl_script=self._hcl.get_windows_hcl_script(log_cmd_str, env)
					),
					stdout,
					stderr
				)
			return stdout

		return hcl_execute_multiple_attempts(run_func, self._get_host_description(), log_cmd_str)

	def sh_unchecked(
		self, cmd_str, args=None, stdin_content=None, output_codepage=None,
		error_policy='strict', env=None
	):
		def run_func():
			return self._sh_unchecked(cmd_str, args, stdin_content, output_codepage, error_policy, env)

		return hcl_execute_multiple_attempts(
			run_func, self._get_host_description(), self._get_cmd_str_formatargs(cmd_str, args)
		)

	def _sh_unchecked(
		self, cmd_str, args=None, stdin_content=None, output_codepage=None,
		error_policy='strict', env=None
	):
		if stdin_content is not None:
			raise NotImplementedError(messages.RUNNER_DOES_NOT_SUPPORT_STDIN_CONTENT)

		if args is not None:
			cmd_str = windows_utils.format_command(cmd_str, **args)

		return self._run_command_and_decode_output(cmd_str, output_codepage, error_policy, env)

	def _run_command_and_decode_output(self, cmd_str, output_codepage=None, error_policy='strict', env=None):
		"""Run a command, return its output decoded from 'codepage' to UTF."""
		check_windows_national_symbols_command(cmd_str)
		codepage = output_codepage if output_codepage else self.codepage
		try:
			with profiler.measure_command_call(cmd_str, self._get_host_description()):
				return self._hcl.windows_exec_unchecked(
					self.poa_host_id, cmd_str,
					codepage=codepage, error_policy=error_policy, env=env
				)
		except poa_utils.HclPleskdCtlNonZeroExitCodeError as e:
			logger.debug(messages.LOG_EXCEPTION, exc_info=True)
			raise HclRunnerException(
				strip_multiline_spaces(messages.FAILED_EXECUTE_COMMAND_COMMAND_AT_HOST).format(
					stderr=e.stderr,
					stdout=e.stdout,
					command=cmd_str,
					host=self._get_host_description(),
					host_id=self.poa_host_id,
					hcl_script=self._hcl.get_windows_hcl_script(cmd_str, env)
				),
				'',
				str(e),
				cause=e,
				host_description=self._get_host_description()
			)

	def upload_file(self, local_filename, remote_filename):
		with open(local_filename, 'rb') as fp:
			return self._hcl.upload_file(self.poa_host_id, remote_filename, fp.read())

	def upload_file_content(self, filename, content):
		return self._hcl.upload_file(self.poa_host_id, filename, content)

	def get_file(self, remote_filename, local_filename):
		with open(local_filename, 'wb') as fp:
			fp.write(self._hcl.get_file_contents(self.poa_host_id, remote_filename))

	def get_file_contents(self, remote_filename):
		return self._hcl.get_file_contents(self.poa_host_id, remote_filename)

	def mkdir(self, dirname):
		self.sh('cmd /c if not exist {dirname} ( md {dirname} )', dict(dirname=dirname))

	def _get_host_description(self):
		return default(self.host_description, messages.DEFAULT_PPA_HOST_DESCRIPTION % self.poa_host_id)

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

	@staticmethod
	def _get_cmd_str_formatargs(cmd_str, args):
		if args is not None:
			return windows_utils.format_command(cmd_str, **args)
		else:
			return cmd_str
