// Package process provides process isolation and security enforcement for worker tasks.
// This file implements Network Micro-Segmentation enforcement hooks.
//go:build linux
// +build linux

package process

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// NetworkPolicy defines network segmentation rules for a task
type NetworkPolicy struct {
	// Mode is the network isolation mode: "none", "bridge", "container", "host"
	Mode string

	// AllowedEndpoints is a list of allowed network endpoints (host:port format)
	// Only used when Mode is "bridge" or "container"
	AllowedEndpoints []string

	// BlockedSubnets is a list of CIDR ranges to block
	BlockedSubnets []string

	// DNSResolution controls DNS resolution (true = allow, false = block)
	DNSResolution bool

	// OutboundTraffic controls outbound connections (true = allow, false = block)
	OutboundTraffic bool

	// InboundTraffic controls inbound connections (true = allow, false = block)
	InboundTraffic bool
}

// DefaultNetworkPolicy returns a hardened default network policy
// This implements Network Micro-Segmentation
func DefaultNetworkPolicy() NetworkPolicy {
	return NetworkPolicy{
		Mode:             "none",
		AllowedEndpoints: []string{},
		BlockedSubnets:   []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"},
		DNSResolution:    false,
		OutboundTraffic:  false,
		InboundTraffic:   false,
	}
}

// HIPAACompliantPolicy returns a network policy suitable for HIPAA compliance
// This blocks all external network access except specific allowlisted endpoints
func HIPAACompliantPolicy(allowlist []string) NetworkPolicy {
	return NetworkPolicy{
		Mode:             "bridge",
		AllowedEndpoints: allowlist,
		BlockedSubnets:   []string{"0.0.0.0/0"}, // Block everything by default
		DNSResolution:    len(allowlist) > 0,    // Only allow DNS if endpoints specified
		OutboundTraffic:  len(allowlist) > 0,    // Only allow outbound if endpoints specified
		InboundTraffic:   false,                 // Never allow inbound
	}
}

// Validate checks the network policy for security violations
func (np *NetworkPolicy) Validate() error {
	// Validate mode
	validModes := map[string]bool{
		"none":      true,
		"bridge":    true,
		"container": true,
		"host":      true,
	}
	if !validModes[np.Mode] {
		return fmt.Errorf("invalid network mode: %q", np.Mode)
	}

	// Block host network mode in production
	if np.Mode == "host" {
		return fmt.Errorf("host network mode is not allowed for security reasons")
	}

	// Validate allowed endpoints format
	for _, endpoint := range np.AllowedEndpoints {
		if !isValidEndpoint(endpoint) {
			return fmt.Errorf("invalid endpoint format: %q (expected host:port)", endpoint)
		}
	}

	// Validate CIDR blocks
	for _, cidr := range np.BlockedSubnets {
		if !isValidCIDR(cidr) {
			return fmt.Errorf("invalid CIDR format: %q", cidr)
		}
	}

	return nil
}

// isValidEndpoint checks if an endpoint string is valid (host:port format)
func isValidEndpoint(endpoint string) bool {
	if endpoint == "" {
		return false
	}
	parts := strings.Split(endpoint, ":")
	if len(parts) != 2 {
		return false
	}
	// Basic validation - port should be numeric
	if _, err := parsePort(parts[1]); err != nil {
		return false
	}
	return true
}

// isValidCIDR performs basic CIDR validation
func isValidCIDR(cidr string) bool {
	// Simple validation - check for / separator
	if !strings.Contains(cidr, "/") {
		return false
	}
	parts := strings.Split(cidr, "/")
	if len(parts) != 2 {
		return false
	}
	// Check prefix is numeric
	if _, err := parsePort(parts[1]); err != nil {
		return false
	}
	return true
}

// parsePort parses a port string (helper for validation)
func parsePort(s string) (int, error) {
	port := 0
	for _, c := range s {
		if c < '0' || c > '9' {
			return 0, fmt.Errorf("invalid port")
		}
		port = port*10 + int(c-'0')
	}
	return port, nil
}

