package iptables

import (
	"fmt"
	"os/exec"
	"strings"

	"firewall_containers/network-go/logger"
)

// IPTablesAPI defines the interface for iptables operations, enabling mock implementations for testing
type IPTablesAPI interface {
	Binary() string
	Chain() string
	EnsureIPForward() error
	EnsureEstablishedRelated(chain string) error
	DeleteLine(chain string, lineNum string) error
	InsertPreroutingRule(sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error
	InsertPreroutingRuleOnInterface(iface, proto, sourcePort, targetIP, targetPort, comment string) error
	InsertPostroutingMasquerade(sourceCIDR, proto, sourcePort, comment string) error
	InsertPostroutingMasqueradeForTarget(targetCIDR, proto, targetPort, comment string) error
	InsertForwardAccept(chain, sourceIP, targetIP, proto, sourcePort, targetPort, comment string) error
	DeleteForwardAccept(chain, comment string) error
	InsertPreroutingRuleInContainer(pid int, sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error
	InsertPostroutingMasqueradeInContainer(pid int, sourceCIDR, proto, sourcePort, comment string) error
}

// Manager manages iptables rules via the iptables/iptables-legacy CLI
type Manager struct {
	binary    string
	chain     string // "DOCKER-USER" or "FORWARD"
	debug     bool
}

// Ensure Manager implements IPTablesAPI
var _ IPTablesAPI = (*Manager)(nil)

// NewManager creates a new iptables manager, auto-detecting the binary
func NewManager(debug bool) *Manager {
	m := &Manager{debug: debug}
	m.detectBinary()
	return m
}

// detectBinary checks which iptables binary has the DOCKER-USER chain (Docker-managed)
func (m *Manager) detectBinary() {
	logger.Info("IPTABLES: detecting iptables binary and Docker chain")

	// Check iptables-legacy first
	cmd := exec.Command("/usr/sbin/iptables-legacy", "-L")
	output, err := cmd.CombinedOutput()
	if err == nil && strings.Contains(string(output), "DOCKER-USER") {
		m.binary = "/usr/sbin/iptables-legacy"
		m.chain = "DOCKER-USER"
		logger.Info("IPTABLES: detected iptables-legacy with DOCKER-USER chain")
		return
	}

	// Check default iptables (may be nft-based)
	cmd2 := exec.Command("/usr/sbin/iptables", "-L")
	output2, err2 := cmd2.CombinedOutput()
	if err2 == nil && strings.Contains(string(output2), "DOCKER-USER") {
		m.binary = "/usr/sbin/iptables"
		m.chain = "DOCKER-USER"
		logger.Info("IPTABLES: detected iptables with DOCKER-USER chain")
		return
	}

	// No DOCKER-USER chain found — default to DOCKER-USER for ACCEPT rules
	m.binary = "/usr/sbin/iptables"
	m.chain = "DOCKER-USER"
	logger.Info("IPTABLES: no DOCKER-USER chain detected, defaulting to DOCKER-USER with default iptables")
}

// Binary returns the detected iptables binary path
func (m *Manager) Binary() string {
	return m.binary
}

// Chain returns the firewall chain to use for FORWARD rules (DOCKER-USER or FORWARD)
func (m *Manager) Chain() string {
	return m.chain
}

// run executes an iptables command on the host
func (m *Manager) run(args ...string) error {
	cmdStr := m.binary + " " + strings.Join(args, " ")
	logger.Info("IPTABLES: executing: %s", cmdStr)
	if m.debug {
		logger.Debug("IPTABLES DEBUG: %s %s", m.binary, strings.Join(args, " "))
	}
	cmd := exec.Command(m.binary, args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		logger.Error("IPTABLES: command failed: %s\noutput: %s", cmdStr, strings.TrimSpace(string(output)))
		return fmt.Errorf("iptables %s failed: %w\noutput: %s", strings.Join(args, " "), err, string(output))
	}
	logger.Debug("IPTABLES: command succeeded: %s", cmdStr)
	if len(output) > 0 {
		logger.Debug("IPTABLES: output: %s", strings.TrimSpace(string(output)))
	}
	return nil
}

// runInContainer executes an iptables command inside a container's network namespace via nsenter
func (m *Manager) runInContainer(pid int, table string, args ...string) error {
	// Use the same binary path that was auto-detected on the host
	// Alpine installs iptables at /usr/sbin/ not /sbin/
	iptPath := m.binary

	fullArgs := []string{"-t", fmt.Sprintf("%d", pid), "-n", "--", iptPath}
	if table != "" {
		fullArgs = append(fullArgs, "-t", table)
	}
	fullArgs = append(fullArgs, args...)

	cmdStr := "nsenter " + strings.Join(fullArgs, " ")
	logger.Info("IPTABLES: executing nsenter for PID %d: %s", pid, cmdStr)
	if m.debug {
		logger.Debug("IPTABLES DEBUG: nsenter %s", strings.Join(fullArgs, " "))
	}

	cmd := exec.Command("nsenter", fullArgs...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		logger.Error("IPTABLES: nsenter command failed for PID %d\noutput: %s", pid, strings.TrimSpace(string(output)))
		return fmt.Errorf("nsenter iptables failed: %w\noutput: %s", err, string(output))
	}
	logger.Info("IPTABLES: nsenter command succeeded for PID %d", pid)
	if len(output) > 0 {
		logger.Debug("IPTABLES: nsenter output: %s", strings.TrimSpace(string(output)))
	}
	return nil
}

// EnsureIPForward enables IP forwarding on the host.
// Logs a warning if it fails (e.g. read-only filesystem in a container),
// since this should be configured at the host level.
func (m *Manager) EnsureIPForward() error {
	logger.Info("IPTABLES: enabling IP forwarding (echo 1 > /proc/sys/net/ipv4/ip_forward)")
	cmd := exec.Command("sh", "-c", "echo 1 > /proc/sys/net/ipv4/ip_forward")
	output, err := cmd.CombinedOutput()
	if err != nil {
		logger.Warn("IPTABLES: failed to enable ip_forward (expected if read-only fs): %v", err)
		return fmt.Errorf("failed to enable ip_forward: %w\noutput: %s", err, string(output))
	}
	logger.Info("IPTABLES: IP forwarding enabled")
	return nil
}

// EnsureEstablishedRelated inserts an ESTABLISHED,RELATED accept rule at the top of a chain
func (m *Manager) EnsureEstablishedRelated(chain string) error {
	logger.Debug("IPTABLES: checking for ESTABLISHED,RELATED rule in %s", chain)
	checkArgs := []string{"-w", "-n", "-L", chain}
	cmd := exec.Command(m.binary, checkArgs...)
	output, err := cmd.Output()
	if err != nil {
		logger.Debug("IPTABLES: could not list chain %s: %v", chain, err)
		return nil
	}

	if !strings.Contains(string(output), "ESTABLISHED") || !strings.Contains(string(output), "RELATED") {
		logger.Info("IPTABLES: inserting ESTABLISHED,RELATED ACCEPT rule in %s", chain)
		args := []string{"-w", "-I", chain, "-m", "state", "--state", "established,related", "-j", "ACCEPT"}
		return m.run(args...)
	}
	logger.Debug("IPTABLES: ESTABLISHED,RELATED rule already exists in %s", chain)
	return nil
}

// DeleteLine deletes a specific line number from a chain
func (m *Manager) DeleteLine(chain string, lineNum string) error {
	logger.Info("IPTABLES: deleting line %s from chain %s", lineNum, chain)
	args := []string{"-w", "-D", chain, lineNum}
	return m.run(args...)
}

// DeleteLineInContainer deletes a specific line number from a chain inside a container namespace
func (m *Manager) DeleteLineInContainer(pid int, table, chain, lineNum string) error {
	logger.Info("IPTABLES: deleting line %s from chain %s in container PID %d", lineNum, chain, pid)
	args := []string{"-D", chain, lineNum}
	return m.runInContainer(pid, table, args...)
}

// getLineNumbers returns line numbers matching certain criteria in a chain/table
func (m *Manager) getLineNumbers(chain, table string, grepPatterns ...string) []string {
	args := []string{"-w", "--line-number", "-n", "-L", chain}
	if table != "" {
		args = []string{"-w", "-t", table, "--line-number", "-n", "-L", chain}
	}

	cmd := exec.Command(m.binary, args...)
	output, err := cmd.Output()
	if err != nil {
		logger.Debug("IPTABLES: getLineNumbers failed for %s: %v", chain, err)
		return nil
	}

	lines := strings.Split(string(output), "\n")
	var matchingLines []string
	for _, line := range lines {
		matchesAll := true
		for _, pattern := range grepPatterns {
			if !strings.Contains(line, pattern) {
				matchesAll = false
				break
			}
		}
		if matchesAll {
			fields := strings.Fields(line)
			if len(fields) > 0 {
				matchingLines = append(matchingLines, fields[0])
			}
		}
	}
	logger.Debug("IPTABLES: getLineNumbers chain=%s patterns=%v found=%v", chain, grepPatterns, matchingLines)
	return matchingLines
}

// deleteMatchingLines deletes all lines in a chain matching the given patterns
func (m *Manager) deleteMatchingLines(chain, table string, grepPatterns ...string) error {
	lines := m.getLineNumbers(chain, table, grepPatterns...)
	if len(lines) > 0 {
		logger.Info("IPTABLES: deleting %d matching lines from %s: %v", len(lines), chain, lines)
	}
	for i := len(lines) - 1; i >= 0; i-- {
		if err := m.DeleteLine(chain, lines[i]); err != nil {
			return err
		}
	}
	return nil
}

// deleteMatchingLinesInContainer deletes matching lines inside a container namespace
func (m *Manager) deleteMatchingLinesInContainer(pid int, table, chain string, grepPatterns ...string) error {
	iptPath := m.binary

	nsenterArgs := []string{"-t", fmt.Sprintf("%d", pid), "-n", "--", iptPath, "-w", "--line-number", "-n", "-t", table, "-L", chain}
	cmd := exec.Command("nsenter", nsenterArgs...)
	output, err := cmd.Output()
	if err != nil {
		logger.Debug("IPTABLES: deleteMatchingLinesInContainer list failed for PID %d chain %s: %v", pid, chain, err)
		return nil
	}

	lines := strings.Split(string(output), "\n")
	var matchingLines []string
	for _, line := range lines {
		matchesAll := true
		for _, pattern := range grepPatterns {
			if !strings.Contains(line, pattern) {
				matchesAll = false
				break
			}
		}
		if matchesAll {
			fields := strings.Fields(line)
			if len(fields) > 0 {
				matchingLines = append(matchingLines, fields[0])
			}
		}
	}

	if len(matchingLines) > 0 {
		logger.Info("IPTABLES: deleting %d matching lines from container PID %d chain %s: %v",
			len(matchingLines), pid, chain, matchingLines)
	}

	for i := len(matchingLines) - 1; i >= 0; i-- {
		if err := m.DeleteLineInContainer(pid, table, chain, matchingLines[i]); err != nil {
			return err
		}
	}
	return nil
}

// InsertPreroutingRule inserts a DNAT PREROUTING rule on the host
func (m *Manager) InsertPreroutingRule(sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error {
	logger.Info("IPTABLES: checking PREROUTING DNAT rule: src=%s proto=%s sport=%s -> dst=%s dport=%s comment=%q",
		sourceIP, proto, sourcePort, targetIP, targetPort, comment)

	// Validate source and destination networks are defined
	if sourceIP == "" || targetIP == "" {
		logger.Warn("IPTABLES: PREROUTING DNAT rule skipped — source=%q or destination=%q network not defined (comment=%q)",
			sourceIP, targetIP, comment)
		return nil
	}

	// Idempotent: check if rule already exists
	existing := m.getLineNumbers("PREROUTING", "nat", comment, "DNAT", targetIP, targetPort)
	if len(existing) > 0 {
		logger.Debug("IPTABLES: PREROUTING DNAT rule already exists (lines=%v), skipping", existing)
		return nil
	}

	logger.Info("IPTABLES: inserting PREROUTING DNAT rule: src=%s proto=%s sport=%s -> dst=%s dport=%s comment=%q",
		sourceIP, proto, sourcePort, targetIP, targetPort, comment)
	args := []string{
		"-w", "-t", "nat", "-I", "PREROUTING",
		"-d", sourceIP,
		"-p", proto,
		"--dport", sourcePort,
		"-m", "comment", "--comment", comment,
		"-j", "DNAT", "--to", targetIP + ":" + targetPort,
	}
	return m.run(args...)
}

// InsertPreroutingRuleOnInterface inserts a DNAT PREROUTING rule on a specific interface
func (m *Manager) InsertPreroutingRuleOnInterface(iface, proto, sourcePort, targetIP, targetPort, comment string) error {
	logger.Info("IPTABLES: checking PREROUTING DNAT rule on interface %s: proto=%s dport=%s -> %s:%s comment=%q",
		iface, proto, sourcePort, targetIP, targetPort, comment)

	// Validate destination network is defined
	if targetIP == "" {
		logger.Warn("IPTABLES: PREROUTING DNAT rule on %s skipped — destination network not defined (comment=%q)", iface, comment)
		return nil
	}

	// Check if rule already exists (idempotent: don't re-apply)
	// Must include port in check to distinguish port 80 from port 443 rules with same comment
	existing := m.getLineNumbers("PREROUTING", "nat", comment, "DNAT", targetIP, targetPort)
	if len(existing) > 0 {
		logger.Debug("IPTABLES: PREROUTING DNAT rule already exists on %s (lines=%v), skipping", iface, existing)
		return nil
	}

	logger.Info("IPTABLES: inserting PREROUTING DNAT rule on interface %s: proto=%s dport=%s -> %s:%s comment=%q",
		iface, proto, sourcePort, targetIP, targetPort, comment)
	args := []string{
		"-w", "-t", "nat", "-I", "PREROUTING",
		"-i", iface,
		"-p", proto,
		"--dport", sourcePort,
		"-m", "comment", "--comment", comment,
		"-j", "DNAT", "--to", targetIP + ":" + targetPort,
	}
	return m.run(args...)
}

// InsertPostroutingMasquerade inserts a MASQUERADE POSTROUTING rule on the host
func (m *Manager) InsertPostroutingMasquerade(destCIDR, proto, destPort, comment string) error {
	logger.Info("IPTABLES: checking POSTROUTING MASQUERADE rule: dst=%s proto=%s dport=%s comment=%q",
		destCIDR, proto, destPort, comment)

	// Validate destination network is defined
	if destCIDR == "" {
		logger.Warn("IPTABLES: POSTROUTING MASQUERADE rule skipped — destination network not defined (comment=%q)", comment)
		return nil
	}

	// Check if rule already exists (idempotent: don't re-apply)
	// Must include port in check to distinguish port 80 from port 443 rules with same comment
	existing := m.getLineNumbers("POSTROUTING", "nat", comment, "MASQUERADE", destCIDR, destPort)
	if len(existing) > 0 {
		logger.Debug("IPTABLES: POSTROUTING MASQUERADE rule already exists (lines=%v), skipping", existing)
		return nil
	}

	logger.Info("IPTABLES: inserting POSTROUTING MASQUERADE rule: dst=%s proto=%s dport=%s comment=%q",
		destCIDR, proto, destPort, comment)
	args := []string{
		"-w", "-t", "nat", "-I", "POSTROUTING",
		"-d", destCIDR,
		"-p", proto,
		"--dport", destPort,
		"-m", "comment", "--comment", comment,
		"-j", "MASQUERADE",
	}
	return m.run(args...)
}

// InsertPostroutingMasqueradeForTarget inserts a MASQUERADE POSTROUTING rule for a target
func (m *Manager) InsertPostroutingMasqueradeForTarget(targetCIDR, proto, targetPort, comment string) error {
	logger.Info("IPTABLES: checking POSTROUTING MASQUERADE rule for target: dst=%s proto=%s dport=%s comment=%q",
		targetCIDR, proto, targetPort, comment)

	// Validate destination network is defined
	if targetCIDR == "" {
		logger.Warn("IPTABLES: POSTROUTING MASQUERADE for target skipped — destination network not defined (comment=%q)", comment)
		return nil
	}

	// Idempotent: check if rule already exists
	// Must include port in check to distinguish port 80 from port 443 rules with same comment
	existing := m.getLineNumbers("POSTROUTING", "nat", comment, "MASQUERADE", targetCIDR, targetPort)
	if len(existing) > 0 {
		logger.Debug("IPTABLES: POSTROUTING MASQUERADE for target already exists (lines=%v), skipping", existing)
		return nil
	}

	logger.Info("IPTABLES: inserting POSTROUTING MASQUERADE rule for target: dst=%s proto=%s dport=%s comment=%q",
		targetCIDR, proto, targetPort, comment)
	args := []string{
		"-w", "-t", "nat", "-I", "POSTROUTING",
		"-d", targetCIDR,
		"-p", proto,
		"--dport", targetPort,
		"-m", "comment", "--comment", comment,
		"-j", "MASQUERADE",
	}
	return m.run(args...)
}

// InsertForwardAccept inserts a FORWARD ACCEPT rule on the host
func (m *Manager) InsertForwardAccept(chain, sourceIP, targetIP, proto, sourcePort, targetPort, comment string) error {
	logger.Info("IPTABLES: checking FORWARD ACCEPT rule: chain=%s src=%s dst=%s proto=%s sport=%s dport=%s comment=%q",
		chain, sourceIP, targetIP, proto, sourcePort, targetPort, comment)

	// Validate source and destination networks are defined
	if sourceIP == "" || targetIP == "" {
		logger.Warn("IPTABLES: FORWARD ACCEPT rule skipped in %s — source=%q or destination=%q network not defined (comment=%q)",
			chain, sourceIP, targetIP, comment)
		return nil
	}

	// Idempotent: check if rule already exists
	var grepPatterns []string
	grepPatterns = append(grepPatterns, comment, proto)
	if sourceIP != "" {
		grepPatterns = append(grepPatterns, sourceIP)
	}
	if targetIP != "" {
		grepPatterns = append(grepPatterns, targetIP)
	}
	if sourcePort != "" {
		grepPatterns = append(grepPatterns, sourcePort)
	}
	if targetPort != "" {
		grepPatterns = append(grepPatterns, targetPort)
	}
	existing := m.getLineNumbers(chain, "", grepPatterns...)
	if len(existing) > 0 {
		logger.Debug("IPTABLES: FORWARD ACCEPT rule already exists in %s (lines=%v), skipping", chain, existing)
		return nil
	}

	logger.Info("IPTABLES: inserting FORWARD ACCEPT rule: chain=%s src=%s dst=%s proto=%s sport=%s dport=%s comment=%q",
		chain, sourceIP, targetIP, proto, sourcePort, targetPort, comment)
	args := []string{"-w", "-I", chain, "-p", proto}
	if sourceIP != "" {
		args = append(args, "-s", sourceIP)
	}
	if targetIP != "" {
		args = append(args, "-d", targetIP)
	}
	if sourcePort != "" {
		args = append(args, "--sport", sourcePort)
	}
	if targetPort != "" {
		args = append(args, "--dport", targetPort)
	}
	args = append(args, "-m", "comment", "--comment", comment, "-j", "ACCEPT")

	return m.run(args...)
}

// DeleteForwardAccept deletes a FORWARD ACCEPT rule by comment
func (m *Manager) DeleteForwardAccept(chain, comment string) error {
	logger.Info("IPTABLES: deleting FORWARD ACCEPT rules in %s with comment=%q", chain, comment)
	lines := m.getLineNumbers(chain, "", comment)
	if len(lines) > 0 {
		logger.Info("IPTABLES: found %d FORWARD ACCEPT rules to delete: %v", len(lines), lines)
	}
	for i := len(lines) - 1; i >= 0; i-- {
		if err := m.DeleteLine(chain, lines[i]); err != nil {
			return err
		}
	}
	return nil
}

// checkContainerChainExists lists rules inside a container namespace and returns
// the output, or an error if nsenter/iptables fails inside the container.
func (m *Manager) checkContainerChainExists(pid int, table, chain string) (string, error) {
	iptPath := m.binary
	nsenterArgs := []string{"-t", fmt.Sprintf("%d", pid), "-n", "--", iptPath, "-w", "-n", "-t", table, "-L", chain}
	cmd := exec.Command("nsenter", nsenterArgs...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("nsenter iptables list failed for PID %d chain %s/%s: %w (output: %s)", pid, table, chain, err, strings.TrimSpace(string(output)))
	}
	return string(output), nil
}

// InsertPreroutingRuleInContainer inserts a DNAT PREROUTING rule inside a container namespace
func (m *Manager) InsertPreroutingRuleInContainer(pid int, sourceIP, proto, sourcePort, targetIP, targetPort, comment string) error {
	logger.Info("IPTABLES: inserting PREROUTING DNAT rule in container PID %d: src=%s proto=%s dport=%s -> %s:%s comment=%q",
		pid, sourceIP, proto, sourcePort, targetIP, targetPort, comment)

	// First, try to list the chain inside the container to check state
	output, err := m.checkContainerChainExists(pid, "nat", "PREROUTING")
	if err != nil {
		// Cannot inspect container iptables — return error so caller falls back to host
		logger.Warn("IPTABLES: cannot check container PREROUTING (PID %d): %v", pid, err)
		return fmt.Errorf("cannot check container iptables: %w", err)
	}

	// Idempotent check: scan existing rules for matching patterns
	ruleExists := false
	for _, line := range strings.Split(output, "\n") {
		if strings.Contains(line, "DNAT") &&
			strings.Contains(line, sourcePort) &&
			strings.Contains(line, targetIP) &&
			strings.Contains(line, comment) {
			ruleExists = true
			break
		}
	}
	if ruleExists {
		logger.Info("IPTABLES: PREROUTING DNAT rule already exists in container PID %d (dport=%s -> %s), skipping", pid, sourcePort, targetIP)
		return nil
	}

	// Rule doesn't exist — clean up stale/duplicate rules then insert
	patterns := []string{"DNAT", sourcePort, targetIP, targetPort, comment}
	if delErr := m.deleteMatchingLinesInContainer(pid, "nat", "PREROUTING", patterns...); delErr != nil {
		logger.Debug("IPTABLES: stale PREROUTING cleanup in container PID %d: %v", pid, delErr)
	}

	args := []string{
		"-I", "PREROUTING",
		"-d", sourceIP,
		"-p", proto,
		"--dport", sourcePort,
		"-m", "comment", "--comment", comment,
		"-j", "DNAT", "--to", targetIP + ":" + targetPort,
	}
	return m.runInContainer(pid, "nat", args...)
}

// InsertPostroutingMasqueradeInContainer inserts a MASQUERADE POSTROUTING rule inside a container namespace
func (m *Manager) InsertPostroutingMasqueradeInContainer(pid int, destCIDR, proto, destPort, comment string) error {
	logger.Info("IPTABLES: inserting POSTROUTING MASQUERADE rule in container PID %d: dst=%s proto=%s dport=%s comment=%q",
		pid, destCIDR, proto, destPort, comment)

	// First, try to list the chain inside the container to check state
	output, err := m.checkContainerChainExists(pid, "nat", "POSTROUTING")
	if err != nil {
		logger.Warn("IPTABLES: cannot check container POSTROUTING (PID %d): %v", pid, err)
		return fmt.Errorf("cannot check container iptables: %w", err)
	}

	// Idempotent check: scan existing rules for matching patterns
	// Must include port to distinguish port 80 from port 443 rules with same comment
	ruleExists := false
	for _, line := range strings.Split(output, "\n") {
		if strings.Contains(line, "MASQUERADE") &&
			strings.Contains(line, comment) &&
			strings.Contains(line, destCIDR) &&
			strings.Contains(line, destPort) {
			ruleExists = true
			break
		}
	}
	if ruleExists {
		logger.Info("IPTABLES: POSTROUTING MASQUERADE rule already exists in container PID %d (dst=%s dport=%s), skipping", pid, destCIDR, destPort)
		return nil
	}

	// Rule doesn't exist — clean up stale/duplicate rules then insert
	patterns := []string{"MASQUERADE", comment, destCIDR, destPort}
	if delErr := m.deleteMatchingLinesInContainer(pid, "nat", "POSTROUTING", patterns...); delErr != nil {
		logger.Debug("IPTABLES: stale POSTROUTING cleanup in container PID %d: %v", pid, delErr)
	}

	args := []string{
		"-I", "POSTROUTING",
		"-d", destCIDR,
		"-p", proto,
		"--dport", destPort,
		"-m", "comment", "--comment", comment,
		"-j", "MASQUERADE",
	}
	return m.runInContainer(pid, "nat", args...)
}
