package config

import (
	"strings"
	"testing"
)

func TestIsPasswordHashed(t *testing.T) {
	// Generate a valid bcrypt hash for testing (60 chars, starts with $2)
	validBcryptHash := "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"

	tests := []struct {
		name     string
		password string
		expected bool
	}{
		// Valid bcrypt hashes (60 chars, $2a/$2b/$2y prefix)
		{"valid bcrypt 2a", validBcryptHash, true},
		{"valid bcrypt 2b", "$2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy", true},
		{"valid bcrypt 2y", "$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy", true},
		{"valid bcrypt different cost", "$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.BFB8hMSWy6s/FO", true},

		// Invalid: wrong prefix
		{"no $2 prefix", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456", false},
		{"plain password", "mysecretpassword", false},
		{"$1 prefix (MD5)", "$1$saltsalt$hashed", false},
		{"$5 prefix (SHA-256)", "$5$rounds=5000$saltsalt$hash", false},
		{"$6 prefix (SHA-512)", "$6$rounds=5000$saltsalt$hash", false},

		// Invalid: wrong length
		{"too short", "$2a$10$abc", false},
		{"59 chars (truncated)", "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhW", false},
		{"61 chars (too long)", "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWyX", false},
		{"55 chars (truncated, logs warning)", "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL1", false},

		// Edge cases
		{"empty string", "", false},
		{"just $2", "$2", false},
		{"$2a$", "$2a$", false},
		{"$2a$10$", "$2a$10$", false},
		{"whitespace", "   ", false},
		{"$2 but short", "$2a$10$short", false},

		// Real-world edge cases
		{"hash with newline", validBcryptHash + "\n", false},
		{"hash with trailing space", validBcryptHash + " ", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := IsPasswordHashed(tt.password)
			if result != tt.expected {
				t.Errorf("IsPasswordHashed(%q) = %v, want %v", tt.password, result, tt.expected)
			}
		})
	}
}

func TestIsValidDiscoveryEnvironment(t *testing.T) {
	tests := []struct {
		name     string
		value    string
		expected bool
	}{
		// Valid values (case insensitive)
		{"empty string (auto)", "", true},
		{"auto lowercase", "auto", true},
		{"AUTO uppercase", "AUTO", true},
		{"Auto mixed", "Auto", true},
		{"native", "native", true},
		{"NATIVE", "NATIVE", true},
		{"docker_host", "docker_host", true},
		{"DOCKER_HOST", "DOCKER_HOST", true},
		{"docker_bridge", "docker_bridge", true},
		{"DOCKER_BRIDGE", "DOCKER_BRIDGE", true},
		{"lxc_privileged", "lxc_privileged", true},
		{"LXC_PRIVILEGED", "LXC_PRIVILEGED", true},
		{"lxc_unprivileged", "lxc_unprivileged", true},
		{"LXC_UNPRIVILEGED", "LXC_UNPRIVILEGED", true},

		// Valid with whitespace trimming
		{"auto with leading space", " auto", true},
		{"auto with trailing space", "auto ", true},
		{"auto with both spaces", " auto ", true},
		{"native with tabs", "\tnative\t", true},

		// Invalid values
		{"unknown value", "unknown", false},
		{"typo dockerhost", "dockerhost", false},
		{"typo docker-host", "docker-host", false},
		{"kubernetes", "kubernetes", false},
		{"vm", "vm", false},
		{"baremetal", "baremetal", false},
		{"container", "container", false},
		{"partial match docker", "docker", false},
		{"partial match lxc", "lxc", false},
		{"underscore only", "_", false},
		{"random string", "xyzabc", false},
		{"numeric", "123", false},
		{"special chars", "docker@host", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := IsValidDiscoveryEnvironment(tt.value)
			if result != tt.expected {
				t.Errorf("IsValidDiscoveryEnvironment(%q) = %v, want %v", tt.value, result, tt.expected)
			}
		})
	}
}

