//go:build windows

package agent

import (
	"context"
	"log/slog"
	"os/exec"
	"strconv"
	"strings"
	"syscall"
	"time"
)

const windowsProcessTreeKillTimeout = 5 * time.Second
const createNoWindow = 0x08000000

// configureRuntimeCommand installs a context-cancel hook that terminates the
// whole runtime tree. Windows has no Setpgid equivalent, so a plain
// exec.CommandContext cancel would only kill the wrapper process (for example
// opencode.cmd) and could leave node/bun children running.
func configureRuntimeCommand(cmd *exec.Cmd) {
	if cmd == nil {
		return
	}
	configureWindowsNoWindow(cmd)
	cmd.Cancel = func() error {
		terminateRuntimeCommand(cmd)
		return nil
	}
}

func configureWindowsNoWindow(cmd *exec.Cmd) {
	if cmd == nil {
		return
	}
	if cmd.SysProcAttr == nil {
		cmd.SysProcAttr = &syscall.SysProcAttr{}
	}
	cmd.SysProcAttr.HideWindow = true
	cmd.SysProcAttr.CreationFlags |= createNoWindow
}

// terminateRuntimeCommand kills the runtime process tree on Windows.
func terminateRuntimeCommand(cmd *exec.Cmd) {
	if cmd == nil || cmd.Process == nil {
		return
	}
	pid := cmd.Process.Pid
	if err := terminateWindowsProcessTree(pid); err != nil {
		slog.Warn("failed to terminate runtime process tree with taskkill; falling back to direct process kill",
			"pid", pid,
			"error", err,
		)
		if killErr := cmd.Process.Kill(); killErr != nil {
			slog.Warn("failed to directly kill runtime process after taskkill fallback",
				"pid", pid,
				"error", killErr,
			)
		}
	}
}

func terminateWindowsProcessTree(pid int) error {
	if pid <= 0 {
		return nil
	}
	ctx, cancel := context.WithTimeout(context.Background(), windowsProcessTreeKillTimeout)
	defer cancel()
	cmd := exec.CommandContext(ctx, "taskkill", "/T", "/F", "/PID", strconv.Itoa(pid))
	configureWindowsNoWindow(cmd)
	out, err := cmd.CombinedOutput()
	if err == nil {
		return nil
	}
	lower := strings.ToLower(string(out))
	if strings.Contains(lower, "not found") || strings.Contains(lower, "not running") {
		return nil
	}
	return err
}

// runtimeCommandExited reports whether the runtime process has already exited.
func runtimeCommandExited(cmd *exec.Cmd) bool {
	if cmd == nil {
		return true
	}
	if cmd.ProcessState != nil {
		return cmd.ProcessState.Exited()
	}
	return false
}
