import logging
import subprocess
#import os
from termcolor import colored

class CommandOutput:
	"""Class for shell command output.

	Args:
		returnCode: Linux Exit Code (0-255)
		stdOut: Linux STDOUT
		stdErr: Linux STDERR
	"""

	def __init__(self,
		returnCode: int,
		stdOut: str,
		stdErr: str
	):
		"""Init self."""
		self.returnCode = returnCode
		self.stdOut = stdOut
		self.stdErr = stdErr

def shell_exec(*, args:list[str]|str, timeout: int = 150, start_new_session: bool = False, communicate: bool = True) -> tuple[CommandOutput, list[str]|None]:
	"""Execute a shell command with subprocess.

	Args:
		args: Command and arguments to execute.
		timeout: Maximum time in seconds to wait for the command to complete.
		start_new_session: Whether to run the process in the background, if not, it will die together with Python
		communicate: Attempt comms with the process - it will block if true

	Returns:
		CommandOutput: An object containing the returncode, stdout, and stderr.

	Raises:
		ShellExecError: If an error occurs during command execution.

	"""
	logging.debug(f"Executing command: {' '.join(args) if isinstance(args, list) else args}")
	# Make sure env is available so xdg-open has access to XDG_CURRENT_DESKTOP
	# TODO it doesn't work -> we don't use xdg-open anymore, just forget about this?
#	logging.info(colored(text=os.environ, color='dark_grey'))
#	process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, env=os.environ.copy())
	process = subprocess.Popen(
		args,
		stdout=subprocess.PIPE,
		stderr=subprocess.PIPE,
		text=True,
    start_new_session=start_new_session
	)
	errorList = []
	if not communicate:
		return (CommandOutput(0, '', ''), [])
	try:
		stdout, stderr = process.communicate(timeout=timeout)
#		print(vars(process))
	except subprocess.TimeoutExpired as timedOutProcess:
		logging.warning(f"timedoutproctimeout is {timedOutProcess.stdout}; stderr is {timedOutProcess.stderr}")
		process.kill()
		stdout, stderr = timedOutProcess.stdout, timedOutProcess.stderr
#		stdout, stderr = process.communicate()
#		logging.warning(f"timedoutproctimeout postkill is {stdout}; stderr is {stderr}")
		logging.error(f"Command '{' '.join(args) if isinstance(args, list) else args}' timed out after {timeout} seconds, user probably ignored notification.")
		errorList.append('Timeout')
	except subprocess.SubprocessError:
		process.kill()
		stdout, stderr = process.communicate() # Is this useful? Needs testing
		logging.critical(f"An error occurred while executing command '{' '.join(args) if isinstance(args, list) else args}'.")
		errorList.append('Subprocess')

	# We have byte and str and None
	if stdout is None:
		stdout = ''
	elif isinstance(stdout, bytes):
		stdout = stdout.strip().decode('utf-8')
	elif isinstance(stdout, str):
		stdout = stdout.strip()
	else:
		logging.critical('Uknown command error!')
		return (CommandOutput(123, '', 'UKNOWN ERROR'), list('Unknown'))

	if stderr is None:
		stderr = ''
	elif isinstance(stderr, bytes):
		stderr = stderr.strip().decode('utf-8')
	elif isinstance(stderr, str):
		stderr = stderr.strip()
	else:
		logging.critical('Uknown command error!')
		return (CommandOutput(123, '', 'UKNOWN ERROR'), list('Unknown'))

	if stderr != '':
		logging.warning(colored(stderr, 'red'))

	return (CommandOutput(process.returncode, stdout, stderr), errorList)

def convert_to_unicode(*, input_str:str) -> str:
	"""Convert string to its unicode representation."""
	return ''.join(f'\\u{ord(c):04x}' for c in input_str)


if __name__ == "__main__":
	# Example usage of the function
	test_string = "hello"
	print("Testing the convert_to_unicode function:")
	print("Input:", test_string)
	print("Output:", convert_to_unicode(input_str=test_string))