func TestSplitAndTrim(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected []string
	}{
		// Basic splitting
		{"single value", "one", []string{"one"}},
		{"two values", "one,two", []string{"one", "two"}},
		{"three values", "one,two,three", []string{"one", "two", "three"}},

		// Whitespace handling
		{"value with leading space", " one", []string{"one"}},
		{"value with trailing space", "one ", []string{"one"}},
		{"values with spaces around comma", "one , two", []string{"one", "two"}},
		{"values with spaces", " one , two , three ", []string{"one", "two", "three"}},
		{"tabs and spaces", "\tone\t,\ttwo\t", []string{"one", "two"}},

		// Empty handling
		{"empty string", "", []string{}},
		{"only comma", ",", []string{}},
		{"multiple commas", ",,,", []string{}},
		{"comma with spaces", " , , ", []string{}},
		{"empty between values", "one,,two", []string{"one", "two"}},
		{"empty at start", ",one,two", []string{"one", "two"}},
		{"empty at end", "one,two,", []string{"one", "two"}},

		// Real-world examples
		{"CIDR list", "10.0.0.0/8, 192.168.0.0/16", []string{"10.0.0.0/8", "192.168.0.0/16"}},
		{"hostnames", "node1.local, node2.local, node3.local", []string{"node1.local", "node2.local", "node3.local"}},
		{"mixed content", "http://a.com, https://b.com", []string{"http://a.com", "https://b.com"}},

		// Edge cases
		{"single space", " ", []string{}},
		{"value is just spaces", "   ", []string{}},
		{"comma surrounded by spaces value", "one,   ,two", []string{"one", "two"}},
		{"long value", strings.Repeat("a", 100), []string{strings.Repeat("a", 100)}},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := splitAndTrim(tt.input)
			if len(result) != len(tt.expected) {
				t.Errorf("splitAndTrim(%q) returned %d items, want %d", tt.input, len(result), len(tt.expected))
				t.Errorf("got: %v, want: %v", result, tt.expected)
				return
			}
			for i, v := range result {
				if v != tt.expected[i] {
					t.Errorf("splitAndTrim(%q)[%d] = %q, want %q", tt.input, i, v, tt.expected[i])
				}
			}
		})
	}
}

