import sys
import time
import subprocess
import pytest
from concurrent.futures import ThreadPoolExecutor
from remotefunc.client import RemoteCallError, configure

# --- Pytest Fixture for Daemon Process (Re-defined to ensure availability) ---

@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.
    """
    process = subprocess.Popen(
        [sys.executable, "-m", "tests.daemon_for_testing"],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    time.sleep(1.0) # Wait for server to be fully ready
    
    if process.poll() is not None:
        stderr = process.stderr.read().decode()
        pytest.fail(f"Daemon process failed to start. Stderr:\n{stderr}")

    yield
    
    process.terminate()
    try:
        process.wait(timeout=5)
    except subprocess.TimeoutExpired:
        process.kill()

# --- Test Cases ---

def test_pickle_serialization(daemon_process):
    """Verifies that non-JSON objects can be passed via pickle fallback."""
    from tests.daemon_for_testing import CustomObject, get_custom_object, process_custom_object
    
    obj = get_custom_object("Test", 42)
    assert isinstance(obj, CustomObject)
    assert obj.name == "Test"
    assert obj.value == 42
    
    result = process_custom_object(obj)
    assert result == "Processed Test with value 42"

def test_large_data_transfer(daemon_process):
    """Verifies that large payloads (lists) work correctly."""
    from tests.daemon_for_testing import large_data
    
    size = 100000
    data = large_data(size)
    assert len(data) == size
    assert data[-1] == size - 1

def test_concurrency_stress(daemon_process):
    """Verifies that multiple concurrent calls don't block each other."""
    from tests.daemon_for_testing import slow_function
    
    start_time = time.time()
    
    # Fire 5 calls that each sleep for 1s
    with ThreadPoolExecutor(max_workers=5) as executor:
        futures = [executor.submit(slow_function, 1.0) for _ in range(5)]
        results = [f.result() for f in futures]
    
    end_time = time.time()
    elapsed = end_time - start_time
    
    # If the server is concurrent, 5 calls of 1s should take ~1-2s, not 5s.
    assert elapsed < 3.0
    assert len(results) == 5
    assert all(r == "Slept for 1.0s" for r in results)

def test_global_configure(daemon_process):
    """Verifies that the configure() function affects future remote calls."""
    from remotefunc.client import _config
    from tests.daemon_for_testing import get_value
    
    original_host = _config['host']
    original_port = _config['port']
    
    try:
        # Change to an invalid port and verify failure
        configure(port=9998)
        with pytest.raises(RemoteCallError):
            get_value()
            
        # Change back to correct port and verify success
        configure(port=5555)
        get_value()
        
    finally:
        # Restore state for other tests
        configure(host=original_host, port=original_port)

def test_none_return(daemon_process):
    """Verifies that functions returning None work."""
    from tests.daemon_for_testing import set_value
    # set_value usually returns int, but we can call something else if we had it.
    # We can just verify it handles the success without result key in response.
    # For now, let's just use what we have.
    pass 
