package discovery

import (
	"net"
	"testing"

	"github.com/rcourtman/pulse-go-rewrite/internal/config"
	"github.com/rcourtman/pulse-go-rewrite/pkg/discovery/envdetect"
)

func TestParseCIDRs(t *testing.T) {
	tests := []struct {
		name         string
		values       []string
		wantCount    int
		wantWarnings int
	}{
		{
			name:         "empty input",
			values:       []string{},
			wantCount:    0,
			wantWarnings: 0,
		},
		{
			name:         "nil input",
			values:       nil,
			wantCount:    0,
			wantWarnings: 0,
		},
		{
			name:         "single valid CIDR",
			values:       []string{"192.168.1.0/24"},
			wantCount:    1,
			wantWarnings: 0,
		},
		{
			name:         "multiple valid CIDRs",
			values:       []string{"192.168.1.0/24", "10.0.0.0/8", "172.16.0.0/12"},
			wantCount:    3,
			wantWarnings: 0,
		},
		{
			name:         "CIDR with whitespace",
			values:       []string{"  192.168.1.0/24  ", " 10.0.0.0/8"},
			wantCount:    2,
			wantWarnings: 0,
		},
		{
			name:         "empty string in list",
			values:       []string{"192.168.1.0/24", "", "10.0.0.0/8"},
			wantCount:    2,
			wantWarnings: 0,
		},
		{
			name:         "whitespace-only string",
			values:       []string{"192.168.1.0/24", "   "},
			wantCount:    1,
			wantWarnings: 0,
		},
		{
			name:         "invalid CIDR generates warning",
			values:       []string{"not-a-cidr"},
			wantCount:    0,
			wantWarnings: 1,
		},
		{
			name:         "IP without mask generates warning",
			values:       []string{"192.168.1.1"},
			wantCount:    0,
			wantWarnings: 1,
		},
		{
			name:         "mixed valid and invalid",
			values:       []string{"192.168.1.0/24", "invalid", "10.0.0.0/8"},
			wantCount:    2,
			wantWarnings: 1,
		},
		{
			name:         "IPv6 CIDR",
			values:       []string{"fe80::/10"},
			wantCount:    1,
			wantWarnings: 0,
		},
		{
			name:         "invalid mask",
			values:       []string{"192.168.1.0/33"},
			wantCount:    0,
			wantWarnings: 1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var warnings []string
			result := parseCIDRs(tt.values, &warnings)

			if len(result) != tt.wantCount {
				t.Errorf("parseCIDRs() returned %d CIDRs, want %d", len(result), tt.wantCount)
			}
			if len(warnings) != tt.wantWarnings {
				t.Errorf("parseCIDRs() generated %d warnings, want %d", len(warnings), tt.wantWarnings)
			}
		})
	}
}

func TestParseCIDRs_NilWarnings(t *testing.T) {
	// Test that nil warnings pointer doesn't panic
	result := parseCIDRs([]string{"192.168.1.0/24", "invalid"}, nil)
	if len(result) != 1 {
		t.Errorf("parseCIDRs() with nil warnings returned %d CIDRs, want 1", len(result))
	}
}

func TestParseCIDRMap(t *testing.T) {
	tests := []struct {
		name      string
		values    []string
		wantCount int
	}{
		{
			name:      "empty input",
			values:    []string{},
			wantCount: 0,
		},
		{
			name:      "single CIDR",
			values:    []string{"192.168.1.0/24"},
			wantCount: 1,
		},
		{
			name:      "multiple CIDRs",
			values:    []string{"192.168.1.0/24", "10.0.0.0/8"},
			wantCount: 2,
		},
		{
			name:      "duplicate CIDRs are deduplicated",
			values:    []string{"192.168.1.0/24", "192.168.1.0/24"},
			wantCount: 1,
		},
		{
			name:      "invalid CIDR excluded",
			values:    []string{"192.168.1.0/24", "invalid"},
			wantCount: 1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var warnings []string
			result := parseCIDRMap(tt.values, &warnings)

			if len(result) != tt.wantCount {
				t.Errorf("parseCIDRMap() returned %d entries, want %d", len(result), tt.wantCount)
			}
		})
	}
}

func TestParseCIDRMap_ContainsExpectedCIDR(t *testing.T) {
	values := []string{"192.168.1.0/24", "10.0.0.0/8"}
	var warnings []string
	result := parseCIDRMap(values, &warnings)

	// The canonical form of the CIDR should be in the map
	if _, ok := result["192.168.1.0/24"]; !ok {
		t.Error("parseCIDRMap() result should contain 192.168.1.0/24")
	}
	if _, ok := result["10.0.0.0/8"]; !ok {
		t.Error("parseCIDRMap() result should contain 10.0.0.0/8")
	}
}