func TestCloneDiscoveryConfig(t *testing.T) {
	tests := []struct {
		name string
		cfg  DiscoveryConfig
	}{
		{
			name: "empty config",
			cfg:  DiscoveryConfig{},
		},
		{
			name: "full config with all fields",
			cfg: DiscoveryConfig{
				EnvironmentOverride: "docker_host",
				SubnetAllowlist:     []string{"10.0.0.0/8", "192.168.0.0/16"},
				SubnetBlocklist:     []string{"172.16.0.0/12"},
				MaxHostsPerScan:     512,
				MaxConcurrent:       25,
				EnableReverseDNS:    true,
				ScanGateways:        false,
				DialTimeout:         2000,
				HTTPTimeout:         3000,
			},
		},
		{
			name: "nil slices",
			cfg: DiscoveryConfig{
				EnvironmentOverride: "native",
				SubnetAllowlist:     nil,
				SubnetBlocklist:     nil,
				MaxHostsPerScan:     100,
			},
		},
		{
			name: "empty slices",
			cfg: DiscoveryConfig{
				SubnetAllowlist: []string{},
				SubnetBlocklist: []string{},
			},
		},
		{
			name: "single element slices",
			cfg: DiscoveryConfig{
				SubnetAllowlist: []string{"10.0.0.0/8"},
				SubnetBlocklist: []string{"169.254.0.0/16"},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			clone := CloneDiscoveryConfig(tt.cfg)

			// Verify all scalar fields match
			if clone.EnvironmentOverride != tt.cfg.EnvironmentOverride {
				t.Errorf("EnvironmentOverride: got %q, want %q", clone.EnvironmentOverride, tt.cfg.EnvironmentOverride)
			}
			if clone.MaxHostsPerScan != tt.cfg.MaxHostsPerScan {
				t.Errorf("MaxHostsPerScan: got %d, want %d", clone.MaxHostsPerScan, tt.cfg.MaxHostsPerScan)
			}
			if clone.MaxConcurrent != tt.cfg.MaxConcurrent {
				t.Errorf("MaxConcurrent: got %d, want %d", clone.MaxConcurrent, tt.cfg.MaxConcurrent)
			}
			if clone.EnableReverseDNS != tt.cfg.EnableReverseDNS {
				t.Errorf("EnableReverseDNS: got %v, want %v", clone.EnableReverseDNS, tt.cfg.EnableReverseDNS)
			}
			if clone.ScanGateways != tt.cfg.ScanGateways {
				t.Errorf("ScanGateways: got %v, want %v", clone.ScanGateways, tt.cfg.ScanGateways)
			}
			if clone.DialTimeout != tt.cfg.DialTimeout {
				t.Errorf("DialTimeout: got %d, want %d", clone.DialTimeout, tt.cfg.DialTimeout)
			}
			if clone.HTTPTimeout != tt.cfg.HTTPTimeout {
				t.Errorf("HTTPTimeout: got %d, want %d", clone.HTTPTimeout, tt.cfg.HTTPTimeout)
			}

			// Verify slice contents match
			if len(clone.SubnetAllowlist) != len(tt.cfg.SubnetAllowlist) {
				t.Errorf("SubnetAllowlist length: got %d, want %d", len(clone.SubnetAllowlist), len(tt.cfg.SubnetAllowlist))
			} else {
				for i, v := range clone.SubnetAllowlist {
					if v != tt.cfg.SubnetAllowlist[i] {
						t.Errorf("SubnetAllowlist[%d]: got %q, want %q", i, v, tt.cfg.SubnetAllowlist[i])
					}
				}
			}

			if len(clone.SubnetBlocklist) != len(tt.cfg.SubnetBlocklist) {
				t.Errorf("SubnetBlocklist length: got %d, want %d", len(clone.SubnetBlocklist), len(tt.cfg.SubnetBlocklist))
			} else {
				for i, v := range clone.SubnetBlocklist {
					if v != tt.cfg.SubnetBlocklist[i] {
						t.Errorf("SubnetBlocklist[%d]: got %q, want %q", i, v, tt.cfg.SubnetBlocklist[i])
					}
				}
			}
		})
	}
}

// TestCloneDiscoveryConfig_SliceIndependence verifies that modifying the clone
// does not affect the original (deep copy verification).
func TestCloneDiscoveryConfig_SliceIndependence(t *testing.T) {
	original := DiscoveryConfig{
		SubnetAllowlist: []string{"10.0.0.0/8", "192.168.0.0/16"},
		SubnetBlocklist: []string{"172.16.0.0/12", "169.254.0.0/16"},
	}

	clone := CloneDiscoveryConfig(original)

	// Modify the clone's slices
	clone.SubnetAllowlist[0] = "modified"
	clone.SubnetBlocklist[0] = "modified"

	// Original should be unchanged
	if original.SubnetAllowlist[0] != "10.0.0.0/8" {
		t.Errorf("Original SubnetAllowlist was modified: got %q", original.SubnetAllowlist[0])
	}
	if original.SubnetBlocklist[0] != "172.16.0.0/12" {
		t.Errorf("Original SubnetBlocklist was modified: got %q", original.SubnetBlocklist[0])
	}

	// Append to clone slices
	clone.SubnetAllowlist = append(clone.SubnetAllowlist, "new")

	// Original length should be unchanged
	if len(original.SubnetAllowlist) != 2 {
		t.Errorf("Original SubnetAllowlist length changed: got %d", len(original.SubnetAllowlist))
	}
}

