package com.easyagents.agent.runtime.tool.operate;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

/**
 * Linux Shell 独立会话与进程组清理支持。
 *
 * <p>Linux 使用受信任的 util-linux {@code setsid} 创建独立会话，并通过系统 {@code kill}
 * 向负 PGID 发送信号。JDK 17 没有可移植的 killpg API，非 Linux 平台保留 ProcessHandle
 * 后代跟踪降级；脚本显式创建第二个会话仍属于无 OS 沙箱时无法消除的边界。
 */
final class ShellProcessGroupSupport {

    private static final Logger logger = LoggerFactory.getLogger(ShellProcessGroupSupport.class);
    private static final List<Path> SETSID_CANDIDATES = List.of(
            Path.of("/usr/bin/setsid"), Path.of("/bin/setsid"));
    private static final List<Path> KILL_CANDIDATES = List.of(
            Path.of("/bin/kill"), Path.of("/usr/bin/kill"));

    private final Path setsid;
    private final Path kill;

    private ShellProcessGroupSupport(Path setsid, Path kill) {
        this.setsid = setsid;
        this.kill = kill;
    }

    /**
     * 检测当前平台的进程组能力。
     *
     * @return Linux 进程组支持或可移植降级实例
     */
    static ShellProcessGroupSupport detect() {
        String osName = System.getProperty("os.name", "");
        if (!isLinux(osName)) {
            return new ShellProcessGroupSupport(null, null);
        }
        return detect(osName, firstExecutable(SETSID_CANDIDATES), firstExecutable(KILL_CANDIDATES));
    }

    /**
     * 使用显式路径检测平台能力，供启动校验测试使用。
     *
     * @param osName 操作系统名称
     * @param setsidPath setsid 路径，可空
     * @param killPath kill 路径，可空
     * @return 检测结果
     */
    static ShellProcessGroupSupport detect(String osName, Path setsidPath, Path killPath) {
        if (!isLinux(osName)) {
            return new ShellProcessGroupSupport(null, null);
        }
        if (!isTrustedExecutable(setsidPath) || !isTrustedExecutable(killPath)) {
            throw new WorkspaceToolException("WORKSPACE_CONFIG_INVALID",
                    "Linux controlled shell requires executable setsid and kill utilities.", false);
        }
        return new ShellProcessGroupSupport(setsidPath.toAbsolutePath().normalize(),
                killPath.toAbsolutePath().normalize());
    }

    /**
     * 返回是否启用 Linux 独立进程组。
     *
     * @return 启用时为 true
     */
    boolean enabled() {
        return setsid != null && kill != null;
    }

    /**
     * 为 Linux 命令增加受信任 setsid 前缀。
     *
     * @param command 已校验命令参数
     * @return 实际 ProcessBuilder 参数
     */
    List<String> wrap(List<String> command) {
        if (!enabled()) {
            return command;
        }
        List<String> wrapped = new ArrayList<>(command.size() + 1);
        wrapped.add(setsid.toString());
        wrapped.addAll(command);
        return wrapped;
    }

    /**
     * 对独立进程组发送 TERM，随后发送 KILL 清理残留成员。
     *
     * @param processGroupId setsid 进程 PID，同时也是 PGID
     */
    void terminate(long processGroupId) {
        if (!enabled() || processGroupId <= 1) {
            return;
        }
        if (!signal("-TERM", processGroupId)) {
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException error) {
            Thread.currentThread().interrupt();
        }
        signal("-KILL", processGroupId);
    }

    private boolean signal(String signal, long processGroupId) {
        try {
            Process process = new ProcessBuilder(
                    kill.toString(), signal, "--", "-" + processGroupId)
                    .redirectInput(ProcessBuilder.Redirect.from(Path.of("/dev/null").toFile()))
                    .redirectOutput(ProcessBuilder.Redirect.DISCARD)
                    .redirectError(ProcessBuilder.Redirect.DISCARD)
                    .start();
            if (!process.waitFor(500, TimeUnit.MILLISECONDS)) {
                process.destroyForcibly();
                return false;
            }
            return process.exitValue() == 0;
        } catch (IOException error) {
            logger.error("Failed to signal controlled shell process group", error);
            return false;
        } catch (InterruptedException error) {
            Thread.currentThread().interrupt();
            logger.warn("Interrupted while signaling controlled shell process group", error);
            return false;
        }
    }

    private static boolean isLinux(String osName) {
        return osName != null && osName.toLowerCase(Locale.ROOT).contains("linux");
    }

    private static Path firstExecutable(List<Path> candidates) {
        return candidates.stream().filter(ShellProcessGroupSupport::isTrustedExecutable)
                .findFirst().orElse(null);
    }

    private static boolean isTrustedExecutable(Path path) {
        return path != null && path.isAbsolute() && Files.isRegularFile(path) && Files.isExecutable(path);
    }
}
