//go:build !windows
// +build !windows

package process

import (
	"fmt"
	"syscall"
)

// applyResourceLimits sets Unix/Linux resource limits
func applyResourceLimits(cfg IsolationConfig) error {
	// Apply file descriptor limits (RLIMIT_NOFILE for FD exhaustion protection)
	if cfg.MaxOpenFiles > 0 {
		if err := setResourceLimit(syscall.RLIMIT_NOFILE, uint64(cfg.MaxOpenFiles)); err != nil {
			return fmt.Errorf("failed to set max open files limit: %w", err)
		}
	}

	// Apply process limits if available (Linux only)
	if cfg.MaxProcesses > 0 {
		if err := setProcessLimit(cfg.MaxProcesses); err != nil {
			// Log but don't fail - this is defense in depth
			return fmt.Errorf("failed to set max processes limit: %w", err)
		}
	}

	return nil
}

// setResourceLimit sets a soft and hard rlimit for the current process
func setResourceLimit(resource int, limit uint64) error {
	rl := &syscall.Rlimit{
		Cur: limit,
		Max: limit,
	}
	return syscall.Setrlimit(resource, rl)
}

// setProcessLimit sets RLIMIT_NPROC on Linux, no-op on other Unix
func setProcessLimit(maxProcs int) error {
	// Try to set RLIMIT_NPROC - only available on Linux
	// On Darwin/macOS, this returns ENOTSUP
	const RLIMIT_NPROC = 7 // Linux value
	rl := &syscall.Rlimit{
		Cur: uint64(maxProcs),
		Max: uint64(maxProcs),
	}
	err := syscall.Setrlimit(RLIMIT_NPROC, rl)
	if err != nil {
		// ENOTSUP means not supported (macOS)
		if err == syscall.ENOTSUP || err == syscall.EINVAL {
			return nil // Silently ignore on platforms that don't support it
		}
		return err
	}
	return nil
}

// disableSwap attempts to lock memory to prevent swapping (mlockall)
// This is best-effort and requires CAP_IPC_LOCK capability
func disableSwap() error {
	// MCL_CURRENT: lock all current pages
	// MCL_FUTURE: lock all future pages
	const MCL_CURRENT = 0x1
	const MCL_FUTURE = 0x2

	// Note: mlockall requires CAP_IPC_LOCK capability
	// If this fails, we log but continue (defense in depth)
	return syscall.Mlockall(MCL_CURRENT | MCL_FUTURE)
}

// GetCurrentLimits returns the current rlimit values for diagnostics
func GetCurrentLimits() (map[string]uint64, error) {
	limits := make(map[string]uint64)

	// Get NOFILE limit (available on all platforms)
	var nofile syscall.Rlimit
	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &nofile); err != nil {
		return nil, fmt.Errorf("failed to get NOFILE limit: %w", err)
	}
	limits["NOFILE_soft"] = nofile.Cur
	limits["NOFILE_hard"] = nofile.Max

	// Get platform-specific limits
	getPlatformLimits(limits)

	return limits, nil
}

// getPlatformLimits adds platform-specific limits to the map
func getPlatformLimits(limits map[string]uint64) {
	// Get virtual memory limit (AS)
	var as syscall.Rlimit
	if err := syscall.Getrlimit(syscall.RLIMIT_AS, &as); err == nil {
		limits["AS_soft"] = as.Cur
		limits["AS_hard"] = as.Max
	}

	// Get data segment limit
	var data syscall.Rlimit
	if err := syscall.Getrlimit(syscall.RLIMIT_DATA, &data); err == nil {
		limits["DATA_soft"] = data.Cur
		limits["DATA_hard"] = data.Max
	}

	// Try to get RLIMIT_NPROC (Linux only)
	const RLIMIT_NPROC = 7
	var nproc syscall.Rlimit
	if err := syscall.Getrlimit(RLIMIT_NPROC, &nproc); err == nil {
		limits["NPROC_soft"] = nproc.Cur
		limits["NPROC_hard"] = nproc.Max
	}

	// Try to get RLIMIT_RSS (Linux only)
	const RLIMIT_RSS = 5
	var rss syscall.Rlimit
	if err := syscall.Getrlimit(RLIMIT_RSS, &rss); err == nil {
		limits["RSS_soft"] = rss.Cur
		limits["RSS_hard"] = rss.Max
	}
}