// TestCloneDiscoveryConfig_NilSliceIndependence verifies nil slices remain nil in clone.
func TestCloneDiscoveryConfig_NilSliceIndependence(t *testing.T) {
	original := DiscoveryConfig{
		SubnetAllowlist: nil,
		SubnetBlocklist: nil,
	}

	clone := CloneDiscoveryConfig(original)

	if clone.SubnetAllowlist != nil {
		t.Errorf("Expected nil SubnetAllowlist, got %v", clone.SubnetAllowlist)
	}
	if clone.SubnetBlocklist != nil {
		t.Errorf("Expected nil SubnetBlocklist, got %v", clone.SubnetBlocklist)
	}
}

func TestNormalizeDiscoveryConfig(t *testing.T) {
	defaults := DefaultDiscoveryConfig()

	tests := []struct {
		name     string
		cfg      DiscoveryConfig
		expected DiscoveryConfig
	}{
		{
			name: "empty config gets defaults",
			cfg:  DiscoveryConfig{},
			expected: DiscoveryConfig{
				EnvironmentOverride: defaults.EnvironmentOverride,
				SubnetAllowlist:     []string{},
				SubnetBlocklist:     defaults.SubnetBlocklist,
				MaxHostsPerScan:     defaults.MaxHostsPerScan,
				MaxConcurrent:       defaults.MaxConcurrent,
				DialTimeout:         defaults.DialTimeout,
				HTTPTimeout:         defaults.HTTPTimeout,
			},
		},
		{
			name: "valid environment preserved",
			cfg: DiscoveryConfig{
				EnvironmentOverride: "docker_host",
			},
			expected: DiscoveryConfig{
				EnvironmentOverride: "docker_host",
				SubnetAllowlist:     []string{},
				SubnetBlocklist:     defaults.SubnetBlocklist,
				MaxHostsPerScan:     defaults.MaxHostsPerScan,
				MaxConcurrent:       defaults.MaxConcurrent,
				DialTimeout:         defaults.DialTimeout,
				HTTPTimeout:         defaults.HTTPTimeout,
			},
		},
		{
			name: "invalid environment falls back to auto",
			cfg: DiscoveryConfig{
				EnvironmentOverride: "invalid_env",
			},
			expected: DiscoveryConfig{
				EnvironmentOverride: "auto",
				SubnetAllowlist:     []string{},
				SubnetBlocklist:     defaults.SubnetBlocklist,
				MaxHostsPerScan:     defaults.MaxHostsPerScan,
				MaxConcurrent:       defaults.MaxConcurrent,
				DialTimeout:         defaults.DialTimeout,
				HTTPTimeout:         defaults.HTTPTimeout,
			},
		},
		{
			name: "environment with whitespace trimmed",
			cfg: DiscoveryConfig{
				EnvironmentOverride: "  native  ",
			},
			expected: DiscoveryConfig{
				EnvironmentOverride: "native",
				SubnetAllowlist:     []string{},
				SubnetBlocklist:     defaults.SubnetBlocklist,
				MaxHostsPerScan:     defaults.MaxHostsPerScan,
				MaxConcurrent:       defaults.MaxConcurrent,
				DialTimeout:         defaults.DialTimeout,
				HTTPTimeout:         defaults.HTTPTimeout,
			},
		},
		{
			name: "positive values preserved",
			cfg: DiscoveryConfig{
				MaxHostsPerScan: 100,
				MaxConcurrent:   10,
				DialTimeout:     500,
				HTTPTimeout:     1000,
			},
			expected: DiscoveryConfig{
				EnvironmentOverride: defaults.EnvironmentOverride,
				SubnetAllowlist:     []string{},
				SubnetBlocklist:     defaults.SubnetBlocklist,
				MaxHostsPerScan:     100,
				MaxConcurrent:       10,
				DialTimeout:         500,
				HTTPTimeout:         1000,
			},
		},
		{
			name: "zero values get defaults",
			cfg: DiscoveryConfig{
				MaxHostsPerScan: 0,
				MaxConcurrent:   0,
				DialTimeout:     0,
				HTTPTimeout:     0,
			},
			expected: DiscoveryConfig{
				EnvironmentOverride: defaults.EnvironmentOverride,
				SubnetAllowlist:     []string{},
				SubnetBlocklist:     defaults.SubnetBlocklist,
				MaxHostsPerScan:     defaults.MaxHostsPerScan,
				MaxConcurrent:       defaults.MaxConcurrent,
				DialTimeout:         defaults.DialTimeout,
				HTTPTimeout:         defaults.HTTPTimeout,
			},
		},
		{
			name: "negative values get defaults",
			cfg: DiscoveryConfig{
				MaxHostsPerScan: -1,
				MaxConcurrent:   -10,
				DialTimeout:     -100,
				HTTPTimeout:     -1000,
			},
			expected: DiscoveryConfig{
				EnvironmentOverride: defaults.EnvironmentOverride,
				SubnetAllowlist:     []string{},
				SubnetBlocklist:     defaults.SubnetBlocklist,
				MaxHostsPerScan:     defaults.MaxHostsPerScan,
				MaxConcurrent:       defaults.MaxConcurrent,
				DialTimeout:         defaults.DialTimeout,
				HTTPTimeout:         defaults.HTTPTimeout,
			},
		},
		{
			name: "subnet allowlist sanitized",
			cfg: DiscoveryConfig{
				SubnetAllowlist: []string{" 10.0.0.0/8 ", "10.0.0.0/8", "", "192.168.0.0/16"},
			},
			expected: DiscoveryConfig{
				EnvironmentOverride: defaults.EnvironmentOverride,
				SubnetAllowlist:     []string{"10.0.0.0/8", "192.168.0.0/16"},
				SubnetBlocklist:     defaults.SubnetBlocklist,
				MaxHostsPerScan:     defaults.MaxHostsPerScan,
				MaxConcurrent:       defaults.MaxConcurrent,
				DialTimeout:         defaults.DialTimeout,
				HTTPTimeout:         defaults.HTTPTimeout,
			},
		},
		{
			name: "subnet blocklist sanitized",
			cfg: DiscoveryConfig{
				SubnetBlocklist: []string{" 172.16.0.0/12 ", "", "172.16.0.0/12"},
			},
			expected: DiscoveryConfig{
				EnvironmentOverride: defaults.EnvironmentOverride,
				SubnetAllowlist:     []string{},
				SubnetBlocklist:     []string{"172.16.0.0/12"},
				MaxHostsPerScan:     defaults.MaxHostsPerScan,
				MaxConcurrent:       defaults.MaxConcurrent,
				DialTimeout:         defaults.DialTimeout,
				HTTPTimeout:         defaults.HTTPTimeout,
			},
		},
		{
			name: "nil blocklist gets defaults",
			cfg: DiscoveryConfig{
				SubnetBlocklist: nil,
			},
			expected: DiscoveryConfig{
				EnvironmentOverride: defaults.EnvironmentOverride,
				SubnetAllowlist:     []string{},
				SubnetBlocklist:     defaults.SubnetBlocklist,
				MaxHostsPerScan:     defaults.MaxHostsPerScan,
				MaxConcurrent:       defaults.MaxConcurrent,
				DialTimeout:         defaults.DialTimeout,
				HTTPTimeout:         defaults.HTTPTimeout,
			},
		},
		{
			name: "empty blocklist after sanitization stays empty",
			cfg: DiscoveryConfig{
				SubnetBlocklist: []string{"", "  ", ""},
			},
			expected: DiscoveryConfig{
				EnvironmentOverride: defaults.EnvironmentOverride,
				SubnetAllowlist:     []string{},
				SubnetBlocklist:     []string{}, // sanitizeCIDRList returns []string{} not nil
				MaxHostsPerScan:     defaults.MaxHostsPerScan,
				MaxConcurrent:       defaults.MaxConcurrent,
				DialTimeout:         defaults.DialTimeout,
				HTTPTimeout:         defaults.HTTPTimeout,
			},
		},
		{
			name: "boolean fields preserved",
			cfg: DiscoveryConfig{
				EnableReverseDNS: false,
				ScanGateways:     true,
			},
			expected: DiscoveryConfig{
				EnvironmentOverride: defaults.EnvironmentOverride,
				SubnetAllowlist:     []string{},
				SubnetBlocklist:     defaults.SubnetBlocklist,
				MaxHostsPerScan:     defaults.MaxHostsPerScan,
				MaxConcurrent:       defaults.MaxConcurrent,
				EnableReverseDNS:    false,
				ScanGateways:        true,
				DialTimeout:         defaults.DialTimeout,
				HTTPTimeout:         defaults.HTTPTimeout,
			},
		},
		{
			name: "all valid environments",
			cfg: DiscoveryConfig{
				EnvironmentOverride: "lxc_privileged",
			},
			expected: DiscoveryConfig{
				EnvironmentOverride: "lxc_privileged",
				SubnetAllowlist:     []string{},
				SubnetBlocklist:     defaults.SubnetBlocklist,
				MaxHostsPerScan:     defaults.MaxHostsPerScan,
				MaxConcurrent:       defaults.MaxConcurrent,
				DialTimeout:         defaults.DialTimeout,
				HTTPTimeout:         defaults.HTTPTimeout,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := NormalizeDiscoveryConfig(tt.cfg)

			if result.EnvironmentOverride != tt.expected.EnvironmentOverride {
				t.Errorf("EnvironmentOverride: got %q, want %q", result.EnvironmentOverride, tt.expected.EnvironmentOverride)
			}
			if result.MaxHostsPerScan != tt.expected.MaxHostsPerScan {
				t.Errorf("MaxHostsPerScan: got %d, want %d", result.MaxHostsPerScan, tt.expected.MaxHostsPerScan)
			}
			if result.MaxConcurrent != tt.expected.MaxConcurrent {
				t.Errorf("MaxConcurrent: got %d, want %d", result.MaxConcurrent, tt.expected.MaxConcurrent)
			}
			if result.EnableReverseDNS != tt.expected.EnableReverseDNS {
				t.Errorf("EnableReverseDNS: got %v, want %v", result.EnableReverseDNS, tt.expected.EnableReverseDNS)
			}
			if result.ScanGateways != tt.expected.ScanGateways {
				t.Errorf("ScanGateways: got %v, want %v", result.ScanGateways, tt.expected.ScanGateways)
			}
			if result.DialTimeout != tt.expected.DialTimeout {
				t.Errorf("DialTimeout: got %d, want %d", result.DialTimeout, tt.expected.DialTimeout)
			}
			if result.HTTPTimeout != tt.expected.HTTPTimeout {
				t.Errorf("HTTPTimeout: got %d, want %d", result.HTTPTimeout, tt.expected.HTTPTimeout)
			}

			// Check slice equality
			if len(result.SubnetAllowlist) != len(tt.expected.SubnetAllowlist) {
				t.Errorf("SubnetAllowlist length: got %d, want %d", len(result.SubnetAllowlist), len(tt.expected.SubnetAllowlist))
			} else {
				for i, v := range result.SubnetAllowlist {
					if v != tt.expected.SubnetAllowlist[i] {
						t.Errorf("SubnetAllowlist[%d]: got %q, want %q", i, v, tt.expected.SubnetAllowlist[i])
					}
				}
			}

			if len(result.SubnetBlocklist) != len(tt.expected.SubnetBlocklist) {
				t.Errorf("SubnetBlocklist length: got %d, want %d", len(result.SubnetBlocklist), len(tt.expected.SubnetBlocklist))
			} else {
				for i, v := range result.SubnetBlocklist {
					if v != tt.expected.SubnetBlocklist[i] {
						t.Errorf("SubnetBlocklist[%d]: got %q, want %q", i, v, tt.expected.SubnetBlocklist[i])
					}
				}
			}
		})
	}
}

