import logging
import subprocess
import sys
import os
from pipes import quote

from parallels.core import messages
from parallels.core.utils.common.logging import hide_text

logger = logging.getLogger(__name__)


class MigrationError(Exception):
	pass


class ParallelExecutionError(MigrationError):
	def __init__(self):
		super(MigrationError, self).__init__(messages.EXCEPTION_PARALLEL_EXECUTION)


class CommandExecutionError(MigrationError):
	"""Exception occurred when executing some command on some host"""

	def __init__(self, message, stdout='', stderr='', host_description='', cause=''):
		super(MigrationError, self).__init__(message)
		self._stdout = stdout
		self._stderr = stderr
		self._host_description = host_description
		self._cause = cause

	@property
	def stdout(self):
		"""stdout of the command"""
		return self._stdout

	@property
	def stderr(self):
		"""stderr of the command"""
		return self._stderr

	@property
	def host_description(self):
		"""Description of the host where the command was executed"""
		return self._host_description

	@property
	def cause(self):
		"""Root cause of the error - exception caused command execution to fail"""
		return self._cause


class APIError(MigrationError):
	"""Exception occurred when executing some request through external API"""

	@property
	def how_to_reproduce(self):
		"""User-friendly description on how to reproduce API request"""
		raise NotImplementedError()


class MigrationConfigurationFileError(Exception):
	pass


class MigrationNoContextError(Exception):
	pass


class MigrationNoRepeatError(Exception):
	"""Exception explicitly stating that there is no sense to repeat failed operation for the object

	For example, if subscription creation failed because such subscription already exists,
	there is no sense to try to create subscription any more times.
	"""
	pass


def run_and_check_local_command(command, stdin_content=None, output_codepage='utf-8'):
	exit_code, stdout_str, stderr_str = local_command_exec(command, stdin_content, output_codepage)
	if exit_code != 0:
		raise MigrationError(
			messages.LOCAL_COMMAND_FAILED % (
				u" ".join([quote(c) for c in command]),
				exit_code,
				stdout_str,
				stderr_str
			)
		)
	return stdout_str, stderr_str


def local_command_exec(
	command, stdin_content=None, output_codepage='utf-8', error_policy='strict', env=None, log_output=True
):
	if type(command) is list or type(command) is tuple:
		command_str = ' '.join([quote(c) for c in command])
		command_encoded = [c.encode('utf-8') for c in command]
	else:
		command_str = command
		command_encoded = command.encode('utf-8')
	logger.debug(messages.DEBUG_CALL_LOCAL_COMMAND % command_str)

	command_env = os.environ.copy()
	command_env['LANG'] = 'en_US.utf-8'
	if env is not None:
		command_env.update(env)

	try:
		p = subprocess.Popen(
			command_encoded,
			stdin=subprocess.PIPE,
			stdout=subprocess.PIPE,
			stderr=subprocess.PIPE,
			env=command_env
		)
		stdout_str, stderr_str = [s.decode(output_codepage, error_policy) for s in p.communicate(stdin_content)]
		logger.debug(messages.DEBUG_COMMAND_STDOUT, hide_text(stdout_str, not log_output))
		logger.debug(messages.DEBUG_COMMAND_STDERR, hide_text(stderr_str, not log_output))
		logger.debug(messages.DEBUG_COMMAND_EXIT_CODE, p.returncode)
		return p.returncode, stdout_str, stderr_str
	except Exception as e:
		logger.debug(messages.LOG_EXCEPTION, exc_info=True)
		raise Exception(
			messages.FAILED_TO_EXECUTE_LOCAL_COMMAND % (command_str, unicode(e))
		), None, sys.exc_info()[2]


def version_tuple(version):
	"""Convert string product version to a tuple of ints"""
	return tuple(map(int, version.split('.')))