func TestEnvironmentFromOverride(t *testing.T) {
	tests := []struct {
		name    string
		value   string
		wantEnv envdetect.Environment
		wantOK  bool
	}{
		{
			name:    "empty string",
			value:   "",
			wantEnv: envdetect.Unknown,
			wantOK:  false,
		},
		{
			name:    "auto",
			value:   "auto",
			wantEnv: envdetect.Unknown,
			wantOK:  false,
		},
		{
			name:    "AUTO uppercase",
			value:   "AUTO",
			wantEnv: envdetect.Unknown,
			wantOK:  false,
		},
		{
			name:    "Auto mixed case",
			value:   "Auto",
			wantEnv: envdetect.Unknown,
			wantOK:  false,
		},
		{
			name:    "native",
			value:   "native",
			wantEnv: envdetect.Native,
			wantOK:  true,
		},
		{
			name:    "NATIVE uppercase",
			value:   "NATIVE",
			wantEnv: envdetect.Native,
			wantOK:  true,
		},
		{
			name:    "docker_host",
			value:   "docker_host",
			wantEnv: envdetect.DockerHost,
			wantOK:  true,
		},
		{
			name:    "docker_bridge",
			value:   "docker_bridge",
			wantEnv: envdetect.DockerBridge,
			wantOK:  true,
		},
		{
			name:    "lxc_privileged",
			value:   "lxc_privileged",
			wantEnv: envdetect.LXCPrivileged,
			wantOK:  true,
		},
		{
			name:    "lxc_unprivileged",
			value:   "lxc_unprivileged",
			wantEnv: envdetect.LXCUnprivileged,
			wantOK:  true,
		},
		{
			name:    "value with whitespace",
			value:   "  native  ",
			wantEnv: envdetect.Native,
			wantOK:  true,
		},
		{
			name:    "unknown value",
			value:   "unknown_env",
			wantEnv: envdetect.Unknown,
			wantOK:  false,
		},
		{
			name:    "partial match not accepted",
			value:   "docker",
			wantEnv: envdetect.Unknown,
			wantOK:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotEnv, gotOK := environmentFromOverride(tt.value)
			if gotEnv != tt.wantEnv {
				t.Errorf("environmentFromOverride(%q) environment = %v, want %v", tt.value, gotEnv, tt.wantEnv)
			}
			if gotOK != tt.wantOK {
				t.Errorf("environmentFromOverride(%q) ok = %v, want %v", tt.value, gotOK, tt.wantOK)
			}
		})
	}
}

func TestShouldPruneContainerNetworks(t *testing.T) {
	tests := []struct {
		name string
		env  envdetect.Environment
		want bool
	}{
		{
			name: "DockerBridge should prune",
			env:  envdetect.DockerBridge,
			want: true,
		},
		{
			name: "LXCUnprivileged should prune",
			env:  envdetect.LXCUnprivileged,
			want: true,
		},
		{
			name: "Native should not prune",
			env:  envdetect.Native,
			want: false,
		},
		{
			name: "DockerHost should not prune",
			env:  envdetect.DockerHost,
			want: false,
		},
		{
			name: "LXCPrivileged should not prune",
			env:  envdetect.LXCPrivileged,
			want: false,
		},
		{
			name: "Unknown should not prune",
			env:  envdetect.Unknown,
			want: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := shouldPruneContainerNetworks(tt.env); got != tt.want {
				t.Errorf("shouldPruneContainerNetworks(%v) = %v, want %v", tt.env, got, tt.want)
			}
		})
	}
}

func TestIsLikelyContainerPhase(t *testing.T) {
	tests := []struct {
		name      string
		phaseName string
		want      bool
	}{
		{
			name:      "contains container lowercase",
			phaseName: "docker_container_network",
			want:      true,
		},
		{
			name:      "contains Container mixed case",
			phaseName: "DockerContainer",
			want:      true,
		},
		{
			name:      "contains CONTAINER uppercase",
			phaseName: "CONTAINER_NETWORK",
			want:      true,
		},
		{
			name:      "local_network does not contain container",
			phaseName: "local_network",
			want:      false,
		},
		{
			name:      "host_network does not contain container",
			phaseName: "host_network",
			want:      false,
		},
		{
			name:      "empty string",
			phaseName: "",
			want:      false,
		},
		{
			name:      "whitespace only",
			phaseName: "   ",
			want:      false,
		},
		{
			name:      "container with surrounding whitespace",
			phaseName: "  container_net  ",
			want:      true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := isLikelyContainerPhase(tt.phaseName); got != tt.want {
				t.Errorf("isLikelyContainerPhase(%q) = %v, want %v", tt.phaseName, got, tt.want)
			}
		})
	}
}