// TestNormalizeDiscoveryConfig_DoesNotModifyInput verifies the original config is not mutated.
func TestNormalizeDiscoveryConfig_DoesNotModifyInput(t *testing.T) {
	original := DiscoveryConfig{
		EnvironmentOverride: "  docker_host  ",
		SubnetAllowlist:     []string{" 10.0.0.0/8 ", "192.168.0.0/16"},
		SubnetBlocklist:     []string{" 172.16.0.0/12 "},
		MaxHostsPerScan:     -1,
	}

	// Store original values
	origEnv := original.EnvironmentOverride
	origAllowlist := make([]string, len(original.SubnetAllowlist))
	copy(origAllowlist, original.SubnetAllowlist)
	origBlocklist := make([]string, len(original.SubnetBlocklist))
	copy(origBlocklist, original.SubnetBlocklist)
	origMaxHosts := original.MaxHostsPerScan

	_ = NormalizeDiscoveryConfig(original)

	// Verify original is unchanged
	if original.EnvironmentOverride != origEnv {
		t.Errorf("Original EnvironmentOverride modified: got %q, want %q", original.EnvironmentOverride, origEnv)
	}
	if original.MaxHostsPerScan != origMaxHosts {
		t.Errorf("Original MaxHostsPerScan modified: got %d, want %d", original.MaxHostsPerScan, origMaxHosts)
	}
	for i, v := range original.SubnetAllowlist {
		if v != origAllowlist[i] {
			t.Errorf("Original SubnetAllowlist[%d] modified: got %q, want %q", i, v, origAllowlist[i])
		}
	}
	for i, v := range original.SubnetBlocklist {
		if v != origBlocklist[i] {
			t.Errorf("Original SubnetBlocklist[%d] modified: got %q, want %q", i, v, origBlocklist[i])
		}
	}
}

