package uspfilter

import (
	"net"
	"net/netip"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/netbirdio/netbird/client/iface/wgaddr"
)

func TestLocalIPManager(t *testing.T) {
	tests := []struct {
		name      string
		setupAddr wgaddr.Address
		testIP    netip.Addr
		expected  bool
	}{
		{
			name: "Localhost range",
			setupAddr: wgaddr.Address{
				IP:      netip.MustParseAddr("192.168.1.1"),
				Network: netip.MustParsePrefix("192.168.1.0/24"),
			},
			testIP:   netip.MustParseAddr("127.0.0.2"),
			expected: true,
		},
		{
			name: "Localhost standard address",
			setupAddr: wgaddr.Address{
				IP:      netip.MustParseAddr("192.168.1.1"),
				Network: netip.MustParsePrefix("192.168.1.0/24"),
			},
			testIP:   netip.MustParseAddr("127.0.0.1"),
			expected: true,
		},
		{
			name: "Localhost range edge",
			setupAddr: wgaddr.Address{
				IP:      netip.MustParseAddr("192.168.1.1"),
				Network: netip.MustParsePrefix("192.168.1.0/24"),
			},
			testIP:   netip.MustParseAddr("127.255.255.255"),
			expected: true,
		},
		{
			name: "Local IP matches",
			setupAddr: wgaddr.Address{
				IP:      netip.MustParseAddr("192.168.1.1"),
				Network: netip.MustParsePrefix("192.168.1.0/24"),
			},
			testIP:   netip.MustParseAddr("192.168.1.1"),
			expected: true,
		},
		{
			name: "Local IP doesn't match",
			setupAddr: wgaddr.Address{
				IP:      netip.MustParseAddr("192.168.1.1"),
				Network: netip.MustParsePrefix("192.168.1.0/24"),
			},
			testIP:   netip.MustParseAddr("192.168.1.2"),
			expected: false,
		},
		{
			name: "Local IP doesn't match - addresses 32 apart",
			setupAddr: wgaddr.Address{
				IP:      netip.MustParseAddr("192.168.1.1"),
				Network: netip.MustParsePrefix("192.168.1.0/24"),
			},
			testIP:   netip.MustParseAddr("192.168.1.33"),
			expected: false,
		},
		{
			name: "IPv6 address matches",
			setupAddr: wgaddr.Address{
				IP:      netip.MustParseAddr("100.64.0.1"),
				Network: netip.MustParsePrefix("100.64.0.0/16"),
				IPv6:    netip.MustParseAddr("fd00::1"),
				IPv6Net: netip.MustParsePrefix("fd00::/64"),
			},
			testIP:   netip.MustParseAddr("fd00::1"),
			expected: true,
		},
		{
			name: "IPv6 address does not match",
			setupAddr: wgaddr.Address{
				IP:      netip.MustParseAddr("100.64.0.1"),
				Network: netip.MustParsePrefix("100.64.0.0/16"),
				IPv6:    netip.MustParseAddr("fd00::1"),
				IPv6Net: netip.MustParsePrefix("fd00::/64"),
			},
			testIP:   netip.MustParseAddr("fd00::99"),
			expected: false,
		},
		{
			name: "No aliasing between similar IPs",
			setupAddr: wgaddr.Address{
				IP:      netip.MustParseAddr("192.168.1.1"),
				Network: netip.MustParsePrefix("192.168.1.0/24"),
			},
			testIP:   netip.MustParseAddr("192.168.0.17"),
			expected: false,
		},
		{
			name: "IPv6 loopback",
			setupAddr: wgaddr.Address{
				IP:      netip.MustParseAddr("100.64.0.1"),
				Network: netip.MustParsePrefix("100.64.0.0/16"),
			},
			testIP:   netip.MustParseAddr("::1"),
			expected: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			manager := newLocalIPManager()

			mock := &IFaceMock{
				AddressFunc: func() wgaddr.Address {
					return tt.setupAddr
				},
			}

			err := manager.UpdateLocalIPs(mock)
			require.NoError(t, err)

			result := manager.IsLocalIP(tt.testIP)
			require.Equal(t, tt.expected, result)
		})
	}
}

func TestLocalIPManager_AllInterfaces(t *testing.T) {
	manager := newLocalIPManager()
	mock := &IFaceMock{}

	// Get actual local interfaces
	interfaces, err := net.Interfaces()
	require.NoError(t, err)

	var tests []struct {
		ip       string
		expected bool
	}

	// Add all local interface IPs to test cases
	for _, iface := range interfaces {
		addrs, err := iface.Addrs()
		require.NoError(t, err)

		for _, addr := range addrs {
			var ip net.IP
			switch v := addr.(type) {
			case *net.IPNet:
				ip = v.IP
			case *net.IPAddr:
				ip = v.IP
			default:
				continue
			}

			if ip4 := ip.To4(); ip4 != nil {
				tests = append(tests, struct {
					ip       string
					expected bool
				}{
					ip:       ip4.String(),
					expected: true,
				})
			}
		}
	}

	// Add some external IPs as negative test cases
	externalIPs := []string{
		"8.8.8.8",
		"1.1.1.1",
		"208.67.222.222",
	}
	for _, ip := range externalIPs {
		tests = append(tests, struct {
			ip       string
			expected bool
		}{
			ip:       ip,
			expected: false,
		})
	}

	require.NotEmpty(t, tests, "No test cases generated")

	err = manager.UpdateLocalIPs(mock)
	require.NoError(t, err)

	t.Logf("Testing %d IPs", len(tests))
	for _, tt := range tests {
		t.Run(tt.ip, func(t *testing.T) {
			result := manager.IsLocalIP(netip.MustParseAddr(tt.ip))
			require.Equal(t, tt.expected, result, "IP: %s", tt.ip)
		})
	}
}