// ApplyNetworkPolicy applies network policy enforcement to a podman command
// This creates iptables rules and returns the modified command with network options
func ApplyNetworkPolicy(policy NetworkPolicy, baseArgs []string) ([]string, error) {
	if err := policy.Validate(); err != nil {
		return nil, fmt.Errorf("invalid network policy: %w", err)
	}

	// Apply network mode
	args := append(baseArgs, "--network", policy.Mode)

	// For bridge mode with specific restrictions, we need to create a custom network
	if policy.Mode == "bridge" && len(policy.AllowedEndpoints) > 0 {
		// Add additional network restrictions via iptables (applied externally)
		// The container will be started with the bridge network, but external
		// firewall rules will restrict its connectivity

		// Set environment variables to inform the container about network restrictions
		args = append(args, "-e", "FETCHML_NETWORK_RESTRICTED=1")
		if !policy.DNSResolution {
			args = append(args, "-e", "FETCHML_DNS_DISABLED=1")
		}
	}

	// Disable DNS if required (via /etc/resolv.conf bind mount)
	if !policy.DNSResolution {
		// Mount empty resolv.conf to disable DNS
		emptyResolv, err := createEmptyResolvConf()
		if err == nil {
			args = append(args, "-v", fmt.Sprintf("%s:/etc/resolv.conf:ro", emptyResolv))
		}
	}

	return args, nil
}

// createEmptyResolvConf creates a temporary empty resolv.conf file
func createEmptyResolvConf() (string, error) {
	tmpDir := os.TempDir()
	path := filepath.Join(tmpDir, "empty-resolv.conf")

	// Create empty file if it doesn't exist
	if _, err := os.Stat(path); os.IsNotExist(err) {
		if err := os.WriteFile(path, []byte{}, 0644); err != nil {
			return "", err
		}
	}

	return path, nil
}

// SetupExternalFirewall sets up external firewall rules for a container
// This is called after the container starts to enforce egress filtering
// NOTE: This requires root or CAP_NET_ADMIN capability
func SetupExternalFirewall(containerID string, policy NetworkPolicy) error {
	// This function requires root privileges and iptables
	// It's meant to be called from a privileged helper or init container

	if len(policy.BlockedSubnets) == 0 && len(policy.AllowedEndpoints) == 0 {
		return nil // No rules to apply
	}

	// Get container PID for network namespace targeting
	pid, err := getContainerPID(containerID)
	if err != nil {
		return fmt.Errorf("failed to get container PID: %w", err)
	}

	// Create iptables commands in the container's network namespace
	// This requires nsenter with appropriate capabilities

	// Block all outbound traffic by default
	if !policy.OutboundTraffic {
		cmd := exec.Command("nsenter", "-t", pid, "-n", "iptables", "-A", "OUTPUT", "-j", "DROP")
		if err := cmd.Run(); err != nil {
			return fmt.Errorf("failed to block outbound traffic: %w", err)
		}
	}

	// Allow specific endpoints
	for _, endpoint := range policy.AllowedEndpoints {
		host, port := parseEndpoint(endpoint)
		if host != "" {
			cmd := exec.Command("nsenter", "-t", pid, "-n", "iptables", "-I", "OUTPUT", "1",
				"-p", "tcp", "-d", host, "--dport", port, "-j", "ACCEPT")
			if err := cmd.Run(); err != nil {
				return fmt.Errorf("failed to allow endpoint %s: %w", endpoint, err)
			}
		}
	}

	return nil
}

// getContainerPID retrieves the PID of a running container
func getContainerPID(containerID string) (string, error) {
	cmd := exec.Command("podman", "inspect", "-f", "{{.State.Pid}}", containerID)
	output, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(output)), nil
}

// parseEndpoint splits an endpoint string into host and port
func parseEndpoint(endpoint string) (host, port string) {
	parts := strings.Split(endpoint, ":")
	if len(parts) == 2 {
		return parts[0], parts[1]
	}
	return "", ""
}

// NetworkPolicyFromSandbox creates a NetworkPolicy from sandbox configuration
func NetworkPolicyFromSandbox(
	networkMode string,
	allowedEndpoints []string,
	blockedSubnets []string,
) NetworkPolicy {
	// Use defaults if not specified
	if networkMode == "" {
		networkMode = "none"
	}
	if len(blockedSubnets) == 0 {
		blockedSubnets = DefaultNetworkPolicy().BlockedSubnets
	}

	return NetworkPolicy{
		Mode:             networkMode,
		AllowedEndpoints: allowedEndpoints,
		BlockedSubnets:   blockedSubnets,
		DNSResolution:    networkMode != "none" && len(allowedEndpoints) > 0,
		OutboundTraffic:  networkMode != "none" && len(allowedEndpoints) > 0,
		InboundTraffic:   false, // Never allow inbound by default
	}
}
