// On Windows, set up header guards BEFORE any other includes
#ifdef _WIN32
#ifndef _WINSOCKAPI_
#define _WINSOCKAPI_
#endif
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <winsock2.h>
#include <windows.h>
#include <processenv.h>
#pragma comment(lib, "ws2_32.lib")
#endif

#include <lemon/utils/process_manager.h>
#include <stdexcept>
#include <iostream>
#include <thread>
#include <chrono>
#include <string>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <lemon/utils/aixlog.hpp>

#ifdef _WIN32
#ifdef ERROR
#undef ERROR
#endif
#else
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#ifdef __linux__
#include <sys/prctl.h>  // PR_SET_PDEATHSIG — kill child when parent dies
#endif
#ifdef __APPLE__
#include <spawn.h>      // posix_spawn — fork-safe child creation on macOS
extern char** environ;
#endif
#ifdef HAVE_LIBCAP
#include <sys/capability.h>
#endif
#endif

namespace lemon {
namespace utils {

// Helper function to check if a line should be filtered
static bool should_filter_line(const std::string& line) {
    // Filter out health check requests (both /health and /v1/health)
    // Also filter FLM's interactive prompt spam
    return (line.find("GET /health") != std::string::npos ||
            line.find("GET /v1/health") != std::string::npos ||
            line.find("Enter 'exit' to stop the server") != std::string::npos);
}

static bool is_error_line(const std::string& line) {
    std::string lowered = line;
    std::transform(lowered.begin(), lowered.end(), lowered.begin(),
                   [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
    return lowered.find("error") != std::string::npos;
}

static void log_process_line(const std::string& line) {
    if (should_filter_line(line)) {
        return;
    }

    if (is_error_line(line)) {
        LOG(ERROR, "Process") << line << std::endl;
    } else {
        LOG(INFO, "Process") << line << std::endl;
    }
}

#ifdef HAVE_LIBCAP
// Helper function to preserve capabilities across exec()
// This allows child processes to inherit CAP_SYS_RESOURCE and other capabilities
// from the parent process when available
static void preserve_capabilities_for_exec() {
    // Get the current process capabilities
    cap_t caps = cap_get_proc();
    if (!caps) {
        // If we can't get capabilities, just proceed without them
        // This is not a fatal error - the process will run with default permissions
        return;
    }

    // Check if we have any effective capabilities worth preserving
    cap_flag_value_t has_sys_resource = CAP_CLEAR;
    cap_get_flag(caps, CAP_SYS_RESOURCE, CAP_EFFECTIVE, &has_sys_resource);

    // Only proceed if we actually have CAP_SYS_RESOURCE or other useful caps
    if (has_sys_resource == CAP_SET) {
        // Set the capability as inheritable so it survives exec()
        cap_value_t cap_list[] = {CAP_SYS_RESOURCE};

        // Mark CAP_SYS_RESOURCE as inheritable
        if (cap_set_flag(caps, CAP_INHERITABLE, 1, cap_list, CAP_SET) == 0) {
            // Apply the modified capability set
            if (cap_set_proc(caps) == 0) {
                // Enable ambient capabilities (requires Linux 4.3+)
                // This ensures the capability is both inherited and effective in the child
                // Ignore errors as ambient caps might not be supported on older kernels
                prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, CAP_SYS_RESOURCE, 0, 0);
            }
        }
    }

    cap_free(caps);
}
#endif

#ifdef _WIN32
// Helper function to escape arguments for Windows command line
// Windows command-line parsing rules:
// - Arguments are separated by spaces (or tabs)
// - Double quotes can be used to include spaces in arguments
// - To include a double quote in an argument, escape it with a backslash
// - Backslashes before a double quote are also escaped
static std::string escape_windows_arg(const std::string& arg) {
    std::string result = "\"";
    for (size_t i = 0; i < arg.size(); ++i) {
        if (arg[i] == '"') {
            // Escape the quote with a backslash
            result += "\\\"";
        } else if (arg[i] == '\\') {
            // Check if this backslash is followed by a quote
            // If so, we need to escape the backslash too
            if (i + 1 < arg.size() && arg[i + 1] == '"') {
                result += "\\\\";
            } else {
                result += '\\';
            }
        } else {
            result += arg[i];
        }
    }
    result += "\"";
    return result;
}

// Thread function to read from pipe and filter output
static DWORD WINAPI output_filter_thread(LPVOID param) {
    HANDLE pipe = static_cast<HANDLE>(param);
    char buffer[4096];
    DWORD bytes_read;
    std::string line_buffer;

    while (ReadFile(pipe, buffer, sizeof(buffer) - 1, &bytes_read, nullptr) && bytes_read > 0) {
        buffer[bytes_read] = '\0';
        line_buffer += buffer;

        // Process complete lines
        size_t pos;
        while ((pos = line_buffer.find('\n')) != std::string::npos) {
            std::string line = line_buffer.substr(0, pos);
            line_buffer = line_buffer.substr(pos + 1);

            log_process_line(line);
        }
    }

    // Print any remaining partial line
    if (!line_buffer.empty()) {
        log_process_line(line_buffer);
    }

    CloseHandle(pipe);
    return 0;
}

static std::string lowercase_ascii(std::string s) {
    std::transform(s.begin(), s.end(), s.begin(),
                   [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
    return s;
}

static std::vector<char> build_windows_environment_block(
    const std::vector<std::pair<std::string, std::string>>& env_vars) {
    std::vector<std::string> merged_entries;

    LPWCH environment = GetEnvironmentStringsW();
    if (environment) {
        for (const wchar_t* entry = environment; *entry != L'\0';
             entry += std::wcslen(entry) + 1) {
            int size_needed = WideCharToMultiByte(CP_UTF8, 0, entry, -1, nullptr, 0, nullptr, nullptr);
            if (size_needed > 0) {
                std::string narrow(size_needed - 1, '\0');
                WideCharToMultiByte(CP_UTF8, 0, entry, -1, &narrow[0], size_needed, nullptr, nullptr);
                merged_entries.emplace_back(std::move(narrow));
            }
        }
        FreeEnvironmentStringsW(environment);
    }

    for (const auto& env : env_vars) {
        const std::string key_lower = lowercase_ascii(env.first);
        const std::string new_entry = env.first + "=" + env.second;

        bool replaced = false;
        for (auto& existing : merged_entries) {
            size_t equals = existing.find('=');
            if (equals == std::string::npos) {
                continue;
            }

            std::string existing_key = lowercase_ascii(existing.substr(0, equals));
            if (existing_key == key_lower) {
                existing = new_entry;
                replaced = true;
                break;
            }
        }

        if (!replaced) {
            merged_entries.push_back(new_entry);
        }
    }

    std::vector<char> block;
    for (const auto& entry : merged_entries) {
        block.insert(block.end(), entry.begin(), entry.end());
        block.push_back('\0');
    }
    block.push_back('\0');
    return block;
}
#endif

ProcessHandle ProcessManager::start_process(
    const std::string& executable,
    const std::vector<std::string>& args,
    const std::string& working_dir,
    bool inherit_output,
    bool filter_health_logs,
    const std::vector<std::pair<std::string, std::string>>& env_vars) {

    ProcessHandle handle;
    handle.handle = nullptr;
    handle.pid = 0;

#ifdef _WIN32
    // Windows implementation
    std::string cmdline = escape_windows_arg(executable);
    for (const auto& arg : args) {
        cmdline += " " + escape_windows_arg(arg);
    }

    STARTUPINFOA si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));

    HANDLE stdout_read = nullptr;
    HANDLE stdout_write = nullptr;
    HANDLE stderr_read = nullptr;
    HANDLE stderr_write = nullptr;
    HANDLE nul_input = nullptr;

    bool use_filtered_output = (inherit_output && filter_health_logs);

    if (inherit_output && !filter_health_logs) {
        const HANDLE std_in = GetStdHandle(STD_INPUT_HANDLE);
        const HANDLE std_out = GetStdHandle(STD_OUTPUT_HANDLE);
        const HANDLE std_err = GetStdHandle(STD_ERROR_HANDLE);

        const bool invalid_stdio =
            (std_in == nullptr || std_in == INVALID_HANDLE_VALUE) ||
            (std_out == nullptr || std_out == INVALID_HANDLE_VALUE) ||
            (std_err == nullptr || std_err == INVALID_HANDLE_VALUE);

        if (invalid_stdio) {
            use_filtered_output = true;
            LOG(WARNING, "ProcessManager")
                << "Parent std handles are unavailable; enabling filtered output capture"
                << std::endl;
        }
    }

    // If inherit_output is true, either use pipes with filtering or direct inheritance
    if (inherit_output && use_filtered_output) {
        // Create pipes for stdout and stderr to filter output
        SECURITY_ATTRIBUTES sa;
        sa.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa.bInheritHandle = TRUE;
        sa.lpSecurityDescriptor = nullptr;

        if (!CreatePipe(&stdout_read, &stdout_write, &sa, 0)) {
            throw std::runtime_error("Failed to create stdout pipe");
        }
        if (!CreatePipe(&stderr_read, &stderr_write, &sa, 0)) {
            CloseHandle(stdout_read);
            CloseHandle(stdout_write);
            throw std::runtime_error("Failed to create stderr pipe");
        }

        // Make sure the read handles are not inherited
        SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0);
        SetHandleInformation(stderr_read, HANDLE_FLAG_INHERIT, 0);

        si.dwFlags = STARTF_USESTDHANDLES;
        si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
        if (si.hStdInput == nullptr || si.hStdInput == INVALID_HANDLE_VALUE) {
            nul_input = CreateFileA("NUL", GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
            if (nul_input != INVALID_HANDLE_VALUE) {
                SetHandleInformation(nul_input, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
                si.hStdInput = nul_input;
            } else {
                si.hStdInput = nullptr;
            }
        }
        si.hStdOutput = stdout_write;
        si.hStdError = stderr_write;

        LOG(DEBUG, "ProcessManager") << "Starting process with filtered output: " << cmdline << std::endl;
    } else if (inherit_output) {
        // Direct inheritance without filtering
        si.dwFlags |= STARTF_USESTDHANDLES;
        si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
        si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
        si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
        LOG(DEBUG, "ProcessManager") << "Starting process with inherited output: " << cmdline << std::endl;
    } else {
        // Redirect to NUL to suppress output when not in debug mode
        si.dwFlags |= STARTF_USESTDHANDLES;
        si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);

        HANDLE hNul = CreateFileA("NUL", GENERIC_WRITE, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
        if (hNul != INVALID_HANDLE_VALUE) {
            // Ensure the NUL handle is inheritable
            SetHandleInformation(hNul, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
            si.hStdOutput = hNul;
            si.hStdError = hNul;
        }
    }

    std::vector<char> environment_block;
    if (!env_vars.empty()) {
        environment_block = build_windows_environment_block(env_vars);
    }

    BOOL success = CreateProcessA(
        nullptr,
        const_cast<char*>(cmdline.c_str()),
        nullptr,
        nullptr,
        TRUE,  // Inherit handles
        (inherit_output && !use_filtered_output) ? 0 : CREATE_NO_WINDOW,
        environment_block.empty() ? nullptr : environment_block.data(),
        working_dir.empty() ? nullptr : working_dir.c_str(),
        &si,
        &pi
    );

    // If we opened a NUL handle, we can close it now (the child process has its own inherited handle)
    if (!inherit_output && si.hStdOutput != nullptr && si.hStdOutput != INVALID_HANDLE_VALUE) {
        CloseHandle(si.hStdOutput);
    }

    if (!success) {
        DWORD error = GetLastError();
        char error_msg[256];
        FormatMessageA(
            FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
            nullptr,
            error,
            0,
            error_msg,
            sizeof(error_msg),
            nullptr
        );

        if (stdout_write) CloseHandle(stdout_write);
        if (stderr_write) CloseHandle(stderr_write);
        if (stdout_read) CloseHandle(stdout_read);
        if (stderr_read) CloseHandle(stderr_read);

        std::string full_error = "Failed to start process '" + executable +
                                "': " + error_msg + " (Error code: " + std::to_string(error) + ")";
        LOG(ERROR, "ProcessManager") << full_error << std::endl;
        if (nul_input && nul_input != INVALID_HANDLE_VALUE) CloseHandle(nul_input);
        throw std::runtime_error(full_error);
    }

    if (nul_input && nul_input != INVALID_HANDLE_VALUE) {
        CloseHandle(nul_input);
    }

    // Close write ends of pipes in parent process
    if (stdout_write) CloseHandle(stdout_write);
    if (stderr_write) CloseHandle(stderr_write);

    // Start filter threads if needed
    if (inherit_output && use_filtered_output) {
        CreateThread(nullptr, 0, output_filter_thread, stdout_read, 0, nullptr);
        CreateThread(nullptr, 0, output_filter_thread, stderr_read, 0, nullptr);
    }

    if (inherit_output) {
        LOG(INFO, "ProcessManager") << "Process started successfully, PID: " << pi.dwProcessId << std::endl;
    }

    handle.handle = pi.hProcess;
    handle.pid = pi.dwProcessId;
    CloseHandle(pi.hThread);

#else
    // Unix implementation
    int stdout_pipe[2] = {-1, -1};
    int stderr_pipe[2] = {-1, -1};

    // Create pipes for filtering if requested
    if (inherit_output && filter_health_logs) {
        if (pipe(stdout_pipe) < 0 || pipe(stderr_pipe) < 0) {
            throw std::runtime_error("Failed to create pipes for output filtering");
        }
    }

    if (inherit_output) {
        std::string cmdline = executable;
        for (const auto& arg : args) {
            cmdline += " " + arg;
        }
        if (filter_health_logs) {
            LOG(DEBUG, "ProcessManager") << "Starting process with filtered output: " << cmdline << std::endl;
        } else {
            LOG(DEBUG, "ProcessManager") << "Starting process with inherited output: " << cmdline << std::endl;
        }
    }

#if defined(__APPLE__)
    // macOS: use posix_spawn instead of fork()+execvp().
    //
    // Apple has long documented that fork() is unsafe when the parent process
    // has initialized higher-level frameworks (CoreFoundation / Security /
    // Metal / XPC / libdispatch). The child inherits Mach-port rights and
    // XPC-bootstrap handles that cannot be cleanly reset, and execvp() does
    // not scrub that process-level state. The result is silent corruption
    // of services reached via XPC — most visibly MTLCompilerService, which
    // llama-server's ggml-metal probe depends on in builds from b8884+.
    // posix_spawn is Apple's supported path for clean child creation.
    posix_spawn_file_actions_t file_actions;
    posix_spawn_file_actions_init(&file_actions);

    if (inherit_output && filter_health_logs) {
        posix_spawn_file_actions_addclose(&file_actions, stdout_pipe[0]);
        posix_spawn_file_actions_addclose(&file_actions, stderr_pipe[0]);
        posix_spawn_file_actions_adddup2(&file_actions, stdout_pipe[1], STDOUT_FILENO);
        posix_spawn_file_actions_adddup2(&file_actions, stderr_pipe[1], STDERR_FILENO);
        posix_spawn_file_actions_addclose(&file_actions, stdout_pipe[1]);
        posix_spawn_file_actions_addclose(&file_actions, stderr_pipe[1]);
    } else if (!inherit_output) {
        posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO, "/dev/null", O_WRONLY, 0);
        posix_spawn_file_actions_adddup2(&file_actions, STDOUT_FILENO, STDERR_FILENO);
    }

    if (!working_dir.empty()) {
        // `addchdir_np` is deprecated on macOS 26+ in favor of the portable
        // `addchdir`, but `addchdir` is not declared by older SDKs we still
        // build against. Keep the Darwin-specific call and suppress the
        // deprecation warning until the minimum SDK is macOS 26.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        posix_spawn_file_actions_addchdir_np(&file_actions, working_dir.c_str());
#pragma clang diagnostic pop
    }

    posix_spawnattr_t attr;
    posix_spawnattr_init(&attr);
    // CLOEXEC_DEFAULT (Apple extension): every FD in the parent is closed in
    // the child unless explicitly re-added via file_actions. This plugs FD
    // leaks of listening sockets, log files, and kqueue handles.
    // SETSIGDEF + full sigset: reset any SIG_IGN the parent installed
    // (notably SIGPIPE) so the child starts with default dispositions.
    sigset_t default_signals;
    sigfillset(&default_signals);
    posix_spawnattr_setsigdefault(&attr, &default_signals);
    posix_spawnattr_setflags(&attr, POSIX_SPAWN_CLOEXEC_DEFAULT | POSIX_SPAWN_SETSIGDEF);

    // Build envp: parent environ minus any keys we override, plus env_vars.
    std::vector<std::string> env_strings;
    for (char** e = environ; e && *e; ++e) {
        bool override_existing = false;
        for (const auto& env_pair : env_vars) {
            std::string prefix = env_pair.first + "=";
            if (std::strncmp(*e, prefix.c_str(), prefix.size()) == 0) {
                override_existing = true;
                break;
            }
        }
        if (!override_existing) {
            env_strings.emplace_back(*e);
        }
    }
    for (const auto& env_pair : env_vars) {
        env_strings.emplace_back(env_pair.first + "=" + env_pair.second);
    }
    std::vector<char*> envp;
    envp.reserve(env_strings.size() + 1);
    for (auto& s : env_strings) {
        envp.push_back(&s[0]);
    }
    envp.push_back(nullptr);

    std::vector<char*> argv_ptrs;
    argv_ptrs.reserve(args.size() + 2);
    argv_ptrs.push_back(const_cast<char*>(executable.c_str()));
    for (const auto& arg : args) {
        argv_ptrs.push_back(const_cast<char*>(arg.c_str()));
    }
    argv_ptrs.push_back(nullptr);

    pid_t pid = 0;
    int spawn_rc = posix_spawnp(&pid, executable.c_str(), &file_actions, &attr,
                                argv_ptrs.data(), envp.data());

    posix_spawn_file_actions_destroy(&file_actions);
    posix_spawnattr_destroy(&attr);

    if (spawn_rc != 0) {
        if (inherit_output && filter_health_logs) {
            close(stdout_pipe[0]);
            close(stdout_pipe[1]);
            close(stderr_pipe[0]);
            close(stderr_pipe[1]);
        }
        throw std::runtime_error(
            std::string("posix_spawn failed: ") + strerror(spawn_rc));
    }
#else
    pid_t pid = fork();

    if (pid < 0) {
        throw std::runtime_error("Failed to fork process");
    }

    if (pid == 0) {
        // Child process
#ifdef __linux__
        // Ensure this child is killed when the parent process dies.
        // This is the Linux equivalent of the Windows Job Object and
        // prevents orphaned backend processes (llama-server, etc.)
        // when lemond is killed or crashes.
        prctl(PR_SET_PDEATHSIG, SIGTERM);
#endif

        if (!working_dir.empty()) {
            chdir(working_dir.c_str());
        }

        // Set environment variables
        for (const auto& env_pair : env_vars) {
            setenv(env_pair.first.c_str(), env_pair.second.c_str(), 1);
        }

        // Redirect stdout/stderr to pipes if filtering
        if (inherit_output && filter_health_logs) {
            close(stdout_pipe[0]);  // Close read end
            close(stderr_pipe[0]);
            dup2(stdout_pipe[1], STDOUT_FILENO);
            dup2(stderr_pipe[1], STDERR_FILENO);
            close(stdout_pipe[1]);
            close(stderr_pipe[1]);
        } else if (!inherit_output) {
            // Redirect to /dev/null to suppress output when not in debug mode
            int dev_null = open("/dev/null", O_WRONLY);
            if (dev_null >= 0) {
                dup2(dev_null, STDOUT_FILENO);
                dup2(dev_null, STDERR_FILENO);
                close(dev_null);
            }
        }

#ifdef HAVE_LIBCAP
        // Preserve capabilities (e.g., CAP_SYS_RESOURCE) across exec
        // This allows the child process to inherit capabilities from the parent
        preserve_capabilities_for_exec();
#endif

        // Prepare argv
        std::vector<char*> argv_ptrs;
        argv_ptrs.push_back(const_cast<char*>(executable.c_str()));
        for (const auto& arg : args) {
            argv_ptrs.push_back(const_cast<char*>(arg.c_str()));
        }
        argv_ptrs.push_back(nullptr);

        execvp(executable.c_str(), argv_ptrs.data());

        // If execvp returns, it failed
        std::cerr << "Failed to execute: " << executable << std::endl;
        _exit(1);
    }
#endif

    // Parent process
    handle.pid = pid;

    if (inherit_output) {
        LOG(INFO, "ProcessManager") << "Process started successfully, PID: " << pid << std::endl;
    }

    // Start filter threads if needed
    if (inherit_output && filter_health_logs) {
        close(stdout_pipe[1]);  // Close write ends in parent
        close(stderr_pipe[1]);

        // Start threads to read and filter output
        std::thread([fd = stdout_pipe[0]]() {
            char buffer[4096];
            std::string line_buffer;
            ssize_t bytes_read;

            while ((bytes_read = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
                buffer[bytes_read] = '\0';
                line_buffer += buffer;

                size_t pos;
                while ((pos = line_buffer.find('\n')) != std::string::npos) {
                    std::string line = line_buffer.substr(0, pos);
                    line_buffer = line_buffer.substr(pos + 1);

                    log_process_line(line);
                }
            }

            if (!line_buffer.empty()) {
                log_process_line(line_buffer);
            }

            close(fd);
        }).detach();

        std::thread([fd = stderr_pipe[0]]() {
            char buffer[4096];
            std::string line_buffer;
            ssize_t bytes_read;

            while ((bytes_read = read(fd, buffer, sizeof(buffer) - 1)) > 0) {
                buffer[bytes_read] = '\0';
                line_buffer += buffer;

                size_t pos;
                while ((pos = line_buffer.find('\n')) != std::string::npos) {
                    std::string line = line_buffer.substr(0, pos);
                    line_buffer = line_buffer.substr(pos + 1);

                    log_process_line(line);
                }
            }

            if (!line_buffer.empty()) {
                log_process_line(line_buffer);
            }

            close(fd);
        }).detach();
    }

#endif

    return handle;
}

void ProcessManager::stop_process(ProcessHandle handle) {
#ifdef _WIN32
    if (handle.handle) {
        TerminateProcess(handle.handle, 0);
        WaitForSingleObject(handle.handle, 5000);  // Wait up to 5 seconds
        CloseHandle(handle.handle);
    }
#else
    if (handle.pid > 0) {
        kill(handle.pid, SIGTERM);

        // Wait for process to exit
        int status;
        bool exited_gracefully = false;
        for (int i = 0; i < 50; i++) {  // Try for 5 seconds
            if (waitpid(handle.pid, &status, WNOHANG) > 0) {
                exited_gracefully = true;
                break;
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }

        if (!exited_gracefully) {
            // If still alive, force kill
            LOG(WARNING, "ProcessManager") << "Process did not respond to SIGTERM, using SIGKILL" << std::endl;
            kill(handle.pid, SIGKILL);
            waitpid(handle.pid, &status, 0);
        }

        // CRITICAL FIX: GPU drivers need time to release Vulkan/ROCm contexts
        // The process may exit but GPU resources persist briefly in the kernel driver.
        // Without this delay, rapid restarts cause the new process to hang waiting
        // for GPU resources that are still being cleaned up.
        // This matches the Python test behavior which has a 5s delay after server start.
        LOG(INFO, "ProcessManager") << "Process terminated, waiting for GPU driver cleanup..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(2));
    }
#endif
}

bool ProcessManager::is_running(ProcessHandle handle) {
#ifdef _WIN32
    if (!handle.handle) {
        return false;
    }

    DWORD exit_code;
    if (!GetExitCodeProcess(handle.handle, &exit_code)) {
        return false;
    }

    return exit_code == STILL_ACTIVE;
#else
    if (handle.pid <= 0) {
        return false;
    }

    int status;
    pid_t result = waitpid(handle.pid, &status, WNOHANG);
    return result == 0;  // 0 means still running
#endif
}

int ProcessManager::get_exit_code(ProcessHandle handle) {
#ifdef _WIN32
    if (!handle.handle) {
        return -1;
    }

    DWORD exit_code;
    if (!GetExitCodeProcess(handle.handle, &exit_code)) {
        return -1;
    }

    if (exit_code == STILL_ACTIVE) {
        return -1;  // Still running
    }

    return static_cast<int>(exit_code);
#else
    if (handle.pid <= 0) {
        return -1;
    }

    int status;
    pid_t result = waitpid(handle.pid, &status, WNOHANG);

    if (result == 0) {
        return -1;  // Still running
    }

    if (WIFEXITED(status)) {
        return WEXITSTATUS(status);
    }

    return -1;
#endif
}

int ProcessManager::wait_for_exit(ProcessHandle handle, int timeout_seconds) {
#ifdef _WIN32
    if (!handle.handle) {
        return -1;
    }

    DWORD wait_time = timeout_seconds < 0 ? INFINITE : timeout_seconds * 1000;
    DWORD result = WaitForSingleObject(handle.handle, wait_time);

    if (result == WAIT_TIMEOUT) {
        return -1;
    }

    DWORD exit_code;
    GetExitCodeProcess(handle.handle, &exit_code);
    return exit_code;
#else
    if (handle.pid <= 0) {
        return -1;
    }

    int status;
    if (timeout_seconds < 0) {
        waitpid(handle.pid, &status, 0);
        return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
    }

    for (int i = 0; i < timeout_seconds * 10; i++) {
        pid_t result = waitpid(handle.pid, &status, WNOHANG);
        if (result > 0) {
            return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    return -1;  // Timeout
#endif
}

std::string ProcessManager::read_output(ProcessHandle handle, int max_bytes) {
    // Note: This is a simplified version. Full implementation would need pipes
    // for stdout/stderr capture during process creation
    return "";
}

int ProcessManager::run_process_with_output(
    const std::string& executable,
    const std::vector<std::string>& args,
    OutputLineCallback on_line,
    const std::string& working_dir,
    int timeout_seconds) {

#ifdef _WIN32
    // Windows implementation
    std::string cmdline = escape_windows_arg(executable);
    for (const auto& arg : args) {
        cmdline += " " + escape_windows_arg(arg);
    }

    // Create pipes for stdout
    HANDLE stdout_read = nullptr;
    HANDLE stdout_write = nullptr;

    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = nullptr;

    if (!CreatePipe(&stdout_read, &stdout_write, &sa, 0)) {
        throw std::runtime_error("Failed to create stdout pipe");
    }

    // Make sure the read handle is not inherited
    SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0);

    STARTUPINFOA si;
    PROCESS_INFORMATION pi;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESTDHANDLES;
    si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    si.hStdOutput = stdout_write;
    si.hStdError = stdout_write;  // Merge stderr into stdout
    ZeroMemory(&pi, sizeof(pi));

    BOOL success = CreateProcessA(
        nullptr,
        const_cast<char*>(cmdline.c_str()),
        nullptr,
        nullptr,
        TRUE,  // Inherit handles
        CREATE_NO_WINDOW,
        nullptr,
        working_dir.empty() ? nullptr : working_dir.c_str(),
        &si,
        &pi
    );

    // Close write end in parent
    CloseHandle(stdout_write);

    if (!success) {
        CloseHandle(stdout_read);
        DWORD error = GetLastError();
        throw std::runtime_error("Failed to start process: error " + std::to_string(error));
    }

    // Read output line by line
    std::string line_buffer;
    char buffer[4096];
    DWORD bytes_read;
    bool killed_by_callback = false;

    auto start_time = std::chrono::steady_clock::now();

    while (true) {
        // Check timeout
        if (timeout_seconds > 0) {
            auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
                std::chrono::steady_clock::now() - start_time).count();
            if (elapsed > timeout_seconds) {
                TerminateProcess(pi.hProcess, 1);
                killed_by_callback = true;
                break;
            }
        }

        // Check if there's data to read (non-blocking peek)
        DWORD available = 0;
        if (!PeekNamedPipe(stdout_read, nullptr, 0, nullptr, &available, nullptr)) {
            break;  // Pipe closed or error
        }

        if (available > 0) {
            DWORD to_read = (std::min)(available, (DWORD)(sizeof(buffer) - 1));
            if (ReadFile(stdout_read, buffer, to_read, &bytes_read, nullptr) && bytes_read > 0) {
                buffer[bytes_read] = '\0';
                line_buffer += buffer;

                // Process complete lines (split on \n or \r for in-place progress updates)
                size_t pos;
                while (true) {
                    // Find the first line terminator (\n or \r)
                    size_t newline_pos = line_buffer.find('\n');
                    size_t cr_pos = line_buffer.find('\r');

                    if (newline_pos == std::string::npos && cr_pos == std::string::npos) {
                        break;  // No complete line yet
                    }

                    // Use whichever comes first
                    if (newline_pos == std::string::npos) {
                        pos = cr_pos;
                    } else if (cr_pos == std::string::npos) {
                        pos = newline_pos;
                    } else {
                        pos = (std::min)(newline_pos, cr_pos);
                    }

                    std::string line = line_buffer.substr(0, pos);

                    // Skip \r\n as a single delimiter
                    size_t skip = 1;
                    if (pos + 1 < line_buffer.size() &&
                        line_buffer[pos] == '\r' && line_buffer[pos + 1] == '\n') {
                        skip = 2;
                    }
                    line_buffer = line_buffer.substr(pos + skip);

                    // Skip empty lines
                    if (line.empty()) {
                        continue;
                    }

                    // Call the callback
                    if (on_line && !on_line(line)) {
                        TerminateProcess(pi.hProcess, 1);
                        killed_by_callback = true;
                        break;
                    }
                }

                if (killed_by_callback) break;
            }
        } else {
            // No data available, check if process is still running
            DWORD exit_code;
            if (GetExitCodeProcess(pi.hProcess, &exit_code) && exit_code != STILL_ACTIVE) {
                // Process exited, drain any remaining output
                while (ReadFile(stdout_read, buffer, sizeof(buffer) - 1, &bytes_read, nullptr) && bytes_read > 0) {
                    buffer[bytes_read] = '\0';
                    line_buffer += buffer;
                }
                break;
            }

            // Sleep briefly to avoid busy-waiting
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }

    // Process any remaining partial line
    if (!line_buffer.empty() && on_line && !killed_by_callback) {
        // Remove trailing \r if present
        if (!line_buffer.empty() && line_buffer.back() == '\r') {
            line_buffer.pop_back();
        }
        if (!line_buffer.empty()) {
            on_line(line_buffer);
        }
    }

    CloseHandle(stdout_read);

    // Get exit code
    DWORD exit_code = 0;
    WaitForSingleObject(pi.hProcess, 5000);
    GetExitCodeProcess(pi.hProcess, &exit_code);

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return killed_by_callback ? -1 : static_cast<int>(exit_code);

#else
    // Unix implementation
    int stdout_pipe[2];

    if (pipe(stdout_pipe) < 0) {
        throw std::runtime_error("Failed to create pipe");
    }

    pid_t pid = fork();

    if (pid < 0) {
        close(stdout_pipe[0]);
        close(stdout_pipe[1]);
        throw std::runtime_error("Failed to fork process");
    }

    if (pid == 0) {
        // Child process
#ifdef __linux__
        prctl(PR_SET_PDEATHSIG, SIGTERM);
#endif
        close(stdout_pipe[0]);  // Close read end

        // Redirect stdout and stderr to pipe
        dup2(stdout_pipe[1], STDOUT_FILENO);
        dup2(stdout_pipe[1], STDERR_FILENO);
        close(stdout_pipe[1]);

        if (!working_dir.empty()) {
            chdir(working_dir.c_str());
        }

#ifdef HAVE_LIBCAP
        // Preserve capabilities (e.g., CAP_SYS_RESOURCE) across exec
        preserve_capabilities_for_exec();
#endif

        // Prepare argv
        std::vector<char*> argv_ptrs;
        argv_ptrs.push_back(const_cast<char*>(executable.c_str()));
        for (const auto& arg : args) {
            argv_ptrs.push_back(const_cast<char*>(arg.c_str()));
        }
        argv_ptrs.push_back(nullptr);

        execvp(executable.c_str(), argv_ptrs.data());

        // If execvp returns, it failed
        _exit(127);
    }

    // Parent process
    close(stdout_pipe[1]);  // Close write end

    // Read output line by line
    std::string line_buffer;
    char buffer[4096];
    ssize_t bytes_read;
    bool killed_by_callback = false;

    auto start_time = std::chrono::steady_clock::now();

    // Set non-blocking mode
    int flags = fcntl(stdout_pipe[0], F_GETFL, 0);
    fcntl(stdout_pipe[0], F_SETFL, flags | O_NONBLOCK);

    while (true) {
        // Check timeout
        if (timeout_seconds > 0) {
            auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
                std::chrono::steady_clock::now() - start_time).count();
            if (elapsed > timeout_seconds) {
                kill(pid, SIGKILL);
                killed_by_callback = true;
                break;
            }
        }

        bytes_read = read(stdout_pipe[0], buffer, sizeof(buffer) - 1);

        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            line_buffer += buffer;

            // Process complete lines (split on \n or \r for in-place progress updates)
            size_t pos;
            while (true) {
                // Find the first line terminator (\n or \r)
                size_t newline_pos = line_buffer.find('\n');
                size_t cr_pos = line_buffer.find('\r');

                if (newline_pos == std::string::npos && cr_pos == std::string::npos) {
                    break;  // No complete line yet
                }

                // Use whichever comes first
                if (newline_pos == std::string::npos) {
                    pos = cr_pos;
                } else if (cr_pos == std::string::npos) {
                    pos = newline_pos;
                } else {
                    pos = std::min(newline_pos, cr_pos);
                }

                std::string line = line_buffer.substr(0, pos);

                // Skip \r\n as a single delimiter
                size_t skip = 1;
                if (pos + 1 < line_buffer.size() &&
                    line_buffer[pos] == '\r' && line_buffer[pos + 1] == '\n') {
                    skip = 2;
                }
                line_buffer = line_buffer.substr(pos + skip);

                // Skip empty lines
                if (line.empty()) {
                    continue;
                }

                // Call the callback
                if (on_line && !on_line(line)) {
                    kill(pid, SIGKILL);
                    killed_by_callback = true;
                    break;
                }
            }

            if (killed_by_callback) break;
        } else if (bytes_read == 0) {
            // EOF - pipe closed
            break;
        } else {
            // EAGAIN/EWOULDBLOCK - no data available
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // Check if process is still running
                int status;
                pid_t result = waitpid(pid, &status, WNOHANG);
                if (result > 0) {
                    // Process exited, drain remaining output
                    fcntl(stdout_pipe[0], F_SETFL, flags);  // Back to blocking
                    while ((bytes_read = read(stdout_pipe[0], buffer, sizeof(buffer) - 1)) > 0) {
                        buffer[bytes_read] = '\0';
                        line_buffer += buffer;
                    }
                    break;
                }

                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            } else {
                // Real error
                break;
            }
        }
    }

    // Process any remaining partial line
    if (!line_buffer.empty() && on_line && !killed_by_callback) {
        on_line(line_buffer);
    }

    close(stdout_pipe[0]);

    // Wait for process and get exit code
    int status;
    waitpid(pid, &status, 0);

    if (killed_by_callback) {
        return -1;
    }

    return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
#endif
}

void ProcessManager::kill_process(ProcessHandle handle) {
#ifdef _WIN32
    if (handle.handle) {
        TerminateProcess(handle.handle, 1);
        CloseHandle(handle.handle);
    }
#else
    if (handle.pid > 0) {
        kill(handle.pid, SIGKILL);
        int status;
        waitpid(handle.pid, &status, 0);
    }
#endif
}

int ProcessManager::find_free_port(int start_port) {
#ifdef _WIN32
    WSADATA wsa_data;
    WSAStartup(MAKEWORD(2, 2), &wsa_data);
#endif

    for (int port = start_port; port < start_port + 1000; port++) {
        // Test if port is free by attempting to bind to localhost
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0) {
            continue;
        }

        sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");

        int result = bind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
#ifdef _WIN32
        closesocket(sock);
#else
        close(sock);
#endif

        if (result == 0) {
#ifdef _WIN32
            WSACleanup();
#endif
            return port;
        }
    }

#ifdef _WIN32
    WSACleanup();
#endif

    return -1;  // No free port found
}

int ProcessManager::run_command(const std::string& command, std::string& output, int timeout_seconds) {
    output.clear();

#ifdef _WIN32
    // Windows: use CreateProcess + pipe to avoid console window flash.
    // This is a drop-in replacement for _popen() that works in SUBSYSTEM:WINDOWS apps.
    HANDLE stdout_read = nullptr;
    HANDLE stdout_write = nullptr;

    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = nullptr;

    if (!CreatePipe(&stdout_read, &stdout_write, &sa, 0)) {
        return -1;
    }
    SetHandleInformation(stdout_read, HANDLE_FLAG_INHERIT, 0);

    STARTUPINFOA si = {};
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESTDHANDLES;
    si.hStdInput = INVALID_HANDLE_VALUE;
    si.hStdOutput = stdout_write;
    si.hStdError = stdout_write;

    PROCESS_INFORMATION pi = {};
    // Wrap in cmd /c so shell features (redirection, pipes) work
    std::string cmdline = "cmd /c " + command;
    BOOL success = CreateProcessA(
        nullptr, const_cast<char*>(cmdline.c_str()),
        nullptr, nullptr, TRUE, CREATE_NO_WINDOW,
        nullptr, nullptr, &si, &pi);

    CloseHandle(stdout_write);

    if (!success) {
        CloseHandle(stdout_read);
        return -1;
    }

    // Read all output
    char buf[4096];
    DWORD bytes_read;
    while (ReadFile(stdout_read, buf, sizeof(buf), &bytes_read, nullptr) && bytes_read > 0) {
        output.append(buf, bytes_read);
    }
    CloseHandle(stdout_read);

    WaitForSingleObject(pi.hProcess, timeout_seconds > 0 ? timeout_seconds * 1000 : INFINITE);
    DWORD exit_code = 1;
    GetExitCodeProcess(pi.hProcess, &exit_code);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return static_cast<int>(exit_code);
#else
    // Unix: popen is fine — no console window issue
    FILE* pipe = popen(command.c_str(), "r");
    if (!pipe) return -1;

    char buf[4096];
    while (fgets(buf, sizeof(buf), pipe)) {
        output += buf;
    }
    return pclose(pipe);
#endif
}

} // namespace utils
} // namespace lemon
