import subprocess
import sys
import time
import pytest
from remotefunc.client import RemoteCallError, create_remote_function


# --- Pytest Fixture for Daemon Process ---

@pytest.fixture(scope="module", autouse=True)
def daemon_process ():
	"""
	Starts the daemon_for_testing.py script in a separate process
	and ensures it's terminated after the tests are done.
	"""
	# Start the daemon process
	process = subprocess.Popen(
		[sys.executable, "-m", "tests.daemon_for_testing"],
		stdout=subprocess.PIPE,
		stderr=subprocess.PIPE,
	)
	
	# Give the server a moment to start up
	time.sleep(0.5)
	
	# Check if the process started correctly
	if process.poll() is not None:
		# Process terminated unexpectedly, print stderr for debugging
		stderr = process.stderr.read().decode()
		pytest.fail(f"Daemon process failed to start. Stderr:\n{stderr}")
	
	yield
	
	# Teardown: terminate the daemon process
	process.terminate()
	try:
		process.wait(timeout=5)
	except subprocess.TimeoutExpired:
		process.kill()


# --- Test Cases ---

def test_remote_state_change ():
	"""
	Test that remote calls can modify the server's state by importing
	from the daemon module.
	"""
	# This import triggers the remote proxy creation
	from tests.daemon_for_testing import set_value, get_value, test_data
	
	# Initial state check
	set_value(100)
	assert get_value() == 100
	
	# Modify state
	new_val = set_value(200)
	assert new_val == 200
	
	# Verify state change
	current_val = get_value()
	assert current_val == 200
	
	# Verify it does not change in this process
	assert test_data["value"] == 0
	assert test_data["function_called"] is False


def test_remote_call_simple ():
	"""Test a simple remote call with arguments and return value."""
	from tests.daemon_for_testing import add, test_data
	
	result = add(5, 3)
	assert result == 8

	assert test_data["function_called"] is False


def test_remote_call_with_kwargs ():
	"""Test remote calls using keyword arguments."""
	from tests.daemon_for_testing import complex_operation, test_data
	
	payload = {"numbers": [1, 2, 3]}
	result = complex_operation(data=payload, factor=3)
	assert result == {"result": [3, 6, 9]}
	
	assert test_data["function_called"] is False


def test_remote_exception_handling ():
	"""Test that exceptions from the remote function are propagated to the client."""
	from tests.daemon_for_testing import raises_exception, test_data
	
	with pytest.raises(RemoteCallError) as excinfo:
		raises_exception()
	
	# Check that the error message contains the original exception type and message
	assert "ValueError" in str(excinfo.value)
	assert "This is a test exception" in str(excinfo.value)
	
	assert test_data["function_called"] is False


def test_remote_call_not_found ():
	"""Test calling a function that is not registered on the server."""
	# This test uses create_remote_function directly as it's a client-side feature
	non_existent_func = create_remote_function("non.existent.function")
	
	with pytest.raises(RemoteCallError) as excinfo:
		non_existent_func()
	
	assert "Function 'non.existent.function' not found" in str(excinfo.value)


def test_connection_error_on_wrong_port ():
	"""Test that a connection error is raised if the client targets the wrong port."""
	remote_add = create_remote_function("tests.daemon_for_testing.add", port=9999)
	
	with pytest.raises(RemoteCallError) as excinfo:
		remote_add(1, 2)
	
	assert "Failed to connect to server" in str(excinfo.value)