func TestSanitizeCIDRList(t *testing.T) {
	tests := []struct {
		name     string
		input    []string
		expected []string
	}{
		// Nil and empty handling
		{"nil input", nil, nil},
		{"empty slice", []string{}, nil},

		// Single entry
		{"single valid entry", []string{"10.0.0.0/8"}, []string{"10.0.0.0/8"}},
		{"single entry with leading space", []string{" 10.0.0.0/8"}, []string{"10.0.0.0/8"}},
		{"single entry with trailing space", []string{"10.0.0.0/8 "}, []string{"10.0.0.0/8"}},
		{"single entry with both spaces", []string{"  10.0.0.0/8  "}, []string{"10.0.0.0/8"}},
		{"single entry with tabs", []string{"\t10.0.0.0/8\t"}, []string{"10.0.0.0/8"}},

		// Multiple valid entries (no duplicates)
		{"two valid entries", []string{"10.0.0.0/8", "192.168.0.0/16"}, []string{"10.0.0.0/8", "192.168.0.0/16"}},
		{"three valid entries", []string{"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"}, []string{"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"}},

		// Duplicates at various positions
		{"duplicate at end", []string{"10.0.0.0/8", "192.168.0.0/16", "10.0.0.0/8"}, []string{"10.0.0.0/8", "192.168.0.0/16"}},
		{"duplicate at start", []string{"10.0.0.0/8", "10.0.0.0/8", "192.168.0.0/16"}, []string{"10.0.0.0/8", "192.168.0.0/16"}},
		{"duplicate in middle", []string{"10.0.0.0/8", "192.168.0.0/16", "10.0.0.0/8", "172.16.0.0/12"}, []string{"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"}},
		{"all duplicates", []string{"10.0.0.0/8", "10.0.0.0/8", "10.0.0.0/8"}, []string{"10.0.0.0/8"}},
		{"multiple duplicates", []string{"a", "b", "a", "c", "b", "d", "a"}, []string{"a", "b", "c", "d"}},

		// Whitespace variations
		{"entry with leading whitespace", []string{"  10.0.0.0/8", "192.168.0.0/16"}, []string{"10.0.0.0/8", "192.168.0.0/16"}},
		{"entry with trailing whitespace", []string{"10.0.0.0/8  ", "192.168.0.0/16"}, []string{"10.0.0.0/8", "192.168.0.0/16"}},
		{"mixed whitespace", []string{" 10.0.0.0/8", "192.168.0.0/16 ", "  172.16.0.0/12  "}, []string{"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"}},
		{"tabs and spaces mixed", []string{"\t10.0.0.0/8 ", " \t192.168.0.0/16\t ", "  172.16.0.0/12"}, []string{"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"}},

		// Entries that become duplicates after trimming
		{"duplicates after trim", []string{" 10.0.0.0/8", "10.0.0.0/8 ", "  10.0.0.0/8  "}, []string{"10.0.0.0/8"}},
		{"duplicates after trim with others", []string{" 10.0.0.0/8", "192.168.0.0/16", "10.0.0.0/8 "}, []string{"10.0.0.0/8", "192.168.0.0/16"}},

		// All empty/whitespace entries
		{"single empty string", []string{""}, []string{}},
		{"multiple empty strings", []string{"", "", ""}, []string{}},
		{"only whitespace", []string{" ", "  ", "\t", " \t "}, []string{}},
		{"mixed empty and whitespace", []string{"", " ", "", "\t"}, []string{}},

		// Mixed valid and empty entries
		{"valid with empty at start", []string{"", "10.0.0.0/8", "192.168.0.0/16"}, []string{"10.0.0.0/8", "192.168.0.0/16"}},
		{"valid with empty in middle", []string{"10.0.0.0/8", "", "192.168.0.0/16"}, []string{"10.0.0.0/8", "192.168.0.0/16"}},
		{"valid with empty at end", []string{"10.0.0.0/8", "192.168.0.0/16", ""}, []string{"10.0.0.0/8", "192.168.0.0/16"}},
		{"valid with multiple empty", []string{"", "10.0.0.0/8", "", "192.168.0.0/16", "", ""}, []string{"10.0.0.0/8", "192.168.0.0/16"}},
		{"valid with whitespace entries", []string{" ", "10.0.0.0/8", "  ", "192.168.0.0/16", "\t"}, []string{"10.0.0.0/8", "192.168.0.0/16"}},

		// Order preservation
		{"order preserved basic", []string{"z", "a", "m"}, []string{"z", "a", "m"}},
		{"order preserved with duplicates", []string{"z", "a", "m", "z", "a"}, []string{"z", "a", "m"}},
		{"order preserved complex", []string{"third", "first", "second", "third", "first"}, []string{"third", "first", "second"}},
		{"order preserved after trim", []string{" c ", "a", " b ", "c", " a "}, []string{"c", "a", "b"}},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := sanitizeCIDRList(tt.input)

			// Check nil vs empty slice distinction
			if tt.expected == nil {
				if result != nil {
					t.Errorf("sanitizeCIDRList(%v) = %v, want nil", tt.input, result)
				}
				return
			}

			if result == nil {
				t.Errorf("sanitizeCIDRList(%v) = nil, want %v", tt.input, tt.expected)
				return
			}

			// Check length
			if len(result) != len(tt.expected) {
				t.Errorf("sanitizeCIDRList(%v) returned %d items, want %d", tt.input, len(result), len(tt.expected))
				t.Errorf("got: %v, want: %v", result, tt.expected)
				return
			}

			// Check each element
			for i, v := range result {
				if v != tt.expected[i] {
					t.Errorf("sanitizeCIDRList(%v)[%d] = %q, want %q", tt.input, i, v, tt.expected[i])
				}
			}
		})
	}
}