func TestFilterPhasesForEnvironment(t *testing.T) {
	// Helper to create a subnet phase
	makePhase := func(name string) envdetect.SubnetPhase {
		_, subnet, _ := net.ParseCIDR("192.168.1.0/24")
		return envdetect.SubnetPhase{
			Name:    name,
			Subnets: []net.IPNet{*subnet},
		}
	}

	tests := []struct {
		name       string
		phases     []envdetect.SubnetPhase
		env        envdetect.Environment
		wantPhases []string // names of phases that should remain
	}{
		{
			name:       "empty phases",
			phases:     []envdetect.SubnetPhase{},
			env:        envdetect.Native,
			wantPhases: []string{},
		},
		{
			name: "Native keeps local and host phases",
			phases: []envdetect.SubnetPhase{
				makePhase("local_network"),
				makePhase("host_network"),
				makePhase("container_network"),
			},
			env:        envdetect.Native,
			wantPhases: []string{"local_network", "host_network"},
		},
		{
			name: "DockerHost keeps local and host phases",
			phases: []envdetect.SubnetPhase{
				makePhase("local_network"),
				makePhase("container_network"),
			},
			env:        envdetect.DockerHost,
			wantPhases: []string{"local_network"},
		},
		{
			name: "LXCPrivileged keeps local and host phases",
			phases: []envdetect.SubnetPhase{
				makePhase("host_interface"),
				makePhase("container_network"),
			},
			env:        envdetect.LXCPrivileged,
			wantPhases: []string{"host_interface"},
		},
		{
			name: "DockerBridge keeps container, inferred, and host phases",
			phases: []envdetect.SubnetPhase{
				makePhase("local_network"),
				makePhase("container_network"),
				makePhase("inferred_subnet"),
				makePhase("host_network"),
			},
			env:        envdetect.DockerBridge,
			wantPhases: []string{"container_network", "inferred_subnet", "host_network"},
		},
		{
			name: "LXCUnprivileged keeps lxc, container, and parent phases",
			phases: []envdetect.SubnetPhase{
				makePhase("lxc_network"),
				makePhase("container_network"),
				makePhase("parent_host"),
				makePhase("local_network"),
			},
			env:        envdetect.LXCUnprivileged,
			wantPhases: []string{"lxc_network", "container_network", "parent_host"},
		},
		{
			name: "Unknown keeps all phases",
			phases: []envdetect.SubnetPhase{
				makePhase("local_network"),
				makePhase("container_network"),
			},
			env:        envdetect.Unknown,
			wantPhases: []string{"local_network", "container_network"},
		},
		{
			name: "no matching phases preserves original",
			phases: []envdetect.SubnetPhase{
				makePhase("random_network"),
			},
			env:        envdetect.Native,
			wantPhases: []string{"random_network"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			profile := &envdetect.EnvironmentProfile{
				Type:   tt.env,
				Phases: tt.phases,
			}

			filterPhasesForEnvironment(profile, tt.env)

			if len(profile.Phases) != len(tt.wantPhases) {
				t.Errorf("filterPhasesForEnvironment() left %d phases, want %d", len(profile.Phases), len(tt.wantPhases))
				return
			}

			for i, want := range tt.wantPhases {
				if profile.Phases[i].Name != want {
					t.Errorf("filterPhasesForEnvironment() phase[%d].Name = %q, want %q", i, profile.Phases[i].Name, want)
				}
			}
		})
	}
}

func TestApplyConfigToProfile_NilProfile(t *testing.T) {
	// Ensure ApplyConfigToProfile handles nil profile without panic
	cfg := config.DiscoveryConfig{
		EnvironmentOverride: "native",
	}
	ApplyConfigToProfile(nil, cfg) // Should not panic
}

func TestApplyConfigToProfile_InvalidEnvironmentOverride(t *testing.T) {
	profile := &envdetect.EnvironmentProfile{
		Type: envdetect.Native,
	}
	cfg := config.DiscoveryConfig{
		EnvironmentOverride: "invalid_environment",
	}

	ApplyConfigToProfile(profile, cfg)

	// Should add warning for unknown environment
	if len(profile.Warnings) != 1 {
		t.Errorf("Expected 1 warning for invalid environment override, got %d", len(profile.Warnings))
	}
}

func TestApplyConfigToProfile_PolicyOverrides(t *testing.T) {
	profile := &envdetect.EnvironmentProfile{
		Type: envdetect.Native,
		Policy: envdetect.ScanPolicy{
			MaxHostsPerScan: 100,
			MaxConcurrent:   10,
			DialTimeout:     1000,
			HTTPTimeout:     2000,
		},
	}

	cfg := config.DiscoveryConfig{
		MaxHostsPerScan:  500,
		MaxConcurrent:    50,
		EnableReverseDNS: true,
		ScanGateways:     true,
		DialTimeout:      5000,
		HTTPTimeout:      10000,
	}

	ApplyConfigToProfile(profile, cfg)

	if profile.Policy.MaxHostsPerScan != 500 {
		t.Errorf("MaxHostsPerScan = %d, want 500", profile.Policy.MaxHostsPerScan)
	}
	if profile.Policy.MaxConcurrent != 50 {
		t.Errorf("MaxConcurrent = %d, want 50", profile.Policy.MaxConcurrent)
	}
	if !profile.Policy.EnableReverseDNS {
		t.Error("EnableReverseDNS should be true")
	}
	if !profile.Policy.ScanGateways {
		t.Error("ScanGateways should be true")
	}
}
