package ip

import (
	"net"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestNormalizeIP(t *testing.T) {
	tests := []struct {
		name string
		ip   string
		want string
	}{
		{"plain_ip", "192.168.1.1", "192.168.1.1"},
		{"with_port", "192.168.1.1:8080", "192.168.1.1"},
		{"with_spaces", "  192.168.1.1  ", "192.168.1.1"},
		{"ipv6", "::1", "::1"},
		{"ipv6_with_port", "[::1]:8080", "::1"},
		{"empty", "", ""},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := normalizeIP(tt.ip)
			require.Equal(t, tt.want, got)
		})
	}
}

func TestIsPrivateIP(t *testing.T) {
	tests := []struct {
		name string
		ip   string
		want bool
	}{
		{"private_10.x", "10.0.0.1", true},
		{"private_172.16.x", "172.16.0.1", true},
		{"private_172.31.x", "172.31.0.1", true},
		{"private_192.168.x", "192.168.1.1", true},
		{"private_loopback", "127.0.0.1", true},
		{"private_ipv6_loopback", "::1", true},
		{"public_ip", "8.8.8.8", false},
		{"public_ip2", "1.1.1.1", false},
		{"invalid_ip", "invalid", false},
		{"empty_ip", "", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := isPrivateIP(tt.ip)
			require.Equal(t, tt.want, got)
		})
	}
}

func TestCompileIPRules(t *testing.T) {
	tests := []struct {
		name         string
		patterns     []string
		wantCIDRs    int
		wantIPs      int
		wantPatterns int
	}{
		{
			name:         "empty",
			patterns:     []string{},
			wantCIDRs:    0,
			wantIPs:      0,
			wantPatterns: 0,
		},
		{
			name:         "single_ip",
			patterns:     []string{"192.168.1.1"},
			wantCIDRs:    0,
			wantIPs:      1,
			wantPatterns: 1,
		},
		{
			name:         "single_cidr",
			patterns:     []string{"192.168.0.0/24"},
			wantCIDRs:    1,
			wantIPs:      0,
			wantPatterns: 1,
		},
		{
			name:         "mixed",
			patterns:     []string{"192.168.1.1", "10.0.0.0/8"},
			wantCIDRs:    1,
			wantIPs:      1,
			wantPatterns: 2,
		},
		{
			name:         "with_invalid",
			patterns:     []string{"192.168.1.1", "invalid", "10.0.0.0/8"},
			wantCIDRs:    1,
			wantIPs:      1,
			wantPatterns: 3,
		},
		{
			name:         "with_empty_and_spaces",
			patterns:     []string{"", "  ", "192.168.1.1"},
			wantCIDRs:    0,
			wantIPs:      1,
			wantPatterns: 3,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			rules := CompileIPRules(tt.patterns)
			require.Equal(t, tt.wantCIDRs, len(rules.CIDRs))
			require.Equal(t, tt.wantIPs, len(rules.IPs))
			require.Equal(t, tt.wantPatterns, rules.PatternCount)
		})
	}
}

func TestMatchesCompiledRules(t *testing.T) {
	rules := CompileIPRules([]string{"192.168.1.1", "10.0.0.0/8"})

	tests := []struct {
		name string
		ip   string
		want bool
	}{
		{"match_ip", "192.168.1.1", true},
		{"match_cidr", "10.0.1.1", true},
		{"no_match", "8.8.8.8", false},
		{"invalid", "invalid", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ip := net.ParseIP(tt.ip)
			got := matchesCompiledRules(ip, rules)
			require.Equal(t, tt.want, got)
		})
	}
}

func TestMatchesCompiledRules_NilCases(t *testing.T) {
	rules := CompileIPRules([]string{"192.168.1.1"})

	// nil IP
	require.False(t, matchesCompiledRules(nil, rules))

	// nil rules
	validIP := net.ParseIP("192.168.1.1")
	require.False(t, matchesCompiledRules(validIP, nil))
}

func TestMatchesPattern(t *testing.T) {
	tests := []struct {
		name    string
		client  string
		pattern string
		want    bool
	}{
		{"ip_match", "192.168.1.1", "192.168.1.1", true},
		{"ip_no_match", "192.168.1.1", "192.168.1.2", false},
		{"cidr_match", "192.168.1.50", "192.168.1.0/24", true},
		{"cidr_no_match", "192.168.2.1", "192.168.1.0/24", false},
		{"invalid_client", "invalid", "192.168.1.0/24", false},
		{"invalid_pattern", "192.168.1.1", "invalid", false},
		{"invalid_cidr", "192.168.1.1", "192.168.1/24", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := MatchesPattern(tt.client, tt.pattern)
			require.Equal(t, tt.want, got)
		})
	}
}

func TestMatchesAnyPattern(t *testing.T) {
	patterns := []string{"192.168.1.1", "10.0.0.0/8"}

	require.True(t, MatchesAnyPattern("192.168.1.1", patterns))
	require.True(t, MatchesAnyPattern("10.0.1.1", patterns))
	require.False(t, MatchesAnyPattern("8.8.8.8", patterns))
	require.False(t, MatchesAnyPattern("8.8.8.8", []string{}))
}

func TestCheckIPRestriction(t *testing.T) {
	tests := []struct {
		name      string
		clientIP  string
		whitelist []string
		blacklist []string
		wantAllow bool
	}{
		{"no_restrictions", "192.168.1.1", nil, nil, true},
		{"whitelist_match", "192.168.1.1", []string{"192.168.1.0/24"}, nil, true},
		{"whitelist_no_match", "192.168.1.1", []string{"10.0.0.0/8"}, nil, false},
		{"blacklist_match", "192.168.1.1", nil, []string{"192.168.1.0/24"}, false},
		{"blacklist_no_match", "192.168.1.1", nil, []string{"10.0.0.0/8"}, true},
		{"blacklist_priority", "192.168.1.1", []string{"0.0.0.0/0"}, []string{"192.168.1.0/24"}, false},
		{"empty_ip", "", []string{"192.168.1.0/24"}, nil, false},
		{"invalid_ip", "invalid", []string{"192.168.1.0/24"}, nil, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			allow, _ := CheckIPRestriction(tt.clientIP, tt.whitelist, tt.blacklist)
			require.Equal(t, tt.wantAllow, allow)
		})
	}
}

func TestValidateIPPattern(t *testing.T) {
	tests := []struct {
		name    string
		pattern string
		want    bool
	}{
		{"valid_ip", "192.168.1.1", true},
		{"valid_ipv6", "::1", true},
		{"valid_cidr", "192.168.0.0/24", true},
		{"invalid", "not-an-ip", false},
		{"empty", "", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := ValidateIPPattern(tt.pattern)
			require.Equal(t, tt.want, got)
		})
	}
}

func TestValidateIPPatterns(t *testing.T) {
	patterns := []string{"192.168.1.1", "invalid", "192.168.0.0/24", "not-an-ip"}
	invalid := ValidateIPPatterns(patterns)
	require.Equal(t, []string{"invalid", "not-an-ip"}, invalid)

	// all valid
	validPatterns := []string{"192.168.1.1", "192.168.0.0/24"}
	invalid = ValidateIPPatterns(validPatterns)
	require.Empty(t, invalid)
}
