import logging

from parallels.ppa import messages
from parallels.core.run_command import BaseRunner
from parallels.ppa.utils import poa_utils
from parallels.core.utils import unix_utils
from parallels.core.utils.common import safe_string_repr, default
from parallels.core.utils.steps_profiler import get_default_steps_profiler
from parallels.ppa.run_command.hcl_common import HclRunnerException, hcl_execute_multiple_attempts


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


class UnixHclRunner(BaseRunner):
	def __init__(self, ppa_runner, poa_host_id, host_description=None):
		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_unchecker(), but checks exit code and returns only stdout"""
		def run_func():
			log_cmd_str = self._get_cmd_str_listargs(cmd, args)
			exit_code, stdout, stderr = self._run_unchecked(
				cmd, 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(
					messages.COMMAND_RETURNED_NON_ZERO_EXIT_CODE.format(
						stdout=stdout,
						stderr=stderr,
						log_command=log_cmd_str,
						host=self._get_host_description(),
						host_id=self.poa_host_id,
						hcl_script=self._hcl.get_unix_hcl_script(cmd, args, stdin_content, env)
					),
					stdout,
					stderr
				)
			return stdout

		return hcl_execute_multiple_attempts(
			run_func, self._get_host_description(), self._get_cmd_str_listargs(cmd, args)
		)

	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)

		return hcl_execute_multiple_attempts(
			run_func, self._get_host_description(), self._get_cmd_str_listargs(cmd, args)
		)

	def _run_unchecked(self, cmd, args=None, stdin_content=None, output_codepage=None, error_policy='strict', env=None):
		log_cmd_str = self._get_cmd_str_listargs(cmd, args)

		try:
			with profiler.measure_command_call(log_cmd_str, self._get_host_description()):
				return self._hcl.unix_exec_unchecked(self.poa_host_id, cmd, args, stdin_content, env)
		except poa_utils.HclPleskdCtlNonZeroExitCodeError as e:
			logger.debug(messages.LOG_EXCEPTION, exc_info=True)
			raise HclRunnerException(
				messages.FAILED_TO_EXECUTE_COMMAND_DESCRIPTION.format(
					stderr=e.stderr,
					stdout=e.stdout,
					command=log_cmd_str,
					host=self._get_host_description(),
					host_id=self.poa_host_id,
					hcl_script=self._hcl.get_unix_hcl_script(cmd, args, stdin_content, env)
				),
				'',
				str(e),
				cause=e,
				host_description=self._get_host_description()
			)

	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(
					messages.COMMAND_RETURNED_NON_ZERO_EXIT_CODE.format(
						stdout=stdout,
						stderr=stderr,
						log_command=log_cmd_str,
						host=self._get_host_description(),
						host_id=self.poa_host_id,
						hcl_script=self._hcl.get_unix_hcl_script(log_cmd_str, [], stdin_content, 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):
		run_cmd, run_args = self._get_command_list(cmd_str, args)

		return self._run_unchecked(run_cmd, run_args, stdin_content, output_codepage, error_policy, env)

	@staticmethod
	def _get_command_list(cmd_str, args):
		if args is not None:
			cmd_str = unix_utils.format_command(cmd_str, **args)

		return '/bin/sh', ['-c', cmd_str]

	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 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 move(self, src_path, dst_path):
		return self.run('/bin/mv', [src_path, dst_path])

	def get_files_list(self, path):
		return unix_utils.get_files_list(self, path)

	def remove_file(self, filename):
		return self.run('/bin/rm', [filename])

	def mkdir(self, dirname):
		return self.run('/bin/mkdir', ['-p', dirname])

	def file_exists(self, filename):
		return unix_utils.file_exists(self, filename)

	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 unix_utils.format_command_list(cmd, args)

	def _get_cmd_str_formatargs(self, cmd_str, args):
		run_cmd, run_args = self._get_command_list(cmd_str, args)
		return unix_utils.format_command_list(run_cmd, run_args)
