//go:build !windows

package infra

import (
	"fmt"
	"syscall"
	"time"
)

// ProcessKill sends SIGTERM to the process group of handle, then waits up to
// graceSec seconds for the process to exit. If it is still alive after the
// grace period, SIGKILL is sent. Returns an error only if the signal could not
// be delivered (e.g. the process group does not exist).
func ProcessKill(handle *ProcessHandle, graceSec int) error {
	// Send SIGTERM to the process group (negative pid targets the group).
	if err := syscall.Kill(-handle.Pid, syscall.SIGTERM); err != nil {
		// ESRCH means the process is already gone — not an error from our view.
		if err != syscall.ESRCH {
			return fmt.Errorf("process_kill: sigterm: %w", err)
		}
		return nil
	}

	// Poll until the process exits or the grace period expires.
	deadline := time.Now().Add(time.Duration(graceSec) * time.Second)
	for time.Now().Before(deadline) {
		// Check if process has exited by sending signal 0 (no-op).
		err := syscall.Kill(-handle.Pid, 0)
		if err == syscall.ESRCH {
			// Process group is gone.
			return nil
		}
		time.Sleep(100 * time.Millisecond)
	}

	// Still alive after grace period — escalate to SIGKILL.
	if err := syscall.Kill(-handle.Pid, syscall.SIGKILL); err != nil {
		if err != syscall.ESRCH {
			return fmt.Errorf("process_kill: sigkill: %w", err)
		}
	}
	return nil
}